Cloud RunからAlloyDBにパブリックIPアドレスで接続する

記事タイトルとURLをコピーする

G-gen の佐々木です。当記事では AlloyDB for PostgreSQL でパブリック IP アドレスを有効化して、Cloud Run からの接続を試してみます。

前提知識

AlloyDB と Cloud Run

AlloyDB for PostgreSQL(以下、AlloyDB)と Cloud Run については、それぞれ以下の記事をご参照ください。

blog.g-gen.co.jp

blog.g-gen.co.jp

blog.g-gen.co.jp

パブリック IP アドレスを使用した AlloyDB への接続

これまで AlloyDB ではプライベート IP アドレスを使用した接続のみサポートされていましたが、2024年1月より、パブリック IP アドレスを使用した接続が利用できるようになりました。

Cloud SQL 同様、DB インスタンスでパブリック IP アドレスを有効化する場合、承認されたネットワーク として DB インスタンスにアクセスできるアクセス元 IP アドレス範囲を制限することができます。

AlloyDB ではプロキシソフトウェアである AlloyDB Auth Proxy を使用することで、TLS によって暗号化されたデータベース接続を構成することができるため、パブリック IP アドレスを使用する場合は Auth Proxy 経由で接続することを推奨します。また、Auth Proxy を使用する場合、承認されたネットワークの設定は必要ありません。

2024年1月現在では、当機能はプレビューです。

Cloud Run から AlloyDB への接続

Cloud Run などのサーバーレスサービスから Cloud SQL や AlloyDB などの VPC リソースに接続する場合、プライベート IP アドレスを使用した接続ではサーバーレス VPC アクセスを構成し、コネクタインスタンスを経由して接続を行う必要がありました。

Cloud Run からプライベート IP アドレスで AlloyDB に接続する(Auth Proxy は省略可能)

サーバーレス VPC アクセスのコネクタインスタンスは常時起動のリソースであり「起動時間ぶんのコストがかかってしまう」「一度スケールアウトするとスケールインできない」といった仕様があります。そのため Cloud SQL の場合は、Cloud SQL インスタンスでパブリック IP アドレスを有効化し、Cloud SQL Auth Proxy を使用して Cloud Run から接続するケースも少なくありません。

AlloyDB クラスタでパブリック IP アドレスを有効化することで、Cloud SQL 同様、Cloud Run から Auth Proxy を経由したパブリック IP アドレスによる接続を実現することができます。Cloud Run で AlloyDB Auth Proxy を使用する場合、Cloud Run のマルチコンテナ(サイドカー)機能を使用します。

Cloud Run から パブリック IP アドレスで AlloyDB に接続する

Cloud Run のマルチコンテナ機能、およびプライベート IP アドレスを使用した AlloyDB への接続に関しては以下の記事で解説しています。

blog.g-gen.co.jp

なお、2024年4月に Direct VPC Egress が GA となったことで、サーバーレス VPC アクセスを使用することなく、VPC リソースにプライベート IP で接続することができるようになりました。

Direct VPC Egress 機能の詳細については以下の記事で解説しています。

blog.g-gen.co.jp

Cloud Run jobs からの接続

Cloud Run jobs ではマルチコンテナ機能がサポートされていない(= AlloyDB Auth Proxy が使用できない)ため、AlloyDB への接続は引き続きサーバーレス VPC アクセスを使用してプライベート IP アドレスで接続します。

なお、Cloud SQL の場合は Cloud Run のネイティブ機能として Cloud SQL Auth Proxy が利用できるため、Cloud Run jobs であっても Auth Proxy が使用できます。

VPC の作成

AlloyDB に紐付ける VPC を作成します。プライベートサービスアクセス を使用して、この VPC を AlloyDB が実際に作成されるサービスプロデューサー VPC にピアリング接続します。

# VPC の作成
$ gcloud compute networks create my-vpc --subnet-mode=custom
  
# サービスプロデューサー VPC が使用する IP アドレス範囲を作成
gcloud compute addresses create my-iprange \
    --global \
    --purpose=VPC_PEERING \
    --addresses=192.168.200.0 \
    --prefix-length=24 \
    --network=my-vpc
  
