【ハジメテのWebアプリ】Next.JS on GAEのデプロイ🍬完全自動化ガイド🍭

7 min read読了の目安(約6400字

0. はじめに

クラウドの普及により、サーバー管理者がいない・資金のない弱小スタートアップでもプログラマーが1人いればサービスを作れる時代になりました。

ということで、今回は「コードを書くだけでサービスが勝手に動く」状態を作りたいと思います。

初心者の方へプログラミングを料理に例えて説明することがあるようですね。
リアルでも「レシピを書いたら料理ができあがる」装置があれば嬉しいけど😅

● 使用するもの

GCP
  • Cloud Build
    → Githubにコードをpushしたら自動でデプロイを実行します。環境変数などの設定をします。
  • App Engine
    → 今回はここでNext.jsのWebアプリを動かします。
Github
  • gitリポジトリの管理
  • Github Action
    → デプロイ前にテストをする。
ESLint

デプロイ前のテストで構文チェックをして未然にエラーを防ぎます。

● ディレクトリ構造

特にこだわりはありませんが、プロジェクト下にdeployディレクトリを作ることにします。

1. GAEの設定ファイルを書こう

まずは、GAEの設定ファイルを書きます。一般的にはapp.yamlと呼ばれるものです。このファイルでは、GAEで使うインスタンスの台数やオートスケールの設定を書きます。また、フレキシブル環境ではメモリやCPUを詳細に設定することもできます。

※詳しい書き方やオプションはApp Engineのドキュメントで確認してください。

https://cloud.google.com/appengine/docs/standard/nodejs/config/appref

今回はF1インスタンス(フロント用のインスタンス)を指定します。F1インスタンスは 28時間/日の無料枠が用意されているため、1台であればインスタンス料金がかかりません。 練習・ポートフォリオ用のアプリケーションを簡単に運用できますね。✨

deploy/frontend.yaml
runtime: nodejs12
service: default
instance_class: F1

automatic_scaling:
  max_instances: 1

handlers:
  - url: /.*
    secure: always
    script: auto

2. ビルドファイルを書こう

ビルドで実行するアクションを書きます。(yarnを使っている方は適宜置き換えてください)

● シンプルなアプリ

deploy/cloudbuild.yaml
steps:
  # npm install
  - name: 'gcr.io/cloud-builders/npm'
    args: ['install']

  # npm run build
  - name: 'gcr.io/cloud-builders/npm'
    args: ['run', 'build']

  # gcloud app deploy --appyaml=./deploy/frontend.yaml --stop-previous-version --quiet
  - name: 'gcr.io/cloud-builders/gcloud'
    args: ['app', 'deploy', '--appyaml=./deploy/frontend.yaml', '--stop-previous-version', '--quiet']

最もシンプルなNext.jsアプリは上のようなステップだけで良いです。上から順に、

  1. npmで依存パッケージをインストールする。
  2. Next.jsをproductionモードでビルドする。
  3. GAEにデプロイする。

というステップを実行しています。


● .envを用意して環境変数を使う

オープンソースの場合、DBにアクセスするパスワードやJWTシークレット等の機密情報を隠す必要があります。また、ハードコードを避ける・管理を楽にするという点でも、ビルド時に環境変数を組み込むことが役に立ちます。

Next.jsでは npm run build の前に.envを用意しないとうまく動作しません。下のステップを先ほどのファイルの npm run build の前に追加してください。

firebase.yaml
steps: #この行より後を追加
  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        echo 'JWT_SECRET="${_JWT_SECRET}"' > .env
        echo 'FIREBASE_API_KEY="${_FIREBASE_API_KEY}"' >> .env
        echo 'FIREBASE_AUTH_DOMAIN="${_FIREBASE_AUTH_DOMAIN}"' >> .env
        echo 'FIREBASE_DATABASE_URL="${_FIREBASE_DATABASE_URL}"' >> .env
        echo 'FIREBASE_PROJECT_ID="${_FIREBASE_PROJECT_ID}"' >> .env
        echo 'FIREBASE_STORAGE_BUCKET="${_FIREBASE_STORAGE_BUCKET}"' >> .env
        echo 'FIREBASE_MESSEGING_SENDER_ID="${_FIREBASE_MESSEGING_SENDER_ID}"' >> .env
        echo 'FIREBASE_APP_ID="${_FIREBASE_APP_ID}"' >> .env
        echo 'FIREBASE_MEASUREMENT_ID="${_FIREBASE_MEASUREMENT_ID}"' >> .env
        cat .env

${_JWT_SECRET}のようにアンダースコアがついている変数はCloud Buildのトリガーで設定する環境変数です。あとの #5「Cloud Buildのトリガーを作成」— 「環境変数を設定」 で説明します。


● app.yamlを編集する

app.yamlに直に環境変数を書き込むこともできます。Golangなどを動かす場合はこちらの方法を使います。godotenvを導入した方が楽かも?

cloudbuild.yaml
steps: #この行より後を追加
  - name: 'gcr.io/cloud-builders/curl'
    args: ['-o', 'yq', "-L", 'https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64']

  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
    - '-c'
    - |
      chmod +x ./yq
      ./yq write -i ./deploy/backend.yaml --style=double env_variables.OAUTH_ID "${_OAUTH_ID}"
      ./yq write -i ./deploy/backend.yaml --style=double env_variables.OAUTH_SECRET "${_OAUTH_SECRET}"
      ./yq write -i ./deploy/backend.yaml --style=double env_variables.JWT_SECRET "${_JWT_SECRET}"
      cat ./deploy/backend.yaml
      rm -rf ./yq
    

● 他のオプションたち

https://cloud.google.com/cloud-build/docs/configuring-builds/create-basic-configuration?authuser=3

3. Cloud Buildの初期設定

GCPコンソールから Cloud Build > Settings を開きます。

https://console.cloud.google.com/cloud-build/settings

Cloud BuildでApp Engineにデプロイする(gcloudを使う)ためには「App Engine 管理者」と「サービス アカウント ユーザー」の権限を有効にする必要があります。

4. Cloud Buildにリポジトリを接続

GCPコンソールから Cloud Build > Triggers を開きます。

https://console.cloud.google.com/cloud-build/triggers

「リポジトリを接続」をクリックします。Githubでgitを管理している場合は、Githubで認証した後、使うリポジトリを選ぶと接続完了です。

5. Cloud Buildのトリガーを作成

● デプロイ条件を設定

デフォルトでは全てのブランチのpushを検知して、デプロイを開始してしまいます。開発でブランチを分けている場合は、このままでは困りますね。😢

^main$を入力して、mainブランチへのコミットのみをデプロイするようにします。


● ビルドファイルの場所を設定

次に、Cloud Buildで実行するファイルの場所を設定します。自動検出でも構いませんが、確実に deploy/cloudbuild.yaml を選ばせます。


● 環境変数を設定

ここで #2「ビルドファイルを書こう」— 「.envを用意して環境変数を使う」 で作成したアクションで使う環境変数を設定します。変数名は必ず _ で始まるようにしなければなりません。

最後に、保存したら設定は終わりです。Githubのリポジトリへコードがプッシュされると、Cloud Buildが変更を検知して、#2「ビルドファイルを書こう」 で用意したビルドファイルのアクションを実行します。もう手動でデプロイする必要はありません。あとはコードを楽しんで書くだけです。😉 🎉

6. テストをしてデプロイエラーを未然に防ぐ

Github Actionsを使ってコードがpushされるたびにテストを実行します。

  • ESLint
    → JavaScriptの構文チェックをします。
  • ビルドテスト
    → 多少おかしくても、ビルドが通れば最低限は動きます。ダウンタイムは避けたい...。
.github/workflows/test.yaml
name: Build & Test

on: push

jobs:
  test:
    runs-on: ubuntu-latest
    name: Build & Test
    timeout-minutes: 10
    strategy:
      matrix:
        node-version: [12.x]
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{github.event.pull_request.head.ref}}

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install packages
        run: |
          npm i

      - name: ESLint
        run: |
          npm run lint

      - name: Next.js build test
        run: |
          npm run build

ESLintを導入してないよ、って方は破壊的なコードを生み出す前に導入を検討しましょう...。ESLintとPrettierについては下の記事で少し書いているのでよかったら見てください。

https://zenn.dev/66ed3gs/articles/99aa613a86f21f

7. まとめ

作業は以上です。お疲れ様でした。
このようにCloud Build等でデプロイ・テストを自動化すれば、1人でもサービス開発を効率化することができます。

詳しく動作確認をしていないため、間違っている部分があったら教えていただけると幸いです。

いつもサポートありがとうございます!