Oops!!

プログラミングとかのラフなブログ

Next.js + Gitlab CI + Cloud Runで環境構築

Next.js + Gitlab CI + Cloud Runで環境構築

普段から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を有効にする

  1. Cloud Buildに移動して、APIを有効にします。
  2. 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に集約できそうです。

プロフィール画像

すずき ゆうた

愛知県でフリーランスのフロントエンド・エンジニアをしています。Reactを用いた開発が得意です。 他にもプロジェクトマネジメントや組織マネジメントも行ってきました。エビデンスのない事でも自分の経験から書いていくので話半分くらいでお願いします。