# プライベートサービスアクセスの構成
$ gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=my-iprange \
    --network=my-vpc

AlloyDB の作成

AlloyDB クラスターの作成

AlloyDB を作成するには、まず大枠となるクラスターを作成します。

以下のコマンドを使用して AlloyDB クラスターを作成します。

# AlloyDB クラスターの作成
$ gcloud alloydb clusters create my-alloycluster \
    --password=mypassword \
    --region=asia-northeast1 \
    --network=my-vpc

AlloyDB インスタンスの作成

クラスターを作成したら、その中に DB インスタンスを作成していきます。パブリック IP アドレスの有効化はインスタンスの作成後に行うため、ここでは IP アドレスに関するオプションは指定せずにインスタンスを作成していきます。

# AlloyDB インスタンスの作成
$ gcloud alloydb instances create my-alloyinstance \
    --cluster=my-alloycluster \
    --cpu-count=2 \
    --instance-type=PRIMARY \
    --region=asia-northeast1 \
    --availability-type=ZONAL \
    --require-connectors

--require-connectors を指定することで、AlloyDB インスタンスの接続元に Auth Proxy の使用を強制することができます。パブリック IP アドレスを有効化する場合は指定したほうがよいでしょう。

# 使用できるオプションの確認
$ gcloud alloydb instances create --help
  
--- 出力抜粋 ---
--[no-]require-connectors
        Enable or disable enforcing connectors only (ex: AuthProxy)connections
        to the database. Use --require-connectors to enable and
        --no-require-connectors to disable.

プレビュー機能が使用できる gcloud beta alloydb create コマンドでは --assign-inbound-public-ip があり、インスタンス作成時からパブリック IP アドレスを有効化できそうに見えますが、2024年1月の時点では値として NO_PUBLIC_IP しか設定できないようです。

# 使用できるオプションの確認
$ gcloud beta alloydb instances create --help
  
--- 出力抜粋 ---
 [--assign-inbound-public-ip=ASSIGN_INBOUND_PUBLIC_IP] [--async]
     --assign-inbound-public-ip=ASSIGN_INBOUND_PUBLIC_IP
        Specify to enable or disable public IP on an instance. On instance
        creation only disabling public IP is allowed. If you want to enable
        public IP, an instance must be created with public IP disabled first,
        then update the instance to enable public IP. ASSIGN_INBOUND_PUBLIC_IP
            This disables public IP on the instance.

サービスアカウントの作成

AlloyDB インスタンスに接続するための権限を付与したサービスアカウントを作成します。このサービスアカウントは後ほど作成する踏み台 VM と Cloud Run で使用します。

# サービスアカウントを作成する
$ gcloud iam service-accounts create my-serviceaccount --project={プロジェクト名}

AlloyDB に接続するために、roles/alloydb.client および roles/serviceusage.serviceUsageConsumer ロールをサービスアカウントに付与します。

# alloydb.client ロールを付与
$ gcloud projects add-iam-policy-binding {プロジェクト名} \
    --role="roles/alloydb.client" \
    --member=serviceAccount:my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com
  
# serviceusage.serviceUsageConsumer ロールを付与
$ gcloud projects add-iam-policy-binding {プロジェクト名} \
    --role="roles/serviceusage.serviceUsageConsumer" \
    --member=serviceAccount:my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com

パブリック IP アドレスを有効化

以下のコマンドを使用して、AlloyDB インスタンスのパブリック IP アドレスを有効化します。

gcloud beta alloydb instances update my-alloyinstance \
    --cluster=my-alloycluster  \
    --region=asia-northeast1  \
    --assign-inbound-public-ip=ASSIGN_IPV4

2024年1月時点ではコンソールからパブリック IP アドレスの設定を確認できないようなので、以下のコマンドを実行して確認します。publicIpAddress の項目が存在することがわかります。

$ gcloud beta alloydb instances describe my-alloyinstance \
    --cluster=my-alloycluster \
    --region=asia-northeast1
  
