从 Cloud Run 连接

本页介绍从 Cloud Run 中运行的服务连接到 Cloud SQL 实例的相关信息和示例。

如需了解运行连接到 Cloud SQL 的 Cloud Run 示例 Web 应用的分步说明,请参阅从 Cloud Run 连接快速入门

Cloud SQL 是一项全代管式数据库服务,可帮助您在云端设置、维护、管理和控制关系型数据库。

Cloud Run 是一个代管式计算平台,可让您直接在 Google Cloud 基础架构之上运行容器。

设置 Cloud SQL 实例

  1. 如果您要从某个 Google Cloud 项目连接,则在该项目中启用 Cloud SQL Admin API(如果您尚未执行此操作):

    启用 API

  2. 创建 Cloud SQL for MySQL 实例

    默认情况下,Cloud SQL 会为新实例分配公共 IP 地址。 您还可以选择分配专用 IP 地址。如需详细了解这两种地址的连接方案,请参阅连接概览页面。

配置 Cloud Run

配置 Cloud Run 的步骤取决于您分配给 Cloud SQL 实例的 IP 地址类型。如果您通过直接 VPC 出站流量或无服务器 VPC 访问通道连接器路由所有出站流量,请使用专用 IP 地址。比较两种网络出站流量方法

公共 IP(默认)

  • 确保之前创建的实例具有公共 IP 地址。您可以在 Google Cloud 控制台中的实例概览页面上验证这一点。如果您需要添加 IP 地址,请参阅配置公共 IP 页面来查看相关说明。
  • 获取实例的 INSTANCE_CONNECTION_NAME。您可以在 Google Cloud 控制台中的实例概览页面上或者通过运行以下命令找到它:gcloud sql instances describe INSTANCE_NAME
  • 为您的服务配置服务账号。请确保该服务账号具有适当的 Cloud SQL 角色和权限以连接到 Cloud SQL
      您的服务的服务账号需要以下某个 IAM 角色
      • Cloud SQL Client(首选)
      • Cloud SQL Admin
      或者,您也可以手动分配下列 IAM 权限:
      • cloudsql.instances.connect
      • cloudsql.instances.get

与任何配置更改一样,为 Cloud SQL 连接设置新配置会导致创建新的 Cloud Run 修订版本。除非您进行了明确更新,否则后续修订版本也将自动采用此 Cloud SQL 连接。

控制台

  1. 转到 Cloud Run

  2. 开始配置服务:

    • 如果您要向新服务添加 Cloud SQL 连接,请执行以下操作:

      您需要将服务容器化并上传到 Container Registry 或 Artifact Registry。如果您还没有容器映像,请参阅这些有关构建和部署容器映像的说明。

    • 如果您要向现有服务添加 Cloud SQL 连接,请执行以下操作:
      1. 点击服务名称。
      2. 点击修改和部署新修订版本标签页。
  3. 允许连接到 Cloud SQL 实例:
    1. 点击容器、变量和密钥、连接、安全性
    2. 点击容器标签页。
    3. 向下滚动到 Cloud SQL 连接
    4. 点击添加连接
    5. 如果您尚未启用 Cloud SQL Admin API,请点击启用 Cloud SQL Admin 按钮。

    添加 Cloud SQL 连接

    • 如果您是在向自己项目中的 Cloud SQL 实例添加连接,请从下拉菜单中选择所需的 Cloud SQL 实例。
    • 如果您使用的是来自其他项目的 Cloud SQL 实例,请在下拉列表中选择自定义连接字符串,然后以 PROJECT-ID:REGION:INSTANCE-ID 格式输入完整实例连接名称。
    • 如果您要删除连接,请将光标悬停在连接右侧以显示垃圾箱图标,然后点击该图标。
  4. 点击创建部署

命令行

