普段からFirebaseを多用してるので、インフラ環境をGCPにまとめれないか考えてCloud Runをテストしました。
仕事のプロジェクトはGitlabを利用してるので、stagingやmainのブランチにpushしたらciを回し、Cloud Buildを通してCloud Runにデプロイするイメージで構築しました。
ローカル環境でプロジェクトを作る
まずは適当にローカル環境で動くものを作ります。
ここでは公式のチュートリアルを参考にします。
// Tree
├── Dockerfile
├── index.js
├── node_modules
├── package-lock.json
└── package.json
// index.js
const express = require('express')
const app = express()
app.get('/', (req, res) => {
const name = process.env.NAME || 'World';
res.send(`Hello ${name}!`)
})
const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
console.log(`helloworld: listening on port ${port}`)
})
# Dockerfile
FROM node:18-slim
# アプリディレクトリを作成して変更します。
WORKDIR /usr/src/app
# アプリケーションの依存しているもの(package.jsonとpackage-lock.json)をコンテナイメージにコピー。
COPY package*.json ./
# モジュールのインストール
RUN npm install --only=production
# 環境変数
ENV NAME="Cloud Run"
# イメージのコピー
COPY . ./
# サーバーの起動
CMD [ "node", "index.js" ]
動作確認
まずはコマンドを直接叩いて起動するか確認します。
node index.js
次にローカルでDockerを起動させます。
# 起動
docker build . --tag cloudrun-test
docker run -d -p 8080:8080 {IMAGE_ID}
# 停止と削除
docker ps
docker stop {CONTAINER ID}
docker rm {CONTAINER ID}
docker images
docker rmi {IMAGE ID}
確認できたら、これでNext.jsを組み込む前のローカルのセットアップは完了です。
Artifact Registryにコンテナをデプロイする
リポジトリの作成
Artifact Registryに移動し、リポジトリを作成します。
今回はcloudrun-test
で作りました。
以下の赤丸からリポジトリ名まで取得できます。
イメージのPUSH
ローカルで再度タグを付けて、dockerをビルドします。
# docker build . --tag {region}-docer.pkg.dev/{GCP_PROJECT_ID}/{リポジトリ名}/{イメージ名}
docker build . --tag asia-northeast1-docker.pkg.dev/helloworld-123456/cloudrun-test/test:latest
pushします。
docker push asia-northeast1-docker.pkg.dev/helloworld-123456/cloudrun-test/test:latest
成功するとArtifact Registryのリポジトリ内にtestという名前が追加されます。
Cloud Runの設定
サービスの作成から「既存のコンテナイメージから1つのリビジョンをデプロイする」を選び、選択をクリックします。
その中のArtifact Registryタブを選び、リポジトリ名が同じものを開いていき、最新のものを選んでください。
あとは認証を許可して作成をクリックすると完了です。
「xxxxxxx.x.run.app」のurlを開いて「Hello Cloud Run!」が表示されていれば成功です。
Next.jsをCloud RunにDeployする
とりあえず今までのフォルダを空にして、Next.jsのプロジェクトを展開します。名前を聞かれたら「.」で現在のフォルダ内に展開します。
npx create-next-app@latest --ts
✔ What is your project named? … .
Standaloneを有効にして、Dockerfileを書き換える
next.config.jsに追記します。
// next.config.js
const nextConfig = {
reactStrictMode: true,
output: 'standalone',
}
module.exports = nextConfig
Dockerfileも以下のように書き直します。この辺りも公式のものを参考にしてます。
# Dockerfile
FROM node:18-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
FROM node:18-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:18-slim AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
同じようにPushして再デプロイして変更が反映されてばOKです。
Cloud Buildの設定
Cloud Buildを使用するのでIAMの設定を行います。
Cloud Buildを有効にする
- Cloud Buildに移動して、APIを有効にします。
- Cloud Buildの設定にてCloud Run管理者を有効にします。
IAMの設定
IAMのサービスアカウントに移動して、サービスアカウントを作成し、service-account.jsonをダウンロードしておきます。
yamlを追加
プロジェクトのrootにcloudbuildというフォルダを作り、その中に「cloudbuild.staging.yaml」や「cloudbuild.production.yaml」などデプロイしたい環境毎のyamlを作成します。(stagingはCloud RunでStaging環境が欲しい場合などに使ってください)
今回はcloudbuild.production.yamlのみで進めていき、cloudbuildのコマンドを記述していきます。
# cloudbuild.production.yaml
steps:
# Docker Build
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '.',
'--tag', '${_DESTINATION}:latest',
'--cache-from', '${_DESTINATION}:latest']
# Docker push to Google Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '${_DESTINATION}:latest']
# Deploy to Cloud Run
- name: google/cloud-sdk
args: ['gcloud', 'run', 'deploy', '${_CLOUD_RUN_NAME}',
'--image=${_DESTINATION}:latest',
'--region', '${_GCP_REGION}',
'--platform', 'managed',
'--allow-unauthenticated']
images:
- ${_DESTINATION}
substitutions:
_CLOUD_RUN_NAME: cloudrun-test
_GCP_REGION: asia-northeast1
_DESTINATION: asia-northeast1-docker.pkg.dev/helloworld-123456/cloudrun-test/test
編集したい時はsubstitutions
だけを編集したら使えると思います。
ローカル環境ではDockerを使わずにnext dev
で開発をしたいため、.envファイルを作り.gitignoreに追記しておきます。
Gitlab CIの設定
Gitlab管理ページの設定
サイドメニューのSettings → CI/CDからVariablesを開きます。
ここに環境変数を記述していきますが、そこでちょっと工夫します。
SERVICE_ACCOUNTというkeyに、IAMで作成したservice-account.jsonの中をコピペして、Protectedをoffにして保存します。
同じように.envの内容をENV_PRODUCTIONというkeyに書き込んでいきます。本来はkeyに対してvalueは一つですが設定が面倒なので横着をします。
# ENV_PRODUCTION
NEXT_PUBLIC_NAME="CLOUD RUN PRODUCTION"
SECRET_KEY="nklhfuwerp2349hdjfl"
これも同じように保存します。
.gitlab-ci.ymlの作成
次にciの設定ファイルを作成します。今回はmainにmergeしたらciを動かして、cloudbuildにdeployします。
# .gitlab-ci.yml
stages:
- deploy
build-job:
stage: build
deploy-production:
stage: deploy
image: google/cloud-sdk:alpine
script:
- echo $ENV_PRODUCTION | sed -e "s/ /\n/g" > .env
- echo $SERVICE_ACCOUNT > service-account.json
- gcloud auth activate-service-account --key-file service-account.json
- gcloud config set project helloworld-123456
- gcloud auth configure-docker
- gcloud builds submit --config ./cloudbuild/cloudbuild.production.yaml
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
ENV_PRODUCTIONをそのままechoしてしまうとスペースを挟んで一列にされてしまうのでecho $ENV_PRODUCTION | sed -e "s/ /\n/g" > .env
でスペースを改行コードに変換して、.envに書き込んでいます。
これで全ての設定は終わりです。
まとめ
今回は一度にCloud Run、Cloud Build、Gitlab CIを学ばないといけないかったので、結構大変でした。
次からは基本的にGCPに集約できそうです。