Node.js + Prisma + Cloud SQLなアプリを
Cloud Buildを使って、prisma migrateして、
さらにCloud Runにデプロイするときに、
いろいろ調べたときの備忘録。
Cloud Buildを使ったCloud Runへのデプロイについては、
以前の記事でも書いたけれど、
Cloud SQLへのマイグレーションをするとなると、かなりめんどくさいよう。。
やりたいこと
- GitHubへプッシュしたら、
prisma migrate deploy
を実行して- Cloud Runへデプロイ
やりたいことはこれだけなんだけど、
そのままだとCloud BuildからCloud SQLへはアクセスできない。。
ハマったポイント
いろいろ試してみたけど、うまく行かなかった点は以下な感じ。
- CloudBuildでそのまま実行してみる
- Cloud Build内からCloud SQLにアクセスできない...
- Cloud Build内ではCloud Auth Proxyが用意されていない...
- ビルドするDockerfile内でマイグレーション
- Dockerfile内では、バックグランド実行できない...
- => Cloud Auth Proxyを裏で立ち上げれない...
- cloudbuild.yamlの別ビルドステップでCloud Auth Proxyを立ち上げる
- Docker build時はVolumeを参照できない...
やったこと
以前の記事でやった内容に、
少し手を加えたシーケンシャルなやり方だと難しそう。。
そのため、
- waitForを使って、Cloud Auth Proxyの立ち上げを並列でおこなう
- マイグレーション用のDockerfileを用意する
というやり方で実施。
流れとしては、以下の通り。
- Proxyの起動とアプリとマイグレ用のイメージのビルドをそれぞれ開始
- Proxyは起動し続けるので、waitForでIDを指定すると、開始されない
- "gcr.io/cloudsql-docker/gce-proxy"は新しいものだとうまく動かなかった。。
- voluemsをマイグレ用のイメージを実行する際に共有し、 UNIXソケットを使ってCloud SQLに接続
- マイグレーションが完了したら、アプリをデプロイする
- 同時にProxyを
docker stop
で停止する
cloudbuild.yaml
実際のcloudbuild.yaml
はこんな感じ。
steps: # [Proxy] Start Cloud SQL Proxy in container image - id: Proxy:Start name: "gcr.io/cloudsql-docker/gce-proxy:1.15" args: - "/cloud_sql_proxy" - "-dir=/cloudsql" - "-instances=$_INSTANCE_CONNECTION_NAME" volumes: - name: cloudsql path: /cloudsql waitFor: ["-"] # [Migrate] Build container image - id: Migrate:Build name: "docker" args: - "build" - "--no-cache" - "-f" - "Dockerfile.migrate" - "-t" - "$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME/migrate:$COMMIT_SHA" - "." waitFor: ["-"] # [Migrate] Run container image - id: Migrate:Run name: "docker" args: - "run" - "-e" - "DATABASE_URL=$_DATABASE_URL" - "-v" - "cloudsql:/cloudsql" # volumeはhost側はpathではなくnameを指定 - "-w" - "/src" - "$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME/migrate:$COMMIT_SHA" - "sh" - "-c" - "npx prisma migrate deploy" volumes: - name: cloudsql path: /cloudsql waitFor: ["Migrate:Build"] # [App] Build the container image - id: App:Build name: docker args: - build - "--no-cache" - "-t" - "$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME:$COMMIT_SHA" - . - "-f" - "Dockerfile" - "--build-arg" - "DATABASE_URL=$_DATABASE_URL" waitFor: ["-"] # [App] Push the container image to Container Registry - id: App:Push name: docker args: - push - "$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME:$COMMIT_SHA" waitFor: ["Migrate:Run", "App:Build"] # [App] Deploy container image to Cloud Run - id: App:Deploy name: gcr.io/google.com/cloudsdktool/cloud-sdk entrypoint: gcloud args: - run - services - update - $_SERVICE_NAME - "--platform=$_PLATFORM" - "--image=$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME:$COMMIT_SHA" - "--region=$_DEPLOY_REGION" - "--quiet" waitFor: ["App:Push"] # [Proxy] Stop Cloud SQL Proxy Container image - id: Proxy:Stop name: "gcr.io/cloud-builders/docker" entrypoint: "sh" args: - "-c" - 'docker ps -q --filter ancestor="gcr.io/cloudsql-docker/gce-proxy:1.15" | xargs docker stop' waitFor: ["Migrate:Run"] images: - "$_GCR_HOSTNAME/$PROJECT_ID/$_SERVICE_NAME:$COMMIT_SHA" options: substitutionOption: ALLOW_LOOSE substitutions: _DEPLOY_REGION: asia-northeast1 _GCR_HOSTNAME: asia.gcr.io _PLATFORM: managed _SERVICE_NAME: "(Cloud Runのサービス名)" _DATABASE_URL: "mysql://(DB_USER):(DB_PASS)@localhost/(DB_NAME)?socket=(CloudSQLのインスタンス接続名)" _INSTANCE_CONNECTION_NAME: "(CloudSQLのインスタンス接続名)" timeout: 1200s tags: - gcp-cloud-build-deploy-cloud-run - gcp-cloud-build-deploy-cloud-run-managed - $_SERVICE_NAME
Dockerfile.migrate: マイグレ用のイメージ
マイグレ用のDockerfile(Dockerfile.migrate)は、こんな感じ。
nodeのイメージに、package.jsonとprismaディレクトリをコピーして、
インストールしてるだけ。
FROM node:16 # コンテナ内のwork dirを設定 WORKDIR /src # package.jsonをコピーして、パッケージのインストール COPY package.json ./ COPY package-lock.json ./ COPY prisma/ ./prisma/ RUN npm install
Dockerfile: アプリ用のイメージ
こちらはアプリのようDockerfile。
以前の記事と大きくは変わらず。
prismaを使うので、prismaディレクトリをコピーし、
DATABASE_URLを環境変数に設定するようにしている。
ARG VERSION=latest FROM node:16 ARG DATABASE_URL # コンテナ内のwork dirを設定 WORKDIR /src # 環境変数を設定し、ポートとホストを指定 ENV PORT 8080 ENV HOST 0.0.0.0 ENV DATABASE_URL $DATABASE_URL # package.jsonをコピーして、パッケージのインストール COPY package.json ./ COPY package-lock.json ./ COPY prisma/ ./prisma/ RUN npm install # ソースをコピーして、ビルド COPY . . RUN npm run build # コンテナが起動したら、nuxtを起動するよう指定 CMD [ "npm", "run", "start" ]
以上!!
これで、Pushするだけで、マイグレもデプロイもできるように(´ω`)
また、簡易的にDATABASE_URLはそのままにしているけど、
Secret Managerを使って設定するのがよいです〜
おまけ: プライベートIPを使ったCloud SQLへの接続
今回はCloud SQL Proxyを起動する方法にしているけど、
プライベートプールを利用して、静的IP範囲を定義しておけば、
プライベートIPでCloud SQLにも接続できるよう。
ただ、プライベートプールを使ったビルドは料金がかかるので、
今回はCloud SQL Proxyを起動する方法を調べました。
参考にしたサイト様
- 実は難しい、 Cloud Build から Cloud SQL への接続 | by 株式会社ジラフ | Medium
- Cloud BuildでCloud SQLのマイグレーションをしたい - Qiita
- Run node.js database migrations on Google Cloud SQL during Google Cloud Build - Stack Overflow
- Cloud BuildでCD時にCloud SQL Proxyを使ってマイグレーションしてみた話 - Qiita
- [GCP]Cloud Buildの中からCloud SQLにrake db:migrateしてみた話 | SoraRikuTech
- docker build中にVolumeの中身は参照することが出来ない - Crieit
- Cloud BuildからVPC内部のリソースにアクセスする | クラウドエース株式会社
- Cloud SQL Auth Proxy を使用して接続する | Cloud SQL for MySQL | Google Cloud
- Cloud Build の waitFor の挙動 - ぽ靴な缶
- Google Compute Engine(GCE)からCloud SQL接続でハマった - Qiita
- Cloud Run+Cloud SQLでPrisma in NestJSを動かすお話 - Qiita
- NestJS + Prisma + Cloud Run + Cloud SQLを試す
- CloudBuildでSecret Managerの機密データを取得して変数として使用する