从 App Engine 标准环境连接

本页面介绍了从 App Engine 标准环境中运行的服务连接到 Cloud SQL 实例的相关信息和示例。

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

App Engine 是全代管式无服务器平台,可用于大规模开发和托管 Web 应用。您可以选择多种主流的语言、库和框架来开发应用,然后让 App Engine 为您预配服务器并根据需求扩缩应用实例数量。

设置 Cloud SQL 实例

  1. 在您用于连接的 Google Cloud 项目中启用 Cloud SQL Admin API(如果尚未启用):

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the API

  2. 创建 Cloud SQL for SQL Server 实例。我们建议您在 Cloud Run 服务所在的区域中选择 Cloud SQL 实例位置,以缩短延迟时间,从而避免一些网络费用并降低跨区域故障风险。

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

  3. 创建实例时,您可以为实例选择服务器证书 (CA) 层次结构,然后将该层次结构配置为实例的 serverCaMode。对于需要从 App Engine 标准环境 Web 应用连接的实例,您必须选择“每个实例一个 CA”选项 (GOOGLE_MANAGED_INTERNAL_CA) 作为其服务器 CA 模式。

配置 App Engine 标准环境

App Engine 标准环境的配置步骤取决于您分配给 Cloud SQL 实例的 IP 地址类型。

公共 IP(默认)

App Engine 标准环境支持使用 Go、Java 和 Python 连接器通过公共 IP 地址连接到 Cloud SQL for SQL Server。

如需配置 App Engine 标准环境以允许连接到使用公共 IP 地址的 Cloud SQL 实例,请执行以下操作:
  • 确保实例具有公共 IP 地址。您可以在 Google Cloud 控制台的实例概览页面上验证此配置。 如果您需要添加公共 IP 地址,请参阅配置公共 IP 地址页面来获取说明。
  • 获取实例的 INSTANCE_CONNECTION_NAME。您可以在 Google Cloud 控制台中的实例概览页面上或者通过运行以下 gcloud sql instances describe 命令获取此值:
    gcloud sql instances describe INSTANCE_NAME
       
    INSTANCE_NAME 替换为 Cloud SQL 实例的名称。
  • INSTANCE_NAME 变量替换为实例的名称。
  • 确保您的应用用于对 Cloud SQL 调用进行身份验证的服务账号具有 Cloud SQL Client IAM 角色

    如需详细了解如何将 IAM 角色添加到服务账号,请参阅向服务账号授予角色

当您使用 App Engine 时,系统会自动创建默认的 App Engine 服务账号。服务账号身份的格式为 PROJECT_ID@

如果授权服务账号与 Cloud SQL 实例分处不同的项目,则需要为这两个项目添加 Cloud SQL Admin API 和 IAM 权限。

专用 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,否则连接器必须与使用它的资源位于同一项目和区域中,但该连接器可向其他区域中的资源发送流量。
    • 无服务器 VPC 访问通道支持与使用 Cloud VPNVPC 网络对等互连连接的 VPC 网络进行通信。
    • 无服务器 VPC 访问通道不支持旧版网络
  3. 将 App Engine 标准环境配置为使用该连接器。
  4. 使用实例的专用 IP 地址和端口 1433 进行连接。

连接到 Cloud SQL

配置 App Engine 标准环境后,您可以连接到 Cloud SQL 实例。

公共 IP(默认)

对于公共 IP 路径,App Engine 标准环境提供了加密功能,并使用 Cloud SQL 连接器进行连接。

使用 Cloud SQL 连接器连接

Cloud SQL 连接器是在连接到 Cloud SQL 实例时提供加密和基于 IAM 的授权的语言专用库。

Python

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

import os

from google.cloud.sql.connector import Connector, IPTypes
import pytds

import sqlalchemy


def connect_with_connector() -> sqlalchemy.engine.base.Engine:
    """
    Initializes a connection pool for a Cloud SQL instance of SQL Server.

    Uses the Cloud SQL Python Connector package.
    """
    # 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.

    instance_connection_name = os.environ[
        "INSTANCE_CONNECTION_NAME"
    ]  # e.g. 'project:region:instance'
    db_user = os.environ.get("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'

    ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

    # initialize Cloud SQL Python Connector object
    connector = Connector(ip_type=ip_type, refresh_strategy="LAZY")

    connect_args = {}
    # If your SQL Server instance requires SSL, you need to download the CA
    # certificate for your instance and include cafile={path to downloaded
    # certificate} and validate_host=False. This is a workaround for a known issue.
    if os.environ.get("DB_ROOT_CERT"):  # e.g. '/path/to/my/server-ca.pem'
        connect_args = {
            "cafile": os.environ["DB_ROOT_CERT"],
            "validate_host": False,
        }

    def getconn() -> pytds.Connection:
        conn = connector.connect(
            instance_connection_name,
            "pytds",
            user=db_user,
            password=db_pass,
            db=db_name,
            **connect_args
        )
        return conn

    pool = sqlalchemy.create_engine(
        "mssql+pytds://",
        creator=getconn,
        # ...
    )
    return pool

Java

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

注意:

  • CLOUD_SQL_CONNECTION_NAME 应表示为 <MY-PROJECT>:<INSTANCE-REGION>:<INSTANCE-NAME>
  • 如需查看 pom.xml 文件的 JDBC 套接字工厂版本的要求,请点击此处


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

public class ConnectorConnectionPoolFactory extends ConnectionPoolFactory {

  // 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.
  private static final String INSTANCE_CONNECTION_NAME =
      System.getenv("INSTANCE_CONNECTION_NAME");
  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");

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

    // The following is equivalent to setting the config options below:
    // jdbc:sqlserver://;user=<DB_USER>;password=<DB_PASS>;databaseName=<DB_NAME>;
    // socketFactoryClass=com.google.cloud.sql.sqlserver.SocketFactory;
    // socketFactoryConstructorArg=<INSTANCE_CONNECTION_NAME>

    // 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
        .setDataSourceClassName("com.microsoft.sqlserver.jdbc.SQLServerDataSource");
    config.setUsername(DB_USER); // e.g. "root", "sqlserver"
    config.setPassword(DB_PASS); // e.g. "my-password"
    config.addDataSourceProperty("databaseName", DB_NAME);

    config.addDataSourceProperty("socketFactoryClass",
        "com.google.cloud.sql.sqlserver.SocketFactory");
    config.addDataSourceProperty("socketFactoryConstructorArg", INSTANCE_CONNECTION_NAME);

    // The Java Connector provides SSL encryption, so it should be disabled
    // at the driver level.
    config.addDataSourceProperty("encrypt", "false");

    // cloudSqlRefreshStrategy set to "lazy" is used to perform a
    // refresh when needed, rather than on a scheduled interval.
    // This is recommended for serverless environments to
    // avoid background refreshes from throttling CPU.
    config.addDataSourceProperty("cloudSqlRefreshStrategy", "lazy");

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

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

Go

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

package cloudsql

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net"
	"os"

	"cloud.google.com/go/cloudsqlconn"
	mssql "github.com/denisenkom/go-mssqldb"
)

type csqlDialer struct {
	dialer     *cloudsqlconn.Dialer
	connName   string
	usePrivate bool
}

// DialContext adheres to the mssql.Dialer interface.
func (c *csqlDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
	var opts []cloudsqlconn.DialOption
	if c.usePrivate {
		opts = append(opts, cloudsqlconn.WithPrivateIP())
	}
	return c.dialer.Dial(ctx, c.connName, opts...)
}

func connectWithConnector() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Fatal Error in connect_connector.go: %s environment variable not set.\n", 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'
		instanceConnectionName = mustGetenv("INSTANCE_CONNECTION_NAME") // e.g. 'project:region:instance'
		usePrivate             = os.Getenv("PRIVATE_IP")
	)

	dbURI := fmt.Sprintf("user id=%s;password=%s;database=%s;", dbUser, dbPwd, dbName)
	c, err := mssql.NewConnector(dbURI)
	if err != nil {
		return nil, fmt.Errorf("mssql.NewConnector: %w", err)
	}
	// WithLazyRefresh() Option is used to perform refresh
	// when needed, rather than on a scheduled interval.
	// This is recommended for serverless environments to
	// avoid background refreshes from throttling CPU.
	dialer, err := cloudsqlconn