在使用下面的任何命令之前,请先进行以下替换:

  • IMAGE 替换为您要部署的映像
  • SERVICE_NAME 替换为您的 Cloud Run 服务的名称
  • INSTANCE_CONNECTION_NAME 替换为 Cloud SQL 实例的实例连接名称,或以英文逗号分隔的连接名称列表。

    如果要部署新容器,请使用以下命令:

    gcloud run deploy \
      --image=IMAGE \
      --add-cloudsql-instances=INSTANCE_CONNECTION_NAME
    如果要更新现有服务,请使用以下命令:
    gcloud run services update SERVICE_NAME \
      --add-cloudsql-instances=INSTANCE_CONNECTION_NAME

Terraform

以下代码创建一个基础 Cloud Run 容器,其中包含一个连接的 Cloud SQL 实例。

resource "google_cloud_run_v2_service" "default" {
  name     = "cloudrun-service"
  location = "us-central1"

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest" # Image to deploy

      volume_mounts {
        name       = "cloudsql"
        mount_path = "/cloudsql"
      }
    }
    volumes {
      name = "cloudsql"
      cloud_sql_instance {
        instances = [google_sql_database_instance.default.connection_name]
      }
    }
  }
  client     = "terraform"
  depends_on = [google_project_service.secretmanager_api, google_project_service.cloudrun_api, google_project_service.sqladmin_api]
}

  1. 输入 terraform apply 以应用更改。
  2. 查看更改,方法是检查 Cloud Run 服务,点击修订版本标签页,然后点击连接标签页。

专用 IP

如果授权服务账号所属的项目与包含 Cloud SQL 实例的项目不同,请执行以下操作:

  • 在这两个项目中启用 Cloud SQL Admin API。
  • 对于包含 Cloud SQL 实例的项目中的服务账号,添加 IAM 权限
直接 VPC 出站流量和连接器使用专用 IP 地址来处理与您的 VPC 网络的通信。如需使用以下某种出站流量方法直接通过专用 IP 地址进行连接,请执行以下操作:
  1. 确保前面创建的 Cloud SQL 实例具有专用 IP 地址。如需添加内部 IP 地址,请参阅配置专用 IP
  2. 配置出站流量方法以连接到 Cloud SQL 实例所在的 VPC 网络。 请注意以下条件:
    • 直接 VPC 出站流量和无服务器 VPC 访问通道均支持与使用 Cloud VPNVPC 网络对等互连连接的 VPC 网络进行通信。
    • 直接 VPC 出站流量和无服务器 VPC 访问通道不支持旧版网络
    • 除非您使用共享 VPC,否则连接器必须与使用它的资源位于同一项目和区域中,但该连接器可向其他区域中的资源发送流量。
  3. 使用实例的专用 IP 地址和端口 3306 进行连接。

连接到 Cloud SQL

配置 Cloud Run 后,您可以连接到 Cloud SQL 实例。

公共 IP(默认)

对于公共 IP 路径,Cloud Run 提供加密功能,并通过两种方式使用 Cloud SQL Auth 代理进行连接:

使用 Secret Manager

Google 建议您使用 Secret Manager 存储 SQL 凭据等敏感信息。您可以使用 Cloud Run 将 Secret 作为环境变量传递或装载为卷

在 Secret Manager 中创建 Secret 后,使用以下命令更新现有服务:

命令行

gcloud run services update SERVICE_NAME \
  --add-cloudsql-instances=INSTANCE_CONNECTION_NAME
  --update-env-vars=INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME_SECRET \
  --update-secrets=DB_USER=DB_USER_SECRET:latest \
  --update-secrets=DB_PASS=DB_PASS_SECRET:latest \
  --update-secrets=DB_NAME=DB_NAME_SECRET:latest

Terraform

以下命令会创建 Secret 资源,以使用 google_secret_manager_secretgoogle_secret_manager_secret_version 安全地保存数据库用户、密码和名称值。请注意,您必须更新项目计算服务账号才能访问每个 Secret。


# Create dbuser secret
resource "google_secret_manager_secret" "dbuser" {
  secret_id = "dbusersecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbuser secret
resource "google_secret_manager_secret_version" "dbuser_data" {
  secret      = google_secret_manager_secret.dbuser.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbuser secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbuser" {
  secret_id = google_secret_manager_secret.dbuser.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}


# Create dbpass secret
resource "google_secret_manager_secret" "dbpass" {
  secret_id = "dbpasssecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbpass secret
resource "google_secret_manager_secret_version" "dbpass_data" {
  secret      = google_secret_manager_secret.dbpass.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbpass secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbpass" {
  secret_id = google_secret_manager_secret.dbpass.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}


# Create dbname secret
resource "google_secret_manager_secret" "dbname" {
  secret_id = "dbnamesecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbname secret
resource "google_secret_manager_secret_version" "dbname_data" {
  secret      = google_secret_manager_secret.dbname.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbname secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbname" {
  secret_id = google_secret_manager_secret.dbname.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}

更新 Cloud Run 主资源以包含新 Secret。

resource "google_cloud_run_v2_service" "default" {
  name     = "cloudrun-service"
  location = "us-central1"

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest" # Image to deploy

      # Sets a environment variable for instance connection name
      env {
        name  = "INSTANCE_CONNECTION_NAME"
        value = google_sql_database_instance.default.connection_name
      }
      # Sets a secret environment variable for database user secret
      env {
        name = "DB_USER"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbuser.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }
      # Sets a secret environment variable for database password secret
      env {
        name = "DB_PASS"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbpass.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }
      # Sets a secret environment variable for database name secret
      env {
        name = "DB_NAME"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbname.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }

      volume_mounts {
        name       = "cloudsql"
        mount_path = "/cloudsql"
      }
    }
    volumes {
      name = "cloudsql"
      cloud_sql_instance {
        instances = [google_sql_database_instance.default.connection_name]
      }
    }
  }
  client     = "terraform"
  depends_on = [google_project_service.secretmanager_api, google_project_service.cloudrun_api, google_project_service.sqladmin_api]
}

输入 terraform apply 以应用更改。

示例命令使用 Secret 版本 latest;但是,Google 建议将 Secret 固定为特定版本 SECRET_NAME:v1

专用 IP

对于专用 IP 路径,您的应用会通过 VPC 网络直接连接到您的实例。此方法使用 TCP 直接连接到 Cloud SQL 实例,无需使用 Cloud SQL Auth 代理。

使用 TCP 连接

使用作为主机的 Cloud SQL 实例的专用 IP 地址和端口 3306 进行连接。

Python

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

import os

import sqlalchemy

def connect_tcp_socket() -> sqlalchemy.engine.base.Engine:
    """Initializes a TCP connection pool for a Cloud SQL instance of MySQL."""
    # Note: Saving credentials in environment variables is convenient, but not
    # secure - consider a more secure solution such as
    # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
    # keep secrets safe.
    db_host = os.environ[
        "INSTANCE_HOST"
    ]  # e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
    db_user = os.environ["DB_USER"]  # e.g. 'my-db-user'
    db_pass = os.environ["DB_PASS"]  # e.g. 'my-db-password'
    db_name = os.environ["DB_NAME"]  # e.g. 'my-database'
    db_port = os.environ["DB_PORT"]  # e.g. 3306

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # mysql+pymysql://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
        sqlalchemy.engine.url.URL.create(
            drivername="mysql+pymysql",
            username=db_user,
            password=db_pass,
            host=db_host,
            port=db_port,
            database=db_name,
        ),
        # ...
    )
    return pool

Java

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

注意:

  • INSTANCE_CONNECTION_NAME 应表示为 <MY-PROJECT>:<INSTANCE-REGION>:<INSTANCE-NAME>
  • 使用参数 ipTypes=PRIVATE 将强制 SocketFactory 与实例的关联专用 IP 连接
  • 如需查看 pom.xml 文件的 JDBC 套接字工厂版本的要求,请点击此处


import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class TcpConnectionPoolFactory extends ConnectionPoolFactory {

  // Saving credentials in environment variables is convenient, but not secure - consider a more
  // secure solution such as https://cloud.google.com/secret-manager/ to help keep secrets safe.
  private static final String DB_USER = System.getenv("DB_USER");
  private static final String DB_PASS = System.getenv("DB_PASS");
  private static final String DB_NAME = System.getenv("DB_NAME");

  private static final String INSTANCE_HOST = System.getenv("INSTANCE_HOST");
  private static final String DB_PORT = System.getenv("DB_PORT");

  public static DataSource createConnectionPool() {
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();

    // The following URL is equivalent to setting the config options below:
    // jdbc:mysql://<INSTANCE_HOST>:<DB_PORT>/<DB_NAME>?user=<DB_USER>&password=<DB_PASS>
    // See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
    // https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url

    // Configure which instance and what database user to connect with.
    config.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s", INSTANCE_HOST, DB_PORT, DB_NAME));
    config.setUsername(DB_USER); // e.g. "root", "mysql"
    config.setPassword(DB_PASS); // e.g. "my-password"

    // ... Specify additional connection properties here.
    // ...

    // Initialize the connection pool using the configuration object.
    return new HikariDataSource(config);
  }
}

Node.js

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

const mysql = require('promise-mysql');
const fs = require('fs');

// createTcpPool initializes a TCP connection pool for a Cloud SQL
// instance of MySQL.
const createTcpPool = async config => {
  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  const dbConfig = {
    host: process.env.INSTANCE_HOST, // e.g. '127.0.0.1'
    port: process.env.DB_PORT, // e.g. '3306'
    user: process.env.DB_USER, // e.g. 'my-db-user'
    password: process.env.DB_PASS, // e.g. 'my-db-password'
    database: process.env.DB_NAME, // e.g. 'my-database'
    // ... Specify additional properties here.
    ...config,
  };
  // Establish a connection to the database.
  return mysql.createPool(dbConfig);
};

Go

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

package cloudsql

import (
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/go-sql-driver/mysql"
)

// connectTCPSocket initializes a TCP connection pool for a Cloud SQL
// instance of MySQL.
func connectTCPSocket() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Fatal Error in connect_tcp.go: %s environment variable not set.", k)
		}
		return v
	}
	// Note: Saving credentials in environment variables is convenient, but not
	// secure - consider a more secure solution such as
	// Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
	// keep secrets safe.
	var (
		dbUser    = mustGetenv("DB_USER")       // e.g. 'my-db-user'
		dbPwd     = mustGetenv("DB_PASS")       // e.g. 'my-db-password'
		dbName    = mustGetenv("DB_NAME")       // e.g. 'my-database'
		dbPort    = mustGetenv("DB_PORT")       // e.g. '3306'
		dbTCPHost = mustGetenv("INSTANCE_HOST") // e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
	)

	dbURI := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",
		dbUser, dbPwd, dbTCPHost, dbPort, dbName)

	// dbPool is the pool of database connections.
	dbPool, err := sql.Open("mysql", dbURI)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}

	// ...

	return dbPool, nil
}

C#

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

using MySql.Data.MySqlClient;
using System;

namespace CloudSql
{
    public class MySqlTcp
    {
        public static MySqlConnectionStringBuilder NewMysqlTCPConnectionString()
        {
            // Equivalent connection string:
            // "Uid=<DB_USER>;Pwd=<DB_PASS>;Host=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new MySqlConnectionStringBuilder()
            {
                // Note: Saving credentials in environment variables is convenient, but not
                // secure - consider a more secure solution such as
                // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
                // keep secrets safe.
                Server = Environment.GetEnvironmentVariable("INSTANCE_HOST"),   // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                UserID = Environment.GetEnvironmentVariable("DB_USER"),   // e.g. 'my-db-user'
                Password = Environment.GetEnvironmentVariable("DB_PASS"), // e.g. 'my-db-password'
                Database = Environment.GetEnvironmentVariable("DB_NAME"), // e.g. 'my-database'

                // The Cloud SQL proxy provides encryption between the proxy and instance.
                SslMode = MySqlSslMode.Disabled,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;

        }
    }
}

Ruby

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

tcp: &tcp
  adapter: mysql2
  # Configure additional properties here
  # Note: Saving credentials in environment variables is convenient, but not
  # secure - consider a more secure solution such as
  # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  # keep secrets safe.
  username: <%= ENV["DB_USER"] %>  # e.g. "my-database-user"
  password: <%= ENV["DB_PASS"] %> # e.g. "my-database-password"
  database: <%= ENV.fetch("DB_NAME") { "vote_development" } %>
  host: "<%= ENV.fetch("INSTANCE_HOST") { "127.0.0.1" }%>" # '172.17.0.1' if deployed to GAE Flex
  port: <%= ENV.fetch("DB_PORT") { 3306 }%>

PHP

如需了解 Web 应用环境下的此代码段,请查看 GitHub 上的 README

namespace Google\Cloud\Samples\CloudSQL\MySQL;

use PDO;
use PDOException;
use RuntimeException;
use TypeError;

class DatabaseTcp
{
    public static function initTcpDatabaseConnection(): PDO
    {
        try {
            // Note: Saving credentials in environment variables is convenient, but not
            // secure - consider a more secure solution such as
            // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
            // keep secrets safe.
            $username = getenv('DB_USER'); // e.g. 'your_db_user'
            $password = getenv('DB_PASS'); // e.g. 'your_db_password'
            $dbName = getenv('DB_NAME'); // e.g. 'your_db_name'
            $instanceHost = getenv('INSTANCE_HOST'); // e.g. '127.0.0.1' ('172.17.0.1' for GAE Flex)

            // Connect using TCP
            $dsn = sprintf('mysql:dbname=%s;host=%s', $dbName, $instanceHost);

            // Connect to the database
            $conn = new PDO(
                $dsn,
                $username,
                $password,
                # ...
            );
        } catch (TypeError $e) {
            throw new RuntimeException(
                sprintf(
                    'Invalid or missing configuration! Make sure you have set ' .
                        '$username, $password, $dbName, and $instanceHost (for TCP mode). ' .
                        'The PHP error was %s',
                    $e->getMessage()
                ),
                $e->getCode(),
                $e
            );
        } catch (PDOException $e) {
            throw new RuntimeException(
                sprintf(
                    'Could not connect to the Cloud SQL Database. Check that ' .
                        'your username and password are correct, that the Cloud SQL ' .
                        'proxy is running, and that the database exists and is ready ' .
                        'for use. For more assistance, refer to %s. The PDO error was %s',
                    'https://cloud.google.com/sql/docs/mysql/connect-external-app',
                    $e->getMessage()
                ),
                $e->getCode(),
                $e
            );
        }

        return $conn;
    }
}

最佳做法和其他信息

在本地测试应用时,您可以使用 Cloud SQL Auth 代理。如需了解详细说明,请参阅快速入门:使用 Cloud SQL Auth 代理

您也可以通过 Docker 容器使用 Cloud SQL 代理进行测试。

连接池

与底层数据库的连接可能会被数据库服务器本身或平台基础架构断开。我们建议您使用支持连接池的客户端库,因为连接池可自动重新连接断开的客户端连接。如需查看有关如何使用连接池的详细示例,请参阅管理数据库连接页面。

连接限制

MySQL 和 PostgreSQL 版本的 Cloud SQL 都对并发连接数设定了上限,这些限制可能因所选的数据库引擎而异(请参阅 Cloud SQL 配额和限制页面)。

Cloud Run 容器实例与一个 Cloud SQL 数据库之间最多只能建立 100 个连接。Cloud Run 服务或作业的每个实例都可以与数据库建立 100 个连接,随着此服务或作业扩缩,每个部署的连接总数可以增加。

您可以使用连接池来限制每个实例使用的最大连接数。如需查看有关如何限制连接数的详细示例,请参阅管理数据库连接页面。

API 配额限制

Cloud Run 提供了一种使用 Cloud SQL Auth 代理进行连接的机制,该代理使用 Cloud SQL Admin API。API 配额限制适用于 Cloud SQL Auth 代理。使用的 Cloud SQL Admin API 配额大约是任何一次部署的特定服务的 Cloud Run 实例数所配置 Cloud SQL 实例数的两倍。您可以限制或增加 Cloud Run 实例的数量,以修改预期消耗的 API 配额。

后续步骤