Cloud RunのサイドカーコンテナでAlloyDB Auth Proxyを使用する

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

G-gen の佐々木です。当記事では、Cloud Run services の マルチコンテナ (サイドカー) 機能 のユースケースの 1 つである、AlloyDB Auth Proxy をサイドカーコンテナとして使用した Alloy DB への接続を試してみます。

前提知識

Cloud Run services

Cloud Run services(以下、Cloud Run)は Google Cloud のサーバーレス コンテナ サービスである Cloud Run の 1 機能であり、フルマネージドの実行環境で HTTP リクエストをトリガーとしたコンテナアプリケーションを実行することができます。
サービスの詳細は以下の記事で解説しています。

blog.g-gen.co.jp

Cloud Run services におけるマルチコンテナ (サイドカー) 構成

Cloud Run のマルチコンテナ (サイドカー) 機能を使用すると、外部からの HTTP リクエストを受信・処理する Ingress コンテナ と、1 つ以上の サイドカーコンテナ でサービスを構成することができます。

サイドカーコンテナは外部からの HTTP リクエストを受信することはできませんが、ローカルホスト ポートを使用して Ingress コンテナや他のサイドカーコンテナと通信することができます。また、それぞれのコンテナから 共有メモリ内ボリューム にアクセスすることも可能です。

Cloud Run services におけるマルチコンテナ構成

公式ドキュメントで紹介されているマルチコンテナ機能のユースケースは以下の通りです。

  • アプリケーションのモニタリング、ロギング、トレース。
  • Nginx、Envoy、または Apache2 をリバースプロキシとして使用する。
  • 認証および認可フィルターを追加する (Open Policy Agent など)。
  • Alloy DB Auth プロキシなど、アウトバウンド接続用のプロキシを実行する。

マルチコンテナ機能は Cloud Run の第 1 世代、第 2 世代いずれの実行環境でも利用することができます。

AlloyDB for PostgreSQL

AlloyDB for PostgreSQL(以下、AlloyDB)は Google Cloud が提供する PostgreSQL 互換のフルマネージド データベース サービスです。
同じくマネージドな PostgreSQL データベースを提供する Cloud SQL と比較すると、パフォーマンスや可用性、スケーラビリティに優れ、大規模ワークロードに対応したサービスとなっています。
AlloyDB の詳細については以下の記事で解説しています。

blog.g-gen.co.jp

AlloyDB Auth Proxy

AlloyDB Auth Proxy(以下、Auth Proxy)は AlloyDB の接続に使用することができるプロキシソフトウェアであり、Google Cloud の IAM でデータベースの認証・認可 (ログイン) をすることができます。
また、Auth Proxy を使用することで、アプリケーションからデータベースへの通信が TLS で暗号化され、より安全なデータベース接続を構成することができます。

構成

当記事では、Cloud Run のマルチコンテナ機能を利用し、AlloyDB Auth Proxy をサイドカーとして使用するコンテナアプリケーションを作成します。

Cloud SQL 同様、AlloyDB のインスタンスは Google Cloud が管理する VPC (サービスプロデューサー VPC) に作成されます。AlloyDB のインスタンスはプライベート IP しかエンドポイントを持たないため、サービスプロデューサー VPC に接続できる VPC を作成し、プライベートサービスアクセスを構成する必要があります。
Cloud Run から AlloyDB へのアクセスは、サーバーレス VPC アクセスコネクタを VPC に作成し、コネクタ経由でサービスプロデューサー VPC に到達できるようにします。

Auth Proxy を 使用して Cloud Run から AlloyDB に接続する

VPC リソースの作成

VPC、サブネットの作成

asia-northeast1 にサブネットをもつ VPC を作成します。
この VPC を、AlloyDB が作成されるサービスプロデューサー VPC にピアリング接続します。

# VPC の作成
$ gcloud compute networks create my-vpc --subnet-mode=custom

サーバーレス VPC アクセスコネクタを作成するサブネットは /28 の CIDR 範囲である必要があります。
Auth Proxy がプライベート接続で AlloyDB のメタデータを取得する際に Google Cloud APIs にアクセスできる必要があるため、--enable-private-ip-google-access限定公開の Google アクセス を有効化します。

# サブネットの作成
$ gcloud compute networks subnets create my-subnet \
    --network=my-vpc \
    --range=192.168.100.0/28 \
    --region=asia-northeast1 \
    --enable-private-ip-google-access

サービスプロデューサー VPC の IP アドレス範囲を作成

サービスプロデューサー VPC で使用する IP アドレス範囲を確保します。

$ gcloud compute addresses create my-iprange \
    --global \
    --purpose=VPC_PEERING \
    --addresses=192.168.200.0 \
    --prefix-length=24 \
    --network=my-vpc

プライベートサービスアクセスの構成

作成した IP アドレス範囲をサービスプロデューサー VPC で使用し、VPC とのピアリングを構成します。

# プライベートサービスアクセスの構成
$ gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=my-iprange \
    --network=my-vpc

サーバーレス VPC アクセスの構成

Cloud Run が VPC を経由してサービスプロデューサー VPC にある AlloyDB にアクセスできるように、サーバーレス VPC アクセスコネクタを作成します。

# サーバーレス VPC アクセスコネクタの作成
$ gcloud compute networks vpc-access connectors create my-connector \
    --region=asia-northeast1 \
    --subnet=my-subnet \
    --min-instances=2 \
    --max-instances=3 \
    --machine-type=f1-micro

AlloyDB クラスタ、インスタンスの作成

AlloyDB は基本的な管理単位である クラスタ と、クラスタ内に作成され実際にデータベースを持つ プライマリインスタンス 、読み取り専用の リードプールインスタンス で構成されます。
当記事ではリードプールインスタンスは使用しないため、クラスタとプライマリインスタンスのみ作成していきます。

# クラスタの作成
$ gcloud alloydb clusters create my-alloycls \
    --password=mypassword \
    --region=asia-northeast1 \
    --network=my-vpc
  
# プライマリインスタンスの作成
$ gcloud alloydb instances create my-alloyins \
    --cluster=my-alloycls \
    --cpu-count=2 \
    --instance-type=PRIMARY \
    --region=asia-northeast1

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

AlloyDB インスタンスに接続するための権限を付与したサービスアカウントを作成します。

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

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

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

データベースの作成

踏み台 VM の作成

psql クライアントを使用して AlloyDB にデータベースを作成します。
AlloyDB クラスタにはプライベート IP を使用して接続するため、プライベートサービスアクセスを設定した VPC に踏み台 VM を作成し、そこから AlloyDB に接続します。
踏み台 VM には AlloyDB クライアントのロールを付与したサービスアカウントを紐付けます。

# 踏み台 VM の作成
$ gcloud compute instances create my-bastion \
    --machine-type=e2-micro \
    --subnet=my-subnet \
    --zone=asia-northeast1-b \
    --image-family=debian-11 \
    --image-project=debian-cloud \
    --metadata=enable-oslogin=true \
    --service-account=my-serviceaccount@{プロジェクト名}.iam.gserviceaccount.com

ファイアウォールルールを設定し、踏み台への 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 で作業する際に AlloyDB インスタンスの IP アドレスが必要になるため、以下のコマンドで確認しておきます。

# AlloyDB インスタンスの IP アドレスを確認
$ gcloud alloydb instances describe my-alloyins \
    --cluster=my-alloycls \
    --region=asia-northeast1 \
    | grep ipAddress
  
# 出力例
ipAddress: 192.168.200.2

psql クライアントのインストール

踏み台 VM に psql クライアントをインストールします。
以下のコマンドは踏み台 VM に SSH 接続してから実行します。

# 踏み台 VM で実行
# psql クライアントのインストール
$ sudo apt-get update
$ sudo apt-get install -y postgresql-client

データベース、テーブルの作成

踏み台 VM から AlloyDB にデータベースとテーブルを作成します。
作成したテーブルには、Cloud Run にデプロイするアプリケーションから接続確認するために、適当なレコードを挿入しておきます。

データベースの作成

# 踏み台 VM で実行
# AlloyDB に接続(パスワードは AlloyDB クラスタ作成時に指定したもの)
$ psql -h {AlloyDBのプライベートIPアドレス} -U postgres
  
# データベースを作成
> CREATE DATABASE mydb;

テーブルの作成

# 踏み台 VM で実行
# データベースの切り替え
> \c mydb
  
# users テーブルを作成
> CREATE TABLE users (id varchar(128), name varchar(128));
  
# 適当なデータを挿入
> INSERT INTO users VALUES ('3', 'sasashun');

以降、踏み台 VM は使用しないため、消し忘れないようにここで削除してしまっても良いです。

Cloud Run サービスの作成

コンテナイメージの作成

Cloud Run にデプロイするアプリケーションのコンテナイメージを作成します。
Auth Proxy のコンテナイメージについては、Google Cloud から提供されているものをそのまま利用します。

使用するコード

PostgreSQL ドライバを使用して AlloyDB の mydb データベースに接続し、users テーブルのデータを取得する処理を実装します。
当記事では 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 ファイル

当記事ではYAML ファイルを使用して Cloud Run サービスをデプロイします。Google Cloud コンソール、Google Cloud CLI からもマルチコンテナ機能を使用したサービスをデプロイすることが可能です。

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

なお、当記事では AlloyDB の作成時に自動で作成される postgres ユーザを使用していますが、実運用では専用のユーザを使用することが推奨されます。
また実運用では、ユーザやパスワードなどの認証情報は Secret Manager に格納し、それを環境変数から読み込む方式にしたほうが良いでしょう。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  # サービスの名前
  name: service-alloyrun
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/execution-environment: gen1
        # Cloud Run からの外向き通信をサーバーレス VPC アクセスコネクタ経由にする
        run.googleapis.com/vpc-access-egress: all-traffic
        # サーバーレス VPC アクセスコネクタを指定する
        run.googleapis.com/vpc-access-connector: projects/{プロジェクト名}/locations/asia-northeast1/connectors/my-connector
        # AlloyDB Auth Proxy コンテナの起動後にアプリケーションコンテナを起動するようにする
        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-alloycls/instances/my-alloyins"
        - --address=0.0.0.0
        - --port=5432
        # Startup Probe の設定
        startupProbe:
          tcpSocket:
            port: 5432
          initialDelaySeconds: 5
          timeoutSeconds: 1
          failureThreshold: 10
          periodSeconds: 3

コンテナの起動順序について

Auth Proxy コンテナが起動していない状態ではアプリケーションコンテナが正常に動作しないため、spec.template.metadata.annotationsrun.googleapis.com/container-dependencies でコンテナの依存関係を指定しています。
それに加えて Auth Proxy コンテナに Startup Probe を設定することで、Auth Proxy コンテナの TCP 5432 ポートと疎通が取れるようになるのを待ってからアプリケーションが実行されるようにしています。

サービスのデプロイ

YAML ファイルからのデプロイは、新規であっても gcloud run services replace コマンドを使用します。

# 新しいサービスのデプロイ
$ gcloud run services replace service.yaml --region=asia-northeast1

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 サービスからの接続

gcloud run services replace コマンドで Cloud Run サービスをデプロイした後、エンドポイントとなる URL が出力されているので、ブラウザからアクセスします。

# 出力例
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

アプリケーションから AlloyDB のデータベースに接続され、SELECT * クエリの結果が画面に表示されます。

ブラウザから Cloud Run サービスにアクセスする

佐々木 駿太 (記事一覧)

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

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

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