從 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. 建立 PostgreSQL 適用的 Cloud SQL 執行個體。建議您選擇與 Cloud Run 服務位於相同區域的 Cloud SQL 執行個體位置,以縮短延遲時間、避免部分網路費用,並降低跨區域故障風險。

    根據預設,Cloud SQL 會為新執行個體指派公開 IP 位址。 您也可以選擇指派私人 IP 位址。如要進一步瞭解這兩者的連線選項,請參閱「 連線總覽」頁面。

  3. 建立執行個體時,您可以選擇執行個體的 伺服器憑證 (CA) 階層,然後將該階層設定為執行個體的 serverCaMode。如要從網頁應用程式連線至執行個體,您必須選取「每個執行個體」CA 選項 (GOOGLE_MANAGED_INTERNAL_CA) 做為執行個體的伺服器 CA 模式。

設定 Artifact Registry 存放區

  1. 如果尚未啟用,請在您要連線的 Google Cloud 專案中啟用 Artifact Registry API:

    Enable the API

  2. 建立 Docker Artifact Registry。 為改善延遲時間、降低跨區域故障的風險,並避免產生額外的網路費用,建議您選擇與 Cloud Run 服務位於相同區域的 Artifact Registry 位置

設定 Cloud Build

設定 Cloud Build 的步驟取決於指派給 Cloud SQL 執行個體的 IP 位址類型。

公開 IP (預設)

確認 Cloud Build 服務帳戶具備連線至 Cloud SQL 執行個體所需的 IAM 角色和權限。Cloud Build 服務帳戶會列在 Google Cloud 控制台IAM「IAM」[YOUR-PROJECT-NUMBER]@cloudbuild.gserviceaccount.com頁面中。

如要在 Google Cloud 控制台中查看這個服務帳戶,請選取「包含 Google 提供的角色授權」核取方塊。

Cloud Build 服務帳戶需要 Cloud SQL Client IAM 角色

如果 Cloud Build 服務帳戶與 Cloud SQL 執行個體屬於不同專案,則必須為這兩個專案新增 Cloud SQL Admin API 和角色。

私人 IP

如要透過私人 IP 連線至 Cloud SQL 執行個體,Cloud Build 必須與 Cloud SQL 執行個體位於相同的虛擬私有雲網路。如要設定這項功能,請按照下列步驟操作:

  1. Cloud SQL 執行個體的虛擬私有雲網路與服務供應商網路之間設定私人連線
  2. 建立 Cloud Build 私人集區

設定完成後,當建構作業在集區中執行時,應用程式就能使用執行個體的私人 IP 位址和通訊埠 5432 直接連線。

連線至 Cloud SQL

設定 Cloud Build 後,即可連線至 Cloud SQL 執行個體。

公開 IP (預設)

如果是公開 IP 路徑,Cloud Build 支援 Unix 和 TCP Socket。

您可以在 Cloud Build 步驟中使用 Cloud SQL 驗證 Proxy,允許連線至資料庫。 這項設定:

  1. 建構容器並推送至 Artifact Registry。
  2. 建構第二個容器,並複製 Cloud SQL 驗證 Proxy 二進位檔。
  3. 使用第二個容器啟動 Cloud SQL 驗證 Proxy,並執行任何遷移指令。
steps:
  - id: install-proxy
    name: gcr.io/cloud-builders/wget
    entrypoint: sh
    args:
      - -c
      - |
        wget -O /workspace/cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/2.17.1
        chmod +x /workspace/cloud-sql-proxy

 - id: migrate
    waitFor: ['install-proxy']
    name: YOUR_CONTAINER_IMAGE_NAME
    entrypoint: sh
    env:
      - "DATABASE_NAME=${_DATABASE_NAME}"
      - "DATABASE_USER=${_DATABASE_USER}"
      - "DATABASE_PORT=${_DATABASE_PORT}"
      - "INSTANCE_CONNECTION_NAME=${_INSTANCE_CONNECTION_NAME}"
    secretEnv:
      - DATABASE_PASS
    args:
      - "-c"
      - |
        /workspace/cloud-sql-proxy ${_INSTANCE_CONNECTION_NAME} --port ${_DATABASE_PORT} & sleep 2;
        # Cloud SQL Proxy is now up and running, add your own logic below to connect
        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_PASSWORD_KEY: database_password
    _AR_REPO_REGION: us-central1
    _AR_REPO_NAME: my-docker-repo
    _IMAGE_NAME: ${_AR_REPO_REGION}-docker.pkg.dev/${PROJECT_ID}/${_AR_REPO_NAME}/sample-sql-proxy

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

Cloud Build 程式碼範例說明如何在部署先前的範例應用程式後,執行假設的 migrate.py 指令碼,使用 Cloud SQL 驗證 Proxy 和 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. 將下列預留位置值替換為專案中使用的值:
      • mydatabase
      • myuser
      • myinstance
  5. Secret Manager 中建立名為 database_password 的密鑰。
    • 如要讓 Cloud Build 服務帳戶存取這個密鑰,您必須在 IAM 中授予該帳戶「Secret Manager 密鑰存取者」角色。詳情請參閱「使用 Secret Manager 中的密鑰」。
  6. sql-proxy 資料夾中建立 migrate.py 指令碼檔案。
    • 指令碼可以參照下列環境變數,以及使用下列範例在 cloudbuild.yaml 檔案中建立的密鑰:
      • 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 驗證 Proxy 建構容器、啟動 Cloud SQL 驗證 Proxy,然後執行 migrate.py 指令碼:
        gcloud builds submit --config cloudbuild.yaml
        

私人 IP

如果是私人 IP 路徑,應用程式會透過私人集區直接連線至執行個體。這個方法會使用 TCP 直接連線至 Cloud SQL 執行個體,而不使用 Cloud SQL 驗證 Proxy。

透過 TCP 連線

使用 Cloud SQL 執行個體的私人 IP 位址做為主機和通訊埠 5432 進行連線。

Python

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

import os
import ssl

import sqlalchemy


def connect_tcp_socket() -> sqlalchemy.engine.base.Engine:
    """Initializes a TCP connection pool for a Cloud SQL instance of Postgres."""
    # 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. 5432

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

Java

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

注意:


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

public class TcpConnectionPoolFactory 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 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:postgresql://<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:postgresql://%s:%s/%s", INSTANCE_HOST, DB_PORT, DB_NAME));
    config.setUsername(DB_USER); // e.g. "root", "postgres"
    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

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

const Knex = require('knex');
const fs = require('fs');

// createTcpPool initializes a TCP connection pool for a Cloud SQL
// instance of Postgres.
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 = {
    client: 'pg',
    connection: {
      host: process.env.INSTANCE_HOST, // e.g. '127.0.0.1'
      port: process.env.DB_PORT, // e.g. '5432'
      user: process.env.DB_USER, // e.g. 'my-user'
      password: process.env.DB_PASS, // e.g. 'my-user-password'
      database: process.env.DB_NAME, // e.g. 'my-database'
    },
    // ... Specify additional properties here.
    ...config,
  };
  // Establish a connection to the database.
  return Knex(dbConfig);
};

Go

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

package cloudsql

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

	// Note: If connecting using the App Engine Flex Go runtime, use
	// "github.com/jackc/pgx/stdlib" instead, since v5 requires
	// Go modules which are not supported by App Engine Flex.
	_ "github.com/jackc/pgx/v5/stdlib"
)

// connectTCPSocket initializes a TCP connection pool for a Cloud SQL
// instance of Postgres.
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'
		dbTCPHost = mustGetenv("INSTANCE_HOST") // e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
		dbPort    = mustGetenv("DB_PORT")       // e.g. '5432'
		dbName    = mustGetenv("DB_NAME")       // e.g. 'my-database'
	)

	dbURI := fmt.Sprintf("host=%s user=%s password=%s port=%s database=%s",
		dbTCPHost, dbUser, dbPwd, dbPort, dbName)


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

	// ...

	return dbPool, nil
}

C#

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

using Npgsql;
using System;

namespace CloudSql
{
    public class PostgreSqlTcp
    {
        public static NpgsqlConnectionStringBuilder NewPostgreSqlTCPConnectionString()
        {
            // Equivalent connection string:
            // "Uid=<DB_USER>;Pwd=<DB_PASS>;Host=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new NpgsqlConnectionStringBuilder()
            {
                // 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.
                Host = Environment.GetEnvironmentVariable("INSTANCE_HOST"),     // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                Username = 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 = SslMode.Disable,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;
        }
    }
}

Ruby

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

tcp: &tcp
  adapter: postgresql
  # 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") { 5432 }%>

PHP

如要查看網頁應用程式中的程式碼片段,請參閱 GitHub 上的 README

namespace Google\Cloud\Samples\CloudSQL\Postgres;

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('pgsql: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/postgres/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
  dynamicSubstitutions: true

substitutions:
  _DATABASE_PASSWORD_KEY: database_password
  _DATABASE_TYPE: postgres
  _AR_REPO_REGION: us-central1
  _AR_REPO_NAME: my-docker-repo
  _IMAGE_NAME: ${_AR_REPO_REGION}-docker.pkg.dev/${PROJECT_ID}/${_AR_REPO_NAME}/sample-private-pool

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

上述 Cloud Build 程式碼範例說明在部署上述範例應用程式後,如何執行假設的 migrate 指令碼,使用 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 的密鑰。
    • 如要讓 Cloud Build 服務帳戶存取這個密鑰,您必須在 IAM 中授予該帳戶「Secret Manager 密鑰存取者」角色。詳情請參閱「使用 Secret Manager 中的密鑰」。
  6. sql-proxy 資料夾中建立 migrate.py 指令碼檔案。
    • 指令碼可以參照下列環境變數,以及使用下列範例在 cloudbuild.yaml 檔案中建立的密鑰:
      • 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 驗證 Proxy 建構容器、啟動 Cloud SQL 驗證 Proxy,然後執行 migrate.py 指令碼:

    gcloud builds submit --config cloudbuild.yaml

最佳做法和其他資訊

在本機測試應用程式時,可以使用 Cloud SQL Auth Proxy。如需詳細操作說明,請參閱使用 Cloud SQL 驗證 Proxy 的快速入門導覽課程

您也可以透過 Docker 容器使用 Cloud SQL Proxy 進行測試。

資料庫結構定義遷移

將 Cloud Build 設定為連線至 Cloud SQL 後,您就能在 Cloud Build 中執行資料庫結構定義遷移工作,使用的程式碼與部署至任何其他無伺服器平台時相同。

使用 Secret Manager

您可以使用 Secret Manager 在建構作業中加入私密資訊。