GKEからCloud SQLに接続
概要
GKEのクラスタからCloud SQLのMySQLに接続する設定を何回も書いて何回もハマったので、メモ
方針
Podは一つだけで、その中に以下の二つのコンテナを持つものとします。
- api (port: 80)
- cloud-sql-proxy (port: 3306)
外部にAPIを公開するため、Serviceを用いて、containerの80番ポートを8000番としてexposeします。
基本的には以下の記事に沿って設定します。
今回はCloud SQL Auth プロキシとWorkload Identityを用いました。
build
コンテナの開発ができたら、Cloud BuildのCLIを用いてArtifact Registryに登録します。
今回は <project>/backend/
というレポジトリに api
というイメージをアップロードします。
$ gcloud builds submit --region=asia-northeast1 --tag asia-northeast1-docker.pkg.dev/<project>/backend/api .
Kubernetes サービス アカウント(KSA)と Google サービス アカウント(GSA)
コンテナからCloud SQLを叩くためのGSAを作成し、KSAにバインディングします。
apiVersion: v1
kind: ServiceAccount
metadata:
name: ksa-cloud-sql
# サービスアカウント(KSA)を作成
$ kubectl apply -f service-account-cloud-sql.yaml
# GSAとKSAのバインディング
$ gcloud iam service-accounts add-iam-policy-binding \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:<YOUR-GOOGLE-CLOUD-PROJECT>.svc.id.goog[default/ksa-cloud-sql]" \
backend-cloud-sql@<YOUR-GOOGLE-CLOUD-PROJECT>.iam.gserviceaccount.com
# KSAにアノテーション
$ kubectl annotate serviceaccount \
ksa-cloud-sql \
iam.gke.io/gcp-service-account=backend-cloud-sql@<YOUR-GOOGLE-CLOUD-PROJECT>.iam.gserviceaccount.com
コンテナイメージは最新版を参照することにしています(image: asia-northeast1-docker.pkg.dev/<project>/backend/api:latest
の部分)
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-backend
spec:
replicas: 1
selector:
matchLabels:
app: test-backend
template:
metadata:
labels:
app: test-backend
spec:
serviceAccountName: ksa-cloud-sql
containers:
- name: api
image: asia-northeast1-docker.pkg.dev/<project>/backend/api:latest
ports:
- containerPort: 80
envFrom:
- secretRef:
name: backend-env
resources:
requests:
memory: "1Gi"
cpu: "500m"
ephemeral-storage: "1Gi"
limits:
memory: "1Gi"
cpu: "500m"
ephemeral-storage: "1Gi"
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
args:
- "--structured-logs"
- "--port=3306"
- "<INSTANCE_NAME>"
securityContext:
runAsNonRoot: true
resources:
requests:
memory: "2Gi"
cpu: "1"
Deploymentの定義を適用します。
$ kubectl apply -f deployment.yaml
Secrets
上記ファイルの、apiのコンテナのこの部分で、環境変数を一括で読み込んでいます。
envFrom:
- secretRef:
name: backend-env
中身はこんな感じ。localhost:3306
から接続できる。
DB_USERNAME=****
DB_PASSWORD=****
INSTANCE_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=****
PORT=80
....
更新は kubectl
の --from-env-file
を使うと便利。
$ kubectl create secret generic backend-env --from-env-file=.env
Service
クラスタと外部との通信ようにserviceを作成する。
apiVersion: v1
kind: Service
metadata:
name: signage-backend-service
spec:
type: LoadBalancer
selector:
app: signage-backend
ports:
- port: 8000
targetPort: 80
$ kubectl apply -f service.yaml
Firewall
最後に、8000番ポートに穴を開けます。
$ gcloud compute firewall-rules create backend --allow=tcp:8000
http://<YOUR_LOAD_BALANCER_IP>:8000
でアクセスできるはず。
(おまけ) GKE限定公開クラスタの場合
GKEからCloud SQLにアクセスするときに、Public IPがなくてエラーが出る。
failed to connect to instance: Dial error: failed to dial (connection name = "<YOUR_CONNECTION_NAME>"): dial tcp <IP>:3307: i/o timeout
ので、Cloud NATの設定をすると通るようになる。
(おまけ) Prisma
PrismaでDB接続を指定する際、デフォルトの env(DATABASE_URL)
は .env
ファイルから読み取るが、上記の構成だと環境変数から .env
ファイルを再構成しなければならず面倒なので、 PrismaClient
のコンストラクタの引数に渡してしまうと便利。
const userName = process.env.DB_USERNAME;
const password = process.env.DB_PASSWORD;
const host = process.env.INSTANCE_HOST;
const port = process.env.DB_PORT;
const database = process.env.DB_NAME;
const prisma = new PrismaClient({
datasources: {
db: {
url: `mysql://${userName}:${password}@${host}:${port}/${database}`,
},
},
});
datasource db {
provider = "mysql"
url = ""
}
Discussion