GitHub Actions で Nuxt.js + Azure App Service の CI/CD を実現する

Nuxt.js の Universal SSR を Azure App Service で運用する方法 - Qiita を投稿後、 GitHub Actions からの デプロイ方法が知りたいという声があったので検証してみました。

なお、検証にあたっては、あの有名な「しばやん雑記」の Node.js アプリを Azure App Service へ最適な形でデプロイする - しばやん雑記 を参考にしながら、まずは Azure Pipelines で事前検証し、それを GitHub Actions に置き換えるアプローチを取りました。両社は共通のインフラを使っているらしいので、雰囲気はとても似ていましたが YAML の書き方は異なります。

事前準備

Nuxt.js はいつものように create-nuxt-app を使って Universal (SSR) で構成しました。Universal なので 実行環境には Node.js サーバーが必要になります。

Azure App Service は、Node.js(Linux) でプロビジョニングし、Nuxt.js 用の環境変数を App Setting に入れておきます。今回は GitHub Actions からのデプロイで Run From Package が使えるため、それも有効になるようにしておきました。

[
{
"name": "HOST",
"value": "0.0.0.0",
"slotSetting": false
},
{
"name": "NODE_ENV",
"value": "production",
"slotSetting": false
},
{
"name": "WEBSITE_RUN_FROM_PACKAGE",
"value": "1",
"slotSetting": false
}
]

また、Oryx ビルドの時と異なり、Node.js バージョンの制約がなかったので、 App Service で使える最新(執筆時点)の Node.js 12.9.0 をアプリケーションスタックに設定しました。

スタートアップコマンドは空白のままでも npm run start が定義されていれば問題ないはずなのですが、最近の create-nuxt-app で生成されるコマンドそのままだと cross-env が App Service でうまく実行できない問題があったので、以下のようにエントリーポイントを直接呼ぶように設定しました。

General Settings

GitHub Actions でのビルド

GitHub Actions で Nuxt のビルドとデプロイを定義するにあたって、特に以下の2点を意識するようにしました。

  • App Service にデプロイするファイルは npm run start で必要なファイル、ディレクトリのみとする
  • node_modules は開発用の依存関係を除いた構成とする

YAML では job を builddeploy に分けて作成します。まず build では、以下のように最終的にデプロイに必要なファイルが artifact にアップロードされるようにしました。

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master

- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}

- name: npm install, build
run: |
npm ci
npm run build

- name: copy publish files
run: |
mkdir publish
cp -r .nuxt server static nuxt.config.js package*.json publish/

- name: Upload publish files
uses: actions/upload-artifact@v1
with:
name: artifact
path: publish

Upload publish files のステップで、ビルドに使用したフル構成の node_modules は含めないようにするのがポイントです。これを入れてしまうと、アップロードタスクの時間が数分では終わらないレベルになってしまいますし、そもそも devDependencies のモジュールは入れたくありません。なお、本来的には actions/upload-artifact@v1path で複数ファイルやディレクトリが指定できれば1つ前のコピータスクが不要でしたが、まだ Issue 段階でしたのでやむを得ずこんな処理になりました。

GitHub Actions でのデプロイ

次の deploy ジョブでは以下の流れでワークフローを組みました。

  1. artifact からデプロイ用ファイルを復元
  2. production 用の node_modules をインストール
  3. Azure にログイン(Service Principal 利用)
  4. App Service へデプロイ

YAML は以下のような感じになりました。

jobs:
build:
# (途中省略)

deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build
steps:
- name: Download publish files
uses: actions/download-artifact@v1
with:
name: artifact

- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}

- name: npm ci for production
run: |
cd artifact
npm ci --production

- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: 'Deploy to Azure WebApp'
uses: azure/webapps-deploy@v1
with:
app-name: 'nuxt-ci'
slot-name: 'gh'
package: 'artifact'

まず、ダウンロードした artifact ディレクトリ上で production 用の node_modules を復元するために npm ci --production を実行します。これをやりたいために artifact を使ったと言ってもよいでしょう。これで production 用のファイル一式が準備できたので、あとは Azure にデプロイするだけです。

Azure へのデプロイで使っている azure/webapps-deploy@v1 アクションでは、 package にディレクトリを指定すると自動的に ZIP でデプロイしてくれます(Run From Package にも対応)。なお、ドキュメントには記載がありませんでしたが、 slot-name でスロットも指定できました。

GitHub と Azure の接続に関しては、 webapps-deploy/README.md at master · Azure/webapps-deploy · GitHub に記載の Service Principal を使った CREDENTIAL の登録手順で特に問題はありませんでした。

Oryx build に比べると、少しやることが多くなりますが、とてもスッキリした形でデプロイできるのと、GitHub Actions なので、テストなどのアクションを適宜追加してワークフローを拡張することができますね。ちなみに、 Azure Pipelines だとユーティリティ的なアクションが充実していることもあり、全体的にもう少しエレガントに YAML が書けました。