--- 出力例 ---
availabilityType: ZONAL
clientConnectionConfig:
  requireConnectors: true
  sslConfig:
    sslMode: ENCRYPTED_ONLY
createTime: '2024-01-30T16:04:03.822045686Z'
instanceType: PRIMARY
ipAddress: 192.168.200.11
machineConfig:
  cpuCount: 2
name: projects/myproject/locations/asia-northeast1/clusters/my-alloycluster/instances/my-alloyinstance
networkConfig:
  enablePublicIp: true
publicIpAddress: 35.189.153.132
queryInsightsConfig:
  queryPlansPerMinute: 5
  queryStringLength: 1024
  recordApplicationTags: false
  recordClientAddress: false
reconciling: false
state: READY
uid: 59d31bc0-8d3e-4fff-97ea-029d0b71b12c
updateTime: '2024-01-30T16:33:37.016243913Z'
writableNode:
  zoneId: asia-northeast1-b

データベースの作成

踏み台 Compute Engine VM の作成

踏み台 VM から psql クライアントを使用して AlloyDB にデータベースを作成していきます。

AlloyDB にはパブリック IP アドレスを使用して接続するため、VM を作成する VPC は任意のもので構いませんが、ここでは AlloyDB に紐づけた VPC を使用していきます。

踏み台 VM には AlloyDB クライアントのロールを付与したサービスアカウントを紐付けます。

# VM 用のサブネットの作成
$ gcloud compute networks subnets create my-subnet \
    --network=my-vpc \
    --range=192.168.100.0/28 \
    --region=asia-northeast1 \
  
# 踏み台 VM の作成
$ gcloud compute instances create my-bastion \
    --machine-type=e2-micro \
    --subnet=my-subnet \
    --zone=asia-northeast1-b \
    --image-family=debian-12 \
    --image-project=debian-cloud \
    --service-account=my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com

ファイアウォールルールを設定し、踏み台 VM への SSH 接続を許可します。

# SSH 接続を許可するファイアウォールルールを作成
$ gcloud compute firewall-rules create my-firewall-rule \
    --network=my-vpc \
    --allow=tcp:22 \
    --direction=INGRESS \
    --target-service-accounts=my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com

踏み台 VM からデータベース、テーブルを作成する

踏み台 VM に SSH 接続して作業を進めていきます。

事前準備

AlloyDB に接続するためのクライアントをインストールし、また AlloyDB Auth Proxy をダウンロードします。

# psql クライアントのインストール
$ sudo apt-get update && sudo apt-get install -y postgresql-client

AlloyDB への接続に Auth Proxy の使用を強制する設定にしたため、AlloyDB Auth Proxy クライアントをダウンロードします。

# Auth Proxy クライアントのダウンロード
$ wget https://storage.googleapis.com/alloydb-auth-proxy/v1.7.0/alloydb-auth-proxy.linux.amd64 -O alloydb-auth-proxy
  
# クライアントを実行可能にする
$ chmod +x alloydb-auth-proxy

AlloyDB に接続する

AlloyDB Auth Proxy を起動して psql クライアントが AlloyDB に接続できるようにします。

Auth Proxy クライアントをそのまま実行した場合、Auth Proxy はプライベート IP アドレスを使用して AlloyDB に接続しようとします。

今回作成した踏み台 VM がある VPC からはプライベートサービスアクセス経由で AlloyDB に接続できるため、プライベート IP アドレスを使用してもよいのですが、ここではあえてパブリック IP アドレスを使用して接続してみます。

以下のコマンドを使用して、バックグラウンドで Auth Proxy を実行します。

# バックグラウンドで AlloyDB Auth Proxy を実行する(実行後 ctrl + C)
$ ./alloydb-auth-proxy projects/{プロジェクト名}/locations/asia-northeast1/clusters/my-alloycluster/instances/my-alloyinstance \
    --public-ip=true &

psql クライアントで Auth Proxy を経由して AlloyDB に接続します。

# psql クライアントを起動する
$ psql -h 127.0.0.1 -U postgres -W
Password:  # パスワードを入力(この記事では mypassword)

AlloyDB に接続したら、データベースとテーブルを作成します。

# データベースを作成
> CREATE DATABASE mydb;
  
# データベースの切り替え
> \c mydb
  
# users テーブルを作成
> CREATE TABLE users (id varchar(128), name varchar(128));
  
# 適当なデータを挿入
> INSERT INTO users VALUES ('3', 'sasashun');
  
# ログアウト
> \q

ここまで完了したら、踏み台 VM からログアウトします。
以降は VM を使用しないため、消し忘れ防止のためここで削除してしまっても構いません。

Cloud Run の作成

使用するコード(Go)

PostgreSQL ドライバを使用して AlloyDB の mydb データベースに接続し、users テーブルのデータを取得する Web アプリケーションを実装します。
取得したデータはブラウザ上にそのまま表示します。

// main.go
package main
  
import (
    "database/sql"
    "fmt"
    "log"
    "net/http"
    "os"
  
    _ "github.com/jackc/pgx/v4/stdlib" // PostgreSQLドライバ
)
  
type User struct {
    ID   string
    NAME string
}
  
// AlloyDB Auth Proxy に接続する関数
func connectTCPSocket() (*sql.DB, error) {
    mustGetenv := func(k string) string {
        v := os.Getenv(k)
        if v == "" {
            log.Fatalf("Warning: %s environment variable not set.", k)
        }
        return v
    }
  
    // Cloud Run の環境変数に設定するデータベース接続情報
    var (
        dbUser    = mustGetenv("DB_USER")       // データベースユーザ
        dbPwd     = mustGetenv("DB_PASS")       // データベースユーザのパスワード
        dbTCPHost = mustGetenv("INSTANCE_HOST") // 127.0.0.1 (Auth Proxy コンテナ)
        dbPort    = mustGetenv("DB_PORT")       // Port 5432
        dbName    = mustGetenv("DB_NAME")       // データベース名
    )
  
    // データベース接続情報
    dbURI := fmt.Sprintf("host=%s user=%s password=%s port=%s database=%s",
        dbTCPHost, dbUser, dbPwd, dbPort, dbName)
  
    // Auth Proxy 経由で AlloyDB に接続
    dbPool, err := sql.Open("pgx", dbURI)
    if err != nil {
        return nil, fmt.Errorf("sql.Open: %v", err)
    }
  
    return dbPool, nil
}
  
// SELECT * を実行する関数
func selectAll() ([]User, error) {
    db, err := connectTCPSocket()
    if err != nil {
        return nil, fmt.Errorf("connectTCPSocket: %v", err)
    }
    defer db.Close()
  
    rows, err := db.Query("SELECT * FROM users")
    if err != nil {
        return nil, fmt.Errorf("db.Query: %v", err)
    }
  
    var users []User
    for rows.Next() {
        var u User
        rows.Scan(&u.ID, &u.NAME)
        users = append(users, u)
    }
  
    return users, nil
}
  
// SELECT オペレーションを実行するハンドラ
func selectHandler(w http.ResponseWriter, r *http.Request) {
    users, err := selectAll()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Fprintf(w, "Select Users: %v\n", users)
}
  
// Cloud Runで Webサーバを実行する
func main() {
    log.Print("starting server...")
  
    http.HandleFunc("/", selectHandler)
  
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("defaulting to port %s", port)
    }
  
    log.Printf("listening on port %s", port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
        log.Fatal(err)
    }
}
  

コンテナイメージのビルド

当記事では Dockerfile を使用せず、Buildpack を使用してコンテナイメージをビルドします。Buildpack を使用することで、ソースコードを自動でパッケージ化し、デプロイ可能なコンテナイメージを生成することができます。

コンテナイメージは Artifact Registry のリポジトリにプッシュします(リポジトリがない場合は作成してください)。

# Buildpack を使用してコンテナイメージをビルド
$ gcloud builds submit --pack image=asia-northeast1-docker.pkg.dev/{プロジェクト名}/{リポジトリ名}/run-alloydb

YAML ファイルの作成

当記事では YAML ファイルを使用して Cloud Run サービスをデプロイしていきます。

サイドカーコンテナのコンテナイメージに AlloyDB Auth Proxy のコンテナイメージを指定したファイル service.yaml を作成します。

踏み台 VM で Auth Proxy を使用したときと同様、サイドカーコンテナのエントリポイントの引数(args)で --public-ip=true を指定することで、パブリック IP アドレスを使用した AlloyDB への接続が可能です。

また、プライベート IP アドレスで AlloyDB に接続する場合(参考)と比べると、サーバーレス VPC アクセスに関する設定が無くなっています。

# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: service-alloyrun
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/execution-environment: gen1
        run.googleapis.com/container-dependencies: '{"alloydb-app":["alloydb-proxy"]}'
        autoscaling.knative.dev/maxScale: '10'
    spec:
      # AlloyDB に接続できるサービスアカウント
      serviceAccountName: my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com
      containers:
      # Ingress Container
      - name: alloydb-app
        # アプリケーションのコンテナイメージを指定する
        image: asia-northeast1-docker.pkg.dev/{プロジェクト名}/{リポジトリ名}/run-alloydb:latest
        ports:
          - containerPort: 8080
        # 環境変数にデータベース接続情報を設定する
        env:
        - name: DB_USER
          value: postgres    # AlloyDB のデフォルトのユーザ
        - name: DB_PASS
          value: mypassword  # AlloyDB 作成時に設定したパスワード
        # サイドカーコンテナの Auth Proxy に接続するため 127.0.0.1(localhost)を指定する
        - name: INSTANCE_HOST
          value: 127.0.0.1
        - name: DB_PORT
          value: '5432'
        - name: DB_NAME
          value: mydb
      # Sidecar Container
      - name: alloydb-proxy
        # AlloyDB Auth Proxy のコンテナイメージを指定する
        image: gcr.io/alloydb-connectors/alloydb-auth-proxy:latest
        args:
        # AlloyDB インスタンスの接続文字列を設定する
        - "projects/{プロジェクト名}/locations/asia-northeast1/clusters/my-alloycluster/instances/my-alloyinstance"
        - --address=0.0.0.0
        - --port=5432
        - --public-ip=true  # パブリックIPを使用して接続する
        # Startup Probe の設定
        startupProbe:
          tcpSocket:
            port: 5432
          initialDelaySeconds: 5
          timeoutSeconds: 1
          failureThreshold: 10
          periodSeconds: 3

Cloud Run サービスの作成

YAML ファイルからサービスを作成する場合、新規作成であっても gcloud run services replace コマンドを使用します。

出力の URL 欄に Cloud Run のエンドポイントが表示されるので、これをメモしておきます(コンソールからも確認可能)。

# 新しいサービスのデプロイ
$ gcloud run services replace service.yaml --region=asia-northeast1
  
--- 出力例 ---
Applying new configuration to Cloud Run service [service-alloyrun] in project [myproject] region [asia-northeast1]
  ✓ Deploying new service... Done.                       
  ✓ Creating Revision... Creating Service.                                        
  ✓ Routing traffic...
Done.
New configuration has been applied to service [service-alloyrun].
URL: https://service-alloyrun-xxxxxxxxxx-an.a.run.app

YAML ファイルからのデプロイでは metadata.annotations.run.googleapis.com/ingressall を設定しても「未認証の呼び出しを許可」の設定ができないようなので、別途 allUsers に対して Cloud Run 起動元 のロールを付与します。

# 未認証で Cloud Run サービスを呼び出せるようにする
$ gcloud run services add-iam-policy-binding service-alloyrun \
    --role="roles/run.invoker" \
    --member="allUsers" \
    --region=asia-northeast1

動作確認

ブラウザから Cloud Run のエンドポイントにアクセスします。

サーバーレス VPC アクセスが設定されていなくても、Cloud Run 上のアプリケーションからパブリック IP アドレスで AlloyDB に接続され、SELECT * クエリの結果が画面に表示されます。

AlloyDB から取得したデータがブラウザに表示される

佐々木 駿太 (記事一覧)

G-gen最北端、北海道在住のクラウドソリューション部エンジニア

2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。

趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。