はじめに
こんにちは、junya です。Cloud Run上で動作するNext.jsアプリケーションから、Cloud SQL (PostgreSQL) データベースへ接続しようとしたところ、思いのほか苦戦したので、記録として残しておきます。
技術的選択肢と接続ライブラリ
選択肢が多々あり、その選択肢の意味を理解し、適切なものを選び、設定していくのが大変でした。Cloud RunとCloud SQLの連携には、主に以下の要素の組み合わせで構成が決まります。ここでは、Cloud Runからの接続方法を主軸に、それぞれの技術的選択肢とDrizzle ORM (Node.js) で利用するライブラリを整理します。
-
前提となる選択肢
-
ネットワーク構成:
-
Public IP: Cloud SQLインスタンスにパブリックIPアドレスを割り当てます。
-
Private IP: VPCネットワーク内にのみIPアドレスを割り当てます。VPCコネクタが別途必要となります。
-
-
PostgreSQL接続方式:
-
TCP:
localhost:5432
のようなホスト名とポート番号で接続します。 -
Unix Socket:
/cloudsql/[INSTANCE_CONNECTION_NAME]
のようなファイルシステムのパスで接続します。
-
-
認証方式:
-
パスワード認証: データベースにユーザー名とパスワードを設定します。
-
IAMデータベース認証: Google CloudのIAMプリンシパルを利用します。接続方法には主に以下の2種類があります。
-
Cloud SQL Auth Proxy: Proxy(またはConnectorライブラリ)が認証トークンの取得と利用を自動化します。
-
gcloud sql generate-login-token:
gcloud
コマンドで一時的な認証トークン(パスワードとして使用)を手動で生成します。
-
-
-
-
Cloud Runからの接続方法の選択肢
-
Cloud SQL Auth Proxy経由
-
概要: Cloud Runの機能として、アプリケーションコンテナと同一環境でCloud SQL Auth Proxyコンテナを起動します。アプリケーションは、TCP (
localhost
) またはUnixソケット経由でこのProxyに接続します。 -
利用ライブラリ (Drizzle):
-
drizzle-orm
-
pg
(node-postgres) またはpostgres
(postgres.js) @google-cloud/cloud-sql-connector
-
- 備考:
- Cloud Run には Cloud SQL Auth Proxy を自動で動かすオプションがあります。
- pg (または postgres) 単体では、IAM 認証がうまくいかず、
@google-cloud/cloud-sql-connector
の力を借りました。
-
-
VPC経由の直接接続
-
概要: VPCコネクタを利用してCloud RunとCloud SQL間のネットワーク経路を確立します。データベースのPrivate IPに直接接続します。認証にはユーザー名とパスワードを使用し、これらの認証情報はSecret Managerで管理するのが一般的です。
-
利用ライブラリ (Drizzle):
-
drizzle-orm
-
pg
(node-postgres) またはpostgres
(postgres.js)
-
-
備考: Secret Manager 等で認証情報を管理
-
-
@google-cloud/cloud-sql-connector の活用
-
概要: Cloud SQL Proxy の代わりに、 @google-cloud/cloud-sql-connector で proxy する方法です。詳しくは知らないので、気になる方は調べて下さい。
-
-
これらの選択肢の組み合わせによって設定方法が異なり、特にライブラリと認証方式の相性によって問題が発生することがあります。
動作した構成
最終的に動作した構成を共有します。
- 接続方式: Cloud SQL Auth Proxy
- Cloud Run : Cloud Run 設定で有効化
- 作業マシン : cloud-sql-proxy コマンド
-
認証方式: IAMデータベース認証
- Cloud SQL : IAM認証を有効化
- Cloud Run : 利用するサービスアカウントに roles/cloudsql.instanceUser 権限を付与
-
ネットワーク構成:
-
Cloud SQL : Public IPを有効化
- Cloud Run : 特別な設定なし
-
-
Cloud Runからの接続: 公式コネクタライブラリ (
@google-cloud/cloud-sql-connector
) をアプリケーションに組み込みます。 -
Next.jsからの接続:
- ORM : Drizzle
- データベースドライバ : node-postgres (pg)
- PostgreSQL接続: Unix Socket接続
- Cloud SQL Proxy を利用すると Unix Socket 接続になります(たぶん)
- 認証 : IAMアカウント
- User : サービスアカウント
- パスワード : 不要
- 認証オプション : @google-cloud/cloud-sql-connector の助けが必要でした。
ハマりポイント
- Cloud SQL はマネージドサービスなので、GCPプロジェクト下にあるように見えて、実際はGoogleが管理するネットワーク下にあり、VPC Peering 設定をして VPC アクセスコネクタを作らないと、Private ネットワーク経由で Cloud Run から Cloud SQL に接続することは出来ません。これは、Cloud SQL Proxy を利用する場合にも同様です。(今回は Public IP を利用したので、この設定はしませんでしたが、Private ネットワーク経由での接続のほうが、よりセキュアです)
- Cloud SQL Proxy を利用するには、Cloud SQL側でIPアドレスが必要ですが、IPさえ発行されていればよく、承認済みネットワークの設定は不要です。
- IAMグループ認証によって、グループ単位で認証が可能になりますが、接続時に利用するのは、個人のメールアドレスです。
- Cloud SQL Proxy を利用する場合、Unix Socket 接続になります。アプリケーション側の設定で、TCP 接続を想定した記述になっていると接続できないので、注意してください。また、なぜか node-postgres 単体では接続ができず、 @google-cloud/cloud-sql-connector による補助的なクライアント設定が必要でした。
- IAMグループ認証で追加されたユーザは、自動で cloudsqlsuperuser 権限が付与されるので、特別なことなどしなくとも、grant 文など発行できます。Cloud SQL で superuser 権限を持つユーザは作れません。
3.1. 本番環境 (Cloud Run)
terraform の google_cloud_run_v2_service リソースで Cloud SQL Auth Proxy を有効化できます。
resource "google_cloud_run_v2_service" "main" { ... template {
# Cloud SQL Proxy volume
volumes {
name = "cloudsql"
cloud_sql_instance {
instances = ["<instance name>"]
}
} containers { ...
# Cloud SQL Proxy volume mount
volume_mounts {
name = "cloudsql"
mount_path = "/cloudsql"
}
...
}
3.2. ローカル開発環境
cloud-sql-proxy --auto-iam-authn <project>:<region>:<instance> --impersonate-service-account <service_account_email>
このように service account になりすまして、
psql "host=127.0.0.1 sslmode=disable dbname=<db_name> user=<service_account から gserviceaccount.com を削除したもの>"
のように接続できます。
Drizzle 設定
動作したコードだけ貼っておきます。
import { AuthTypes, Connector } from '@google-cloud/cloud-sql-connector'; import { drizzle } from 'drizzle-orm/node-postgres'; import { Pool, type PoolConfig } from 'pg'; import * as schema from './schema'; let client: Pool; // see: https://github.com/drizzle-team/drizzle-orm/issues/4165 if (process.env.POSTGRES_INSTANCE_CONNECTION_NAME) { // Cloud Run const connector = new Connector(); // @ts-ignore const clientOpts = await connector.getOptions({ instanceConnectionName: process.env.POSTGRES_INSTANCE_CONNECTION_NAME, authType: AuthTypes.IAM, }); const poolOptions: PoolConfig = { ...clientOpts, user: process.env.POSTGRES_USER, database: process.env.POSTGRES_DATABASE, max: 5, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, ssl: false, }; client = new Pool(poolOptions); } else { // localhost const connectionString = process.env.DATABASE_URL || 'postgres://postgres:password@localhost:5432/db?sslmode=disable'; client = new Pool({ connectionString }); } export const db = drizzle(client, { schema }); export type Database = typeof db;
まとめ
Cloud Run + Cloud SQL なんて、枯れたパターンだと思っていたのですが、思いのほか大変でした。どなたかのお役に立てると幸いです。
参考情報
-
IAM 認証 | Cloud SQL for PostgreSQL | Google Cloud
- IAMユーザ認証・IAMグループ認証・IAMサービスアカウント認証があります。
-
Cloud SQL Auth Proxy について | Cloud SQL for PostgreSQL | Google Cloud
-
PostgreSQL のユーザーとロールについて | Cloud SQL for PostgreSQL | Google Cloud
- cloudsqlsuperuserロールについての記載があります。
-
gcloud sql generate-login-token | Google Cloud SDK Documentation
-
- drizzle からの認証で、こちらの issue に助けられました。
-
Drizzle ORM - next gen TypeScript ORM.
- SQL 使いにはちょうど良くて便利。