从 Cloud Build 连接

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

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

Cloud Build 服务可让您在 Google Cloud 基础架构上执行构建。

设置 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 Build

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

公共 IP(默认)

确保您的 Cloud Build 服务账号具有连接到 Cloud SQL 实例所需的 IAM 角色和权限。Cloud Build 服务账号在 Google Cloud 控制台 IAM 页面上作为主账号 [YOUR-PROJECT-NUMBER]@cloudbuild.gserviceaccount.com 列出。

如需在 Google Cloud 控制台中查看此服务账号,请选中包括 Google 提供的角色授权复选框。

您的 Cloud Build 服务账号需要以下某个 IAM 角色

  • Cloud SQL Client(首选)
  • Cloud SQL Admin
或者,您也可以手动分配下列 IAM 权限:
  • cloudsql.instances.connect
  • cloudsql.instances.get

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

专用 IP

如需通过专用 IP 连接到 Cloud SQL 实例,Cloud Build 必须与 Cloud SQL 实例位于同一 VPC 网络中。如需进行配置,请执行以下操作:

  1. Cloud SQL 实例的 VPC 网络与服务提供方网络之间设置专用连接。
  2. 创建 Cloud Build 专用池

配置完成后,当构建在池中运行时,您的应用将能够使用实例的专用 IP 地址和端口 3306 直接连接。

连接到 Cloud SQL

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

公共 IP(默认)

对于公共 IP 路径,Cloud Build 同时支持 Unix 和 TCP 套接字。

您可以在 Cloud Build 步骤中使用 Cloud SQL Auth 代理来允许与数据库的连接。此配置会执行以下操作:

  1. 构建容器并将其推送到 Container Registry。
  2. 构建第二个容器,并在 Cloud SQL Auth 代理二进制文件中进行复制。
  3. 使用第二个容器启动 Cloud SQL Auth 代理并运行所有迁移命令。
steps:
  - id: "docker-build"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "${_IMAGE_NAME}", "sql-proxy/."]

  - id: "docker-push"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "${_IMAGE_NAME}"]

  - id: "docker-layer"
    name: "gcr.io/cloud-builders/docker"
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        echo "FROM $_IMAGE_NAME
        COPY --from=gcr.io/cloud-sql-connectors/cloud-sql-proxy /cloud-sql-proxy /cloudsql/cloud-sql-proxy" > Dockerfile-proxy;

        docker build -f Dockerfile-proxy -t ${_IMAGE_NAME}-proxy .

  # For TCP connections
  - id: "migrate-tcp"
    name: "${_IMAGE_NAME}-proxy"
    dir: sql-proxy
    env:
      - "DATABASE_NAME=${_DATABASE_NAME}"
      - "DATABASE_USER=${_DATABASE_USER}"
      - "DATABASE_HOST=127.0.0.1"
      - "DATABASE_PORT=${_DATABASE_PORT}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        /cloudsql/cloud-sql-proxy --port ${_DATABASE_PORT} ${_INSTANCE_CONNECTION_NAME} & sleep 2;
        python migrate.py # for example

  # For Unix Socket connections
  - id: "migrate-socket"
    name: "${_IMAGE_NAME}-proxy"
    dir: sql-proxy
    env:
      - "DATABASE_NAME=${_DATABASE_NAME}"
      - "DATABASE_USER=${_DATABASE_USER}"
      - "INSTANCE_CONNECTION_NAME=${_INSTANCE_CONNECTION_NAME}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        /cloudsql/cloud-sql-proxy --unix-socket /cloudsql ${_INSTANCE_CONNECTION_NAME} & sleep 2;
        if [ $_DATABASE_TYPE = 'mssql' ]; then echo "MSSQL doesn't support Unix Sockets. Skippng."; exit 0; fi;
        python migrate.py # for example.

options:
  dynamic_substitutions: true

substitutions:
  _DATABASE_USER: myuser
  _DATABASE_NAME: mydatabase
  _INSTANCE_CONNECTION_NAME: ${PROJECT_ID}:us-central1:myinstance
  _DATABASE_PORT: '5432'
  _DATABASE_TYPE: postgres
  _DATABASE_PASSWORD_KEY: database_password
  _IMAGE_NAME: gcr.io/${PROJECT_ID}/sample-sql-proxy

availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS

上面的 Cloud Build 代码示例展示了如何在部署上述示例应用以使用 Cloud SQL Auth 代理和 Cloud Build 更新其 Cloud SQL 数据库后运行假设性迁移脚本。如需运行此 Cloud Build 代码示例,您需要执行以下设置步骤:

  1. 创建一个名为 sql-proxy 的文件夹
  2. sql-proxy 文件夹中创建 Dockerfile 文件,其中包含其文件内容对应的以下单行代码:

    FROM gcr.io/gcp-runtimes/ubuntu_20_0_4

  3. sql-proxy 文件夹中创建 cloudbuild.yaml 文件。
  4. 更新 cloudbuild.yaml 文件:
    1. 复制上面的 Cloud Build 代码示例并将其粘贴到 cloudbuild.yaml 文件中。
    2. 移除未使用的连接方法的代码块,以使用 TCP 连接或 Unix 套接字连接。
    3. substitutions: 块中的示例代码 _DATABASE_TYPE 更新为 mysql
    4. 如果您使用的是 TCP 连接,请将 substitutions: 块中的示例代码 _DATABASE_PORT 更新为 3306,这是 MySQL 使用的端口。
    5. 将以下占位值替换为项目中使用的值:
      • mydatabase
      • myuser
      • myinstance
  5. Secret Manager 中创建一个名为 database_password 的 Secret。
  6. sql-proxy 文件夹中创建 migrate.py 脚本文件。
    • 该脚本可以使用以下示例引用以下环境变量和 cloudbuild.yaml 文件中创建的 Secret:
      • os.getenv('DATABASE_NAME')
      • os.getenv('DATABASE_USER')
      • os.getenv('DATABASE_PASS')
      • os.getenv('INSTANCE_CONNECTION_NAME')
    • 如需引用 Bash 脚本中的相同变量(例如 migrate.sh),请使用以下示例:
      • $DATABASE_NAME
      • $DATABASE_USER
      • $DATABASE_PASS
      • $INSTANCE_CONNECTION_NAME
  7. 运行以下 gcloud builds submit 命令,以使用 Cloud SQL Auth 代理构建容器,启动 Cloud SQL Auth 代理,然后运行 migrate.py 脚本:

    gcloud builds submit --config cloudbuild.yaml

专用 IP

对于专用 IP 路径,您的应用会通过专用池直接连接到您的实例。此方法使用 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 Build 步骤来直接运行代码。

steps:
  - id: "docker-build"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "${_IMAGE_NAME}", "sql-private-pool/."]

  - id: "docker-push"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "${_IMAGE_NAME}"]

  - id: "migration"
    name: "${_IMAGE_NAME}"
    dir: sql-private-pool
    env:
      - "DATABASE_NAME=mydatabase"
      - "DATABASE_USER=myuser"
      - "DATABASE_HOST=${_DATABASE_HOST}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: python   # for example
    args: ["migrate.py"] # for example

options:
  pool:
    name: projects/$PROJECT_ID/locations/us-central1/workerPools/private-pool
  dynamic_substitutions: true

substitutions:
  _DATABASE_PASSWORD_KEY: database_password
  _DATABASE_TYPE: postgres
  _IMAGE_NAME: gcr.io/${PROJECT_ID}/sample-private-pool

availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS

上面的 Cloud Build 代码示例展示了如何在部署上述示例应用以使用 Cloud Build 更新其 Cloud SQL 数据库后运行假设性迁移脚本。如需运行此 Cloud Build 代码示例,您需要执行以下设置步骤:

  1. 创建一个名为 sql-private-pool 的文件夹
  2. sql-private-pool 文件夹中创建 Dockerfile 文件,其中包含其文件内容对应的以下单行代码:

    FROM gcr.io/gcp-runtimes/ubuntu_20_0_4

  3. sql-private-pool 文件夹中创建 cloudbuild.yaml 文件。
  4. 更新 cloudbuild.yaml 文件:
    1. 复制上面的 Cloud Build 代码示例并将其粘贴到 cloudbuild.yaml 文件中。
    2. 将以下占位值替换为项目中使用的值:
      • mydatabase
      • myuser
      • databasehost,采用 host:port 的形式。
  5. Secret Manager 中创建一个名为 database_password 的 Secret。
  6. sql-proxy 文件夹中创建 migrate.py 脚本文件。
    • 该脚本可以使用以下示例引用以下环境变量和 cloudbuild.yaml 文件中创建的 Secret:
      • os.getenv('DATABASE_NAME')
      • os.getenv('DATABASE_USER')
      • os.getenv('DATABASE_PASS')
      • os.getenv('DATABASE_HOST')
    • 如需引用 Bash 脚本中的相同变量(例如 migrate.sh),请使用以下示例:
      • $DATABASE_NAME
      • $DATABASE_USER
      • $DATABASE_PASS
      • $DATABASE_HOST
  7. 运行以下 gcloud builds submit 命令,以使用 Cloud SQL Auth 代理构建容器,启动 Cloud SQL Auth 代理,然后运行 migrate.py 脚本:

    gcloud builds submit --config cloudbuild.yaml

最佳做法和其他信息

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

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

数据库架构迁移

通过将 Cloud Build 配置为连接到 Cloud SQL,您可以使用您部署到任何其他无服务器平台的相同代码在 Cloud Build 中运行数据库架构迁移任务。

使用 Secret Manager

您可以使用 Secret Manager 在构建中包含敏感信息。