从 Cloud Run functions 连接

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

如需查看有关如何运行连接到 Cloud SQL 的 Cloud Run functions 示例 Web 应用的分步说明,请参阅从 Cloud Run functions 连接快速入门

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

Cloud Run 函数是一个轻量级计算解决方案,可供开发者创建单一用途的独立函数,无需管理服务器或运行时环境即可对云端事件作出响应。

设置 Cloud SQL 实例

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

    Enable the API

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

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

配置 Cloud Run functions

配置 Cloud Run 函数的步骤取决于您分配给 Cloud SQL 实例的 IP 地址类型。

公共 IP(默认)

如需配置 Cloud Run 函数以启用与 Cloud SQL 实例的连接,请执行以下操作:

  • 确认上面创建的实例具有公共 IP 地址。您可以在 Google Cloud 控制台中实例的概览页面上确认这一点。如果您需要添加公共 IP 地址,请参阅配置公共 IP
  • 获取实例的 INSTANCE_CONNECTION_NAME。此值可用:
    • 在实例的概览页面上、在 Google Cloud 控制台中,或
    • 通过运行以下命令:gcloud sql instances describe [INSTANCE_NAME]
  • 为您的函数配置服务账号。如果授权服务账号与 Cloud SQL 实例属于不同项目,请启用 Cloud SQL Admin API,然后为这两个项目添加下面列出的 IAM 权限。确认服务账号具有适当的 Cloud SQL 角色和权限以连接到 Cloud SQL
    • 如需连接到 Cloud SQL,服务账号需要以下某个 IAM 角色
      • Cloud SQL Client(首选)
      • Cloud SQL Editor
      • Cloud SQL Admin
      或者,您也可以手动分配下列 IAM 权限:
      • cloudsql.instances.connect
      • cloudsql.instances.get
  • 如果您使用的是 Cloud Run functions 而不是 Cloud Run functions (第 1 代),则必须执行以下操作(另请参阅配置 Cloud Run):
    1. 初始部署函数。
      当您在 Google Cloud 控制台中首次创建 Cloud Run 函数时,底层 Cloud Run 服务尚未创建。在(通过部署 Cloud Run 函数)创建服务之前,您无法配置 Cloud SQL 连接。
    2. 在 Google Cloud 控制台中,在函数详情页面的右上角,在 Cloud Run 提供支持下,点击链接以访问底层 Cloud 运行服务。
    3. 在 Cloud Run 服务详情页面上,选择修改和部署新的修订版本标签页。
    4. 按照标准步骤(与任何配置更改情况一样),为 Cloud SQL 连接设置新配置。
      这将创建一个新的 Cloud Run 修订版本,后续修订版本会自动接收此 Cloud SQL 连接,除非您进行了明确更改。

专用 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. 将 Cloud Run functions 配置为使用该连接器。
  4. 使用实例的专用 IP 地址和端口 3306 进行连接。

连接到 Cloud SQL

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

公共 IP(默认)

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

专用 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
}

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 代理

连接池

与底层数据库的连接可能会被数据库服务器本身或基础设施底层 Cloud Run functions 断开。我们建议您使用支持连接池的客户端库,因为连接池可自动重新连接断开的客户端连接。此外,我们建议您使用全局范围的连接池,以便提高函数在后续调用中重复使用同一连接的可能性,并在逐出(自动缩减)实例时自然关闭连接。如需查看有关如何使用连接池的详细示例,请参阅管理数据库连接

连接限制

Cloud SQL 对并发连接数量设定了上限,这些限制可能因所选的数据库引擎而有所不同(请参阅 Cloud SQL 配额和限制)。建议使用与 Cloud Run 函数的连接,但务必将连接数上限设置为 1。

您应该尽可能保证仅为需要访问数据库的函数初始化连接池。某些连接池会采用抢占的方式创建连接,这可能会耗用过多资源,也会占用您的连接限额。因此,建议使用延迟初始化来将连接池的创建推迟到必要的时候,并且仅在使用连接池的函数中包含连接池。

如需查看有关如何限制连接数的详细示例,请参阅管理数据库连接

API 配额限制

Cloud Run 函数提供了一种使用 Cloud SQL Auth 代理进行连接的机制,该代理使用 Cloud SQL Admin API。API 配额限制适用于 Cloud SQL Auth 代理。所用 Cloud SQL Admin API 配额约为所配置的 Cloud SQL 实例数量与部署的函数总数乘积的两倍。您可以设置最大并发调用次数来修改预期使用的 API 配额。Cloud Run functions 还对每 100 秒允许的 API 调用次数设定了速率限制

后续步骤