はじめに
Dify を使って GUI のみで爆速で API を作ることができるようになりました。
Fast API を使って AI を活用した API を作成することもあると思います。
API を作っても UI がないとユーザーは使うことができないので、アプリをリリースしてユーザーに使ってもらうには UI を作る必要があります。
UI を作る上で一番メジャーなのが、NextJS だと思います。
NextJS は Vercel にデプロイしたほうが、NextJS の強みを活かせるため、それがいいというのはありつつ、チームで使うと料金が発生してそれが高かったりします。
そのため、GCP の Cloud Run にデプロイするというのも選択肢の一つとして挙がるかと思います。
今回は Cloud Run にデプロイする中でハマったところや自動デプロイの設定方法について詳しく書いていきたいと思います。
環境設定
まずは、今回使用する GCP のサービスは以下の4つになります。
- Cloud Run
- Cloud Build
- Secret Manager
- Artifact Registry
それぞれ簡単に説明します。なお、特徴をそれぞれ挙げてますが、小規模のアプリケーションを作ることを想定して挙げています。
Cloud Run
Google Cloud Run は、コンテナ化されたアプリケーションをサーバーレスで実行するためのマネージドコンピューティングプラットフォームです。以下が Cloud Run の主な特徴です:
- サーバーレス: インフラストラクチャの管理が不要で、アプリケーションのコードに集中できます。
- コンテナベース: Docker コンテナをサポートしているため、任意の言語やフレームワークでアプリケーションを構築できます。
- 自動スケーリング: トラフィックに応じて自動的にスケールアップ/ダウンします。使用していない時は0インスタンスまでスケールダウンすることも可能です。
- 従量課金制: 実際に使用したリソースに対してのみ課金されるため、コスト効率が高いです。
- HTTPS エンドポイント: デフォルトで安全な HTTPS エンドポイントが提供されます。
- カスタムドメイン: 独自のドメインをマッピングすることができます。
Cloud Run は、特にマイクロサービスやウェブアプリケーション、API、バックエンドプロセスなどの実行に適しています。Next.js のようなフロントエンドアプリケーションも、コンテナなので NodeJS 環境にできるため サーバーサイドレンダリング (SSR) を活用しつつ、効率的にデプロイすることができます。
Cloud Build
Google Cloud Build は、Google Cloud Platform (GCP) が提供する完全マネージド型の継続的インテグレーション/継続的デリバリー (CI/CD) プラットフォームです。以下が Cloud Build の主な特徴です:
- Github 連携: GitHub と連携して、main ブランチにマージされたらデプロイなどのように設定できます。
- カスタマイズ性: YAML ファイルでビルド設定を定義し、柔軟にカスタマイズできます。
Cloud Build を使用することで、コードのコミットからテスト、ビルド、そしてデプロイまでの一連のプロセスを自動化し、開発サイクルを大幅に効率化することができます。特に、コンテナ化されたアプリケーションの CI/CD パイプラインの構築に適しています。
このブログではビルド、デプロイを行うために利用します。
Secret Manger
Google Cloud Secret Manager は、API キー、パスワード、証明書などの機密情報を安全に保存、管理、アクセスするためのサービスです。以下が Secret Manager の主な特徴です。
- 中央集中型管理: プロジェクト全体の機密情報を一元管理できます。
- きめ細かなアクセス制御: IAM (Identity and Access Management) との統合により、シークレットごとに詳細なアクセス権限を設定できます。
- 暗号化: デフォルトで全てのシークレットが暗号化されます。カスタマー管理の暗号化キー (CMEK) も使用可能です。
- プログラマティックアクセス: 様々な言語用のクライアントライブラリを通じて、アプリケーションから直接シークレットにアクセスできます。
- 監査ログ: シークレットへのアクセスや変更の詳細な監査ログを提供します。
- バージョン管理: シークレットの複数バージョンを保存し、ロールバックやバージョン間の比較が可能です。
Secret Manager を使用することで、ハードコーディングや安全でない方法での機密情報の管理を避け、セキュリティを向上させつつ、開発プロセスを簡素化することができます。特に、クラウドネイティブな開発環境において、機密情報の安全な管理と利用を実現します。
このブログでは、Firebase の認証情報をこちらに保存して、Cloud Build で利用するという使い方をします。
Artifact Registry
Google Cloud Artifact Registry は、ソフトウェアアーティファクトを保存、管理、セキュア化するための完全マネージド型のサービスです。Docker コンテナイメージ、言語パッケージ、その他のアーティファクトを一元的に管理できます。
このブログでは、Docker コンテナの保存場所として使っています。AWS の ECR と同じ役割を持ちます。
実際の作業
ここでは仮の Firebase プロジェクトとして demo-proj
、サービス名を demo-proj-frontend-prod
とします。また、リージョンは asia-northeast1
(東京) とします。
Docker ファイルの準備
まずは、Docker ファイルを準備します。
FROM node:lts-alpine AS base
# ビルド時の引数として環境変数を定義
ARG NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_FIREBASE_KEY
# 定義した引数を環境変数として設定
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_FIREBASE_KEY=$NEXT_PUBLIC_FIREBASE_KEY
RUN npm install -g yarn
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM node:lts-alpine AS runner
WORKDIR /app
RUN npm install -g yarn
COPY --from=base /app/package.json ./
COPY --from=base /app/yarn.lock ./
COPY --from=base /app/.next ./.next
COPY --from=base /app/public ./public
# 本番環境用の依存関係のみをインストール
RUN yarn install --production --frozen-lockfile
EXPOSE 3000
ENV NODE_ENV production
ENV HOSTNAME "0.0.0.0"
CMD ["yarn", "start"]
next build 時に Firebase の認証情報が必要になります。そのため、ビルド時に環境変数を埋め込むやり方をしています。
そうでないと、next build 時に auth/invalid-api-key
のようなエラーが発生します。(理由は後述)
Secret Manager に環境変数を登録
そして、次に Secret Manager に環境変数を登録していきます。
プロジェクト単位で一元管理されるので、フロントエンドとバックエンドの両方から使う場合は、BACKEND_
などのようにプレフィックスがあると、混ざらずに済むと思いました。
今回は NEXT_PUBLIC_FIREBASE_KEY
と NEXT_PUBLIC_API_URL
を設定します。
Cloud Build の設定ファイルを書く
そして、次に cloudbuild.yaml
で設定を書いていきます。
Secret Manager から値を持ってきて、Docker build の際に引数として、値を渡しています。
steps:
# ビルドステップ
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker build \
--build-arg NEXT_PUBLIC_API_URL=$$NEXT_PUBLIC_API_URL \
--build-arg NEXT_PUBLIC_FIREBASE_KEY=$$NEXT_PUBLIC_FIREBASE_KEY \
-t asia-northeast1-docker.pkg.dev/demo-proj/proj/frontend:${COMMIT_SHA} .
secretEnv: ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_FIREBASE_KEY']
# プッシュステップ
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'asia-northeast1-docker.pkg.dev/demo-proj/proj/frontend:${COMMIT_SHA}']
# デプロイステップ
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'demo-proj-frontend-prod'
- '--image'
- 'asia-northeast1-docker.pkg.dev/demo-proj/proj/frontend:${COMMIT_SHA}'
- '--platform'
- 'managed'
- '--project'
- 'demo-proj'
- '--allow-unauthenticated'
- '--port'
- '3000'
- '--region'
- 'asia-northeast1'
# シークレットの設定
availableSecrets:
secretManager:
- versionName: projects/demo-proj/secrets/NEXT_PUBLIC_API_URL/versions/latest
env: 'NEXT_PUBLIC_API_URL'
- versionName: projects/demo-proj/secrets/NEXT_PUBLIC_FIREBASE_API_KEY/versions/latest
env: 'NEXT_PUBLIC_FIREBASE_API_KEY'
このようにできればあとは gcloud builds submit --project=demo-proj --substitutions=COMMIT_SHA=$(git rev-parse HEAD)
をすればデプロイができます。
次の記事で自動デプロイの設定方法について書く予定です。
ハマったエラーの共有
やることとしてはこれだけですが、たくさんエラーを踏んだので、共有していきたいと思います。
まず前提として、NextJS では環境変数の設定が大きく分けて2種類ありました。
自分の認識ではそれがなかったので、かなりハマってしまいました。
1つ目はビルド時に環境変数を埋め込むもので、NEXT_PUBLIC_
のプレフィックスを持ちます。これを持つとビルド時にクライアントサイドの JavaScript に埋め込まれ、ブラウザで実行されるコードで利用可能になります。
Firebase の設定情報は、クライアントサイドで利用することを前提としているため、NEXT_PUBLIC_
をつけて環境変数を設定します。
そのため、ビルド時に正しい環境変数が設定されている必要があります。
もう一つがサーバーサイドで利用される環境変数で、これはランタイム時に読み込まれ、サーバーサイドでのみ利用が可能になります。
自分はこれら2つを混ぜて考えてしまっていたので、Cloud Run に環境変数を設定したのになぜ next build のときに Firebase 関連で失敗してしまうんだと混乱してしまっていました。
これは、Firebase の設定情報が環境変数として埋め込まれていることを前提としているが、ビルドしている環境 (Dockerfile) の中では Firebase の設定情報が環境変数として設定されていないためでした。
FirebaseError: Firebase: Error (auth/invalid-api-key).
これは Firebase の環境変数がセットできていないことから起こるエラーです。
自分の場合は、まずはローカルで docker build -t gcr.io/demo-proj/frontend ./
のようにビルドを試していました。
そのときに、.env ファイルを .dockerignore ファイルに入れていて、ビルドしていました。それにより Firebase の環境変数がセットされずに上記エラーが発生しました。
denied: Unauthenticated request.
Docker イメージをプッシュしたときに以下のエラーが発生しました。
denied: Unauthenticated request. Unauthenticated requests do not have permission "artifactregistry.repositories.uploadArtifacts" on resource "projects/demo-proj/locations/us/repositories/gcr.io" (or it may not exist)
これは以下のコマンドを打って認証設定を行いました。
gcloud auth configure-docker
Adding credentials for all GCR repositories.
WARNING: A long list of credential helpers may cause delays running 'docker build'. We recommend passing the registry name to configure only the registry you are using.
After update, the following will be written to your Docker config file located at [/Users/***/.docker/config.json]:
{
"credHelpers": {
"gcr.io": "gcloud",
"us.gcr.io": "gcloud",
"eu.gcr.io": "gcloud",
"asia.gcr.io": "gcloud",
"staging-k8s.gcr.io": "gcloud",
"marketplace.gcr.io": "gcloud"
}
}
Do you want to continue (Y/n)? Y
Docker configuration file updated.
デプロイ後のヘルスチェックに失敗する
To make this the default region, run `gcloud config set run/region asia-northeast1`.
Deploying container to Cloud Run service [demo-proj-frontend-prod] in project [demo-proj] region [asia-northeast1]
X Deploying...
- Creating Revision...
. Routing traffic...
✓ Setting IAM Policy...
Deployment failed
ERROR: (gcloud.run.deploy) Revision 'demo-proj-frontend-prod-00004-htl' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=3000 environment variable. Logs for this revision might contain more information.
Logs URL: https://console.cloud.google.com/logs/viewer?project=***
For more troubleshooting guidance, see https://cloud.google.com/run/docs/troubleshooting#container-failed-to-start
これは M1 Mac の問題でビルド時に linux/amd64 をつけることで解決しました。
docker build --platform linux/amd64 -t gcr.io/demo-proj/frontend ./
(注) GCR になっています。
以下のようにするとビルド & デプロイができました。
$ docker build --platform linux/amd64 -t gcr.io/demo-proj/frontend ./
$ docker push gcr.io/demo-proj/frontend
$ gcloud run deploy demo-proj-frontend-prod --image gcr.io/demo-proj/frontend --platform managed --project=demo-proj --port 3000 --allow-unauthenticated
unauthorized: authentication failed
Docker イメージのプッシュで以下のエラーが発生しました。
docker push gcr.io/demo-proj/frontend
Using default tag: latest
The push refers to repository [gcr.io/demo-proj/frontend]
2e689bcbeaa0: Preparing
27c67c19f257: Preparing
c2be8294c58c: Preparing
bd16d5a43914: Preparing
69280f02369e: Preparing
14b8bef233e8: Waiting
2776de19f60c: Waiting
9d6598bb2c09: Waiting
ea02d285a3e0: Waiting
87eaf97051ac: Waiting
9c63a4679e00: Waiting
94e5f06ff8e3: Waiting
unauthorized: authentication failed
その場合は、gcloud auth login
で解決しました。
does not have secretmanager.versions.access permissions for secret
以下のエラーが発生しました。
gcloud builds submit --project=demo-proj
Creating temporary archive of 87 file(s) totalling 1.2 MiB before compression.
Some files were not included in the source upload.
Check the gcloud log [/Users/***/.config/gcloud/logs/2024.07.17/12.53.38.672180.log] to see which files and the contents of the
default gcloudignore file used (see `$ gcloud topic gcloudignore` to learn
more).
Uploading tarball of [.] to [gs://demo-proj_cloudbuild/source/1721188419.070311-8a6648814d9d48bfa938f65b3f0a2c90.tgz]
ERROR: (gcloud.builds.submit) INVALID_ARGUMENT: default Cloud Build service account "*********@cloudbuild.gserviceaccount.com" does not have secretmanager.versions.access permissions for secret "projects/demo-proj/secrets/NEXT_PUBLIC_API_URL"
以下の画像のようにサービスアカウントの権限を有効にすると解決しました。
環境変数が設定されない
以下のようにしてビルド時の引数で環境変数を設定しましたが、設定されませんでした。
steps:
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '--build-arg'
- 'NEXT_PUBLIC_API_URL=$$NEXT_PUBLIC_API_URL'
# 略
bash を使うようにしたところ、解決しました。
steps:
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- |
docker build \
--build-arg NEXT_PUBLIC_API_URL=$$NEXT_PUBLIC_API_URL \
上記の説明なのかは不明ですが、公式で以下のような記述が見つかりました。
ビルドステップで bash ツールを使用するには、
bash
を指すentrypoint
フィールドを追加します。これは、シークレットの環境変数を参照するために必要です。
Artifact Registry にプッシュできない
Artifact Registry にプッシュできなかったので、以下のコマンドを実行したところエラーになりました。
gcloud artifacts repositories list --location=asia-northeast1
ERROR: (gcloud.artifacts.repositories.list) The required property [project] is not currently set.
It can be set on a per-command basis by re-running your command with the [--project] flag.
You may set it for your current workspace by running:
$ gcloud config set project VALUE
or it can be set temporarily by the environment variable [CLOUDSDK_CORE_PROJECT]
これは以下の Issue を参考に gcloud init したら解決しました。
gcloud artifacts repositories list --location=asia-northeast1
Listing items under project demo-proj, location asia-northeast1.
ARTIFACT_REGISTRY
REPOSITORY FORMAT MODE DESCRIPTION LOCATION LABELS ENCRYPTION CREATE_TIME UPDATE_TIME SIZE (MB)
proj DOCKER STANDARD_REPOSITORY asia-northeast1 Google-managed key 2024-07-17T16:47:49 2024-07-17T16:47:49 0
Cloud Build からのデプロイが失敗する
Cloud Build からデプロイしようとすると、以下のエラーになりました。
ERROR: (gcloud.run.deploy) PERMISSION_DENIED: Permission 'run.services.get' denied on resource 'namespaces/demo-proj/services/demo-proj-frontend-prod' (or resource may not exist). This command is authenticated as *******@cloudbuild.gserviceaccount.com which is the active account specified by the [core/account] property.
ERROR
ERROR: build step 0 "gcr.io/google.com/cloudsdktool/cloud-sdk" failed: step exited with non-zero status: 1
--------------------------------------------------------------------
BUILD FAILURE: Build step failure: build step 0 "gcr.io/google.com/cloudsdktool/cloud-sdk" failed: step exited with non-zero status: 1
ERROR: (gcloud.builds.submit) build 136d4bb1-624f-4094-80ba-64a5ab5ae99a completed with status "FAILURE"
こちらは以下のようにサービスアカウント権限を有効にしたら解決しました。
さいごに
色々ハマりどころがありました。
ここまで読んでいただきありがとうございました!