從 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 位址。如要進一步瞭解這兩種連線選項,請參閱「 連線總覽」頁面。

設定 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 頁面中,並顯示為 Principal [YOUR-PROJECT-NUMBER]@cloudbuild.gserviceaccount.com

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

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

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

私人 IP

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

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

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

連線至 Cloud SQL

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

公開 IP (預設)

針對公開 IP 路徑,Cloud Build 支援 Unix 和 TCP 套接字。

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

  1. 建構容器並推送至 Artifact Registry。
  2. 建構第二個容器,並複製 Cloud SQL 驗證 Proxy 二進位檔。
    • 由 Cloud Build 建構的容器不需要推送至任何登錄,且會在建構完成時���棄
  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.16.0
        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 的密鑰。
  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 驗證 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 的密鑰。
  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 驗證 Proxy 建構容器、啟動 Cloud SQL 驗證 Proxy,然後執行 migrate.py 指令碼:

    gcloud builds submit --config cloudbuild.yaml

最佳做法和其他資訊

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

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

資料庫結構定義遷移

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

使用 Secret Manager

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