Nuxt+SSR+TypeScript なアプリケーションを GitHub Actions で GAE に自動デプロイする


対象読者

  • Nuxt.js で SSR したときのお手軽デプロイ方法を知りたい
  • Google App Engine(GAE) に Nuxt をデプロイしたい
  • GitHub Actions で GAE に自動デプロイしたい

概要

Nuxt.js のデプロイ先に悩むことが多く、特に SSR した場合に手軽にデプロイできる方法を探していました。

静的サイトなら Netlify で十分なのですが、SSR だったり、動的にコンテンツが増える状況において、Netlify では役不足です。でも、mater merge 時の自動デプロイとカスタムドメインはありがたい存在なので、他のデプロイ先に移行しつつ、Netlify のメリットを受け継げる方法を探しました。

調査の結果、GAE への Nuxt.js のデプロイによって、自動デプロイとカスタムドメインの要件も含めすべて満たすことができました。

GCPGAE の設定

まずは GCP および、 GAE の初期設定について解説し、その後デプロイ方法について、最後に GitHub Actions について説明していきます。

gcloud コマンドのインストール

自身の PC に、下記ページを参考に gcloud コマンドをインストールします(※MacOS の場合)。

インストール後は PATH を通しておくと便利です。

GCP Project の作成

続いてデプロイ先になる GCP のプロジェクトを作ります。今回の場合、僕が作ってみたサービスでは Firebase を利用しているので、対応する GCP プロジェクトはすでに作られていました。

GCP プロジェクトが作成できたら、課金を有効にしておきます。

そのあと、gcloud initコマンドを実行して、作成した GCP プロジェクトを利用できる状態にします。

app.yaml の設置

続いて、GAE の設定ファイルであるapp.yamlをプロジェクトルートに設置します。

以下のような内容で作成します。とりあえずこれで動いていますが、Node.js のバージョン、および静的ファイルやインスタンスの大きさなどは適宜調整してください。

公式ドキュメントはこちら

runtime: nodejs12
instance_class: F2
handlers:
  - url: /_nuxt
    static_dir: .nuxt/dist/client
    secure: always
  - url: /(.*\.(gif|png|jpg|ico|txt))$
    static_files: static/\1
    upload: static/.*\.(gif|png|jpg|ico|txt)$
    secure: always
  - url: /.*
    script: auto
    secure: always
env_variables:
  HOST: "0.0.0.0"
  NODE_ENV: "production"

Nuxt.js アプリケーションの作成とビルド

アプリケーションの作成

今回は Nuxt.js (バージョン 2.9 以降)で TypeScript を利用しており、かつ、nuxt.config.js などの設定ファイルも TypeScript 対応しているものとします。僕は 2.10.2 と 2.11.0 でデプロイできることを確認済みです。

また、SSR 対応している前提なので、mode: 'spa'ではありません。

nuxt.config.ts
import { Configuration } from '@nuxt/types'

const config: Configuration = {
    ...

package.json のビルドコマンドも nuxt-ts を利用しています。


"build": "nuxt-ts build",

ビルド

ローカルでnpm run buildを実行してビルドが通る状態にしておきます。

$ npm run build
...
ce849990709e675b2f0d.js    7.4 KiB      13  [emitted] [immutable]
e1e7424856593dd60cc8.js   20.1 KiB       8  [emitted] [immutable]  pages/index
ef4de0a4edcd2c1e3068.js   7.49 KiB       0  [emitted] [immutable]
              server.js   32.6 KiB       2  [emitted]              app
   server.manifest.json  899 bytes          [emitted]
Entrypoint app = server.js

GAE への Nuxt アプリケーションのデプロイとカスタムドメイン

ビルドが通ったら、続いてgcloud app deployコマンドを実行します。

$ gcloud app deploy --project={プロジェクト名}

最初のデプロイはファイル数が多いので案外時間がかかります。また、途中で yes/no をプロンプトで訊かれるので、忘れず yes を入力しましょう。

デプロイ後エラーになったとき

僕の記憶ではデプロイ自体に失敗したことはなかったはずですが、デプロイ後にページを開くとエラーになる場合があります。

基本的にはエラーの原因になったライブラリをdevDependenciesからdependenciesに移行すれば解決しました。

@nuxtjs/style-resources@nuxt/typescript-buildが最初devDependenciesに入っていたので、実行時にエラーを起こしていました。package.jsonを編集し、再度npm run buildからやり直すことで、動作しました。

この時点で、https://{プロジェクトID}.appspot.comでページを開くことができるはずです。

デプロイした環境にカスタムドメインを設定する

GAE にはカスタムドメインを紐付けることができます。

僕は AWS の Route53 で日頃ドメインを取っているので、今回も Route53 で取得したものを紐付けました。こだわりがなければ Google Domain で取得したほうが取り回しはいいかもしれません。

概ね、下記記事の通りで紐付けできました。

GitHub Actions を設定し、master merge したときに自動でデプロイ

以上で、手動でデプロイすることは可能になったので、次は master merge したときにデプロイを実現します。

GitHub Actions 自体は、リポジトリ内に.github/workflows/gcloud.ymlといった名称のファイルを置いてリモートに Push するだけで設定可能です。

GitHubActions タブからポチポチするだけで、勝手に yml が置かれて、Feature ブランチに Push されるので本当にすぐ設定は終わります。

問題は yml の中身です。僕は下記の yml で動作しました。

.github/workflows/gcloud.yml
name: Google App Engine Deploy

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.11.0]

    steps:
      - uses: actions/checkout@v2
      - name: Setup gcloud environment
        uses: GoogleCloudPlatform/[email protected]
        with:
          # The version of the gcloud SDK to be installed.  Example: 275.0.0
          # The service account email which will be used for authentication.
          service_account_email: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }}
          # The service account key which will be used for authentication.
          service_account_key: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_CREDENTIALS }}
      - name: Run tests with ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - name: npm install, build
        run: |
          npm install
          npm run build
        env:
          APIKEY: ${{ secrets.FIREBASE_APIKEY }}
          AUTHDOMAIN: ${{ secrets.FIREBASE_AUTHDOMAIN }}
          DATABASEURL: ${{ secrets.FIREBASE_DATABASEURL }}
          PROJECTID: ${{ secrets.FIREBASE_PROJECTID }}
          STORAGEBUCKET: ${{ secrets.FIREBASE_STORAGEBUCKET }}
          MESSAGINGSENDERID: ${{ secrets.FIREBASE_MESSAGINGSENDERID }}
          APPID: ${{ secrets.FIREBASE_APPID }}
          MEASUREMENTID: ${{ secrets.FIREBASE_MEASUREMENTID }}
      - name: GAE deploy
        run: gcloud app deploy app.yaml --project {プロジェクト名}

ポイント

上記の yml を作成するためのポイントはいくつかあります。

on push で master ブランチを指定する

master merge したときにデプロイしたいので、動作するトリガーは master への Push とします。

GoogleCloudPlatform/github-actions を利用する

公式がサービスアカウントのクレデンシャルなどを読み取ることを前提とした設定を公開してくれているので、ありがたく利用します。

サービスアカウントの作成

サービスアカウントを GCP コンソールから作成します。これは、GitHub Actions にデプロイ権限を渡すために必要な作業です。GitHub Actions は第三者のアプリケーションになるので、そこからデプロイを実行するためには適切な権限が必要なので、その権限を付与したサービスアカウントを作成し、そのクレデンシャルを GitHub の Secrets に設定のうえ、yml で読み取るという流れです。

GCP のコンソールから IAM→Service Accounts へアクセスします。

画面内の「+サービスアカウントを作成」ボタンを押し、サービスアカウント名には「GitHub Actions」等の命名をつけます。

また、作成後は、下記の権限を付与します。僕は下記の権限を付与したところで無事にデプロイができるようになりましたが、過不足あればぜひおしえてください。できれば思考停止で管理者権限付与などはしたくないものです。

サービスアカウントの作成時に、キー(クレデンシャル)の作成ができるところがあるので、これをクリックし、JSON 形式でダウンロードしてください。

以上でサービスアカウントの作成が終わりです。

キーは base64 エンコードする

さて、https://github.com/GoogleCloudPlatform/github-actions/tree/master/setup-gcloud に記してあることですが、上記でダウンロードしたクレデンシャルの JSON ファイルは、Base64 エンコードしたうえで GitHubSecrets に保存します。

ローカルで base64 コマンドを叩くなどしてエンコードしてください。

service_account_key: (Optional) The service account key which will be used for authentication. This key should be created, encoded as a Base64 string (eg. cat my-key.json | base64 on macOS), and stored as a secret.

GitHub Secrets 設定

GitHubSetting タブから、Secrets 画面に移動し、上記の yml ファイルに指定されている「GOOGLE_SERVICE_ACCOUNT_CREDENTIALS」などの環境変数を保存していってください。僕がやっていたプロジェクトでは Firebase を利用しているので、Firebase 関連の情報も全部 Secrets に載せました。


以上で設定は終わりです。実際に master merge してみると、Actions タブからデプロイが動いていることを確認できますし、もし失敗してもログが出ているので、だいたいなんとかなると思います。

補足

ERROR: (gcloud.app.deploy) Error Response: [7] Access Not Configured. Cloud Build has not been used in project XXXXXXX before or it is disabled.

というエラーがデプロイ時に出たら、その後に表示されているリンクをクリックしてCode Buildを有効にしてください。

まとめ

最後の GitHub Actions の設定がもっとも難しいところですが、一度やってしまえば、SSR 対応の Nuxt アプリケーションを master merge 時に自動デプロイでき、しかもカスタムドメインで SSL 対応も終わっているので大変便利だと思います。

簡単な用途であれば、SSR 対応のアプリケーションで Netlify とほとんど近しい DX を再現できたと思います。