Q. 「オンボーディングで最もしんどかった部分はどこでしたか?」
\ Azure App Service 一択ウ〜!/
Azure サービス群の中でも屈指の面倒臭さを誇る(当社比)Azure App Service でのデプロイ方法ガイド、始まり始まり。
使用技術
- Node.js
- Express.js
- Visual Studio Code
- GitLens(Visual Studio Code の拡張機能)
- Azure App Service
- GitHub
- GitHub Actions
この記事を読んでわかること
Azure App Service に GitHub Actions を使用してアプリケーションを継続的にデプロイするための具体的な手順。
つまり「main ブランチへ push するたび自動的に Azure App Service へデプロイする」ことができるようになります。
この記事では説明しないこと
- サブスクリプション、リソースグループ、リソースなど Azure 特有の用語の数々。詳しく知りたい方はこちらをまず読んでみてください。
- Node.js や GitHub など使用技術に関する基礎知識
大まかな手順の流れ
- デプロイしたいものを用意する
- リソースグループを作成する
- Azure App Service リソースを作成する
- GitHub Actions が使えるよう準備する
- CI/CD 経由でデプロイ
1. デプロイしたいものを用意
Node.js 製であればなんでも OK。基本の流れは同じです。
今回は Express.js でささっと用意しちゃいます。
今回は「2-Deploy-app-to-azure」の中身だけ使用します。他は消しちゃって OK。
2. Azure Portal からリソースグループを作成
アカウント作成・サブスクリプションの設定についての説明・手順は省きます。
まだの方は Azure 公式サイトより無料アカウントを作成し Azure Portal へアクセス・ログインしてサブスクリプションの用意まで行ってください。
Azure Portal からリソースグループを作成していきます。
無事作成されれば成功。
3. Azure App Service リソースの作成
2で作成したリソースグループの中に Azure App Service リソースを作っていきます。
まずはプランの作成から。
Azure Service プランを選択し「作成」をクリック。
今回はさくっと試したいだけなので価格レベルを Free F1 へ変更。ここを変更しておかないと月額課金が発生するため注意!
「確認および作成」→「作成」でリソース作成が完了します。作成まで少し時間がかかることもあるのでコーヒーでも啜りながら待ちましょう。
完了したら App Service で Web アプリのリソースを作っていきます。
「確認および作成」→「作成」で完了。こちらも時間がかかるためコーヒーをひと啜りしながら待ちましょう。完了後リソースに移動するとこんな感じ。
4. GitHub Actions の準備をしよう
まずは GitHub にレポジトリを作成しましょう。詳細な手順は省きます。GitHub アカウントを持っていない方は
GitHub 公式サイトでアカウントを作成・ログインしてください。
レポジトリの作成が完了したらこんな感じのページが広がっているはず。URL をコピーしておきます。
1で開いていた Visual Studio Code へ戻ります。リモート先を自身で作成したレポジトリへ変更しちゃいましょう。
※ REMOTES なんて見当たらないよ!という人は拡張機能「GitLens」を導入してください。
name: origin
url: コピーした URL を貼り付け
完了したらさっそくコミット&ブランチの発行を試してみてください。最新のものに反映されていれば成功。
ここからが本番。 GitHub Actions 経由でデプロイする準備を進めていきます。
3で作成した App Service へ移動し、GitHub Actions と連携させていきます。
保存するとあら便利、 GitHub 側で勝手に設定ファイルを生成してくれます。
5. CI/CD 経由でデプロイ
さっそく GitHub Actions 経由でデプロイしてみましょう。
やたら長ったらしい名前の .github/workflows/main_fwywdazuredeployintro.yml
ファイルを deploy.yml
へ名称変更しプッシュしてみます。
deploy.yml のデフォルト設定では「main ブランチに push した際にデプロイが走る」ようになっています。デプロイが終わったらさっそく確認しにいきましょう。
おや・・・ Azure App Service くんのようすが・・・?
出たよ。親の顔より見た光景。
アプリケーション起動時に出るエラー。Azure App Service 触ったことがある人は絶対に見たことあるやつです。私はもはや :( を見ると首の後ろがキュッ・・・となります。
:( Application Error に遭遇した時の対処法
基本は以下のステップで地道にエラーを潰していく形になります。
- ログを確認する
- 原因っぽい箇所を探して試行錯誤する
1. ログを確認する
Azure Portal で 該当する Azure App Service を開き「問題の診断と解決」を覗いてみましょう。下部にある「ウェブアプリダウン」をクリック。
現在7つの問題を抱えている模様。詳しく見ていきます。
2. 原因っぽい箇所を探して試行錯誤する
7つの問題のうち直接アプリケーションエラーを引き起こしていそうなものを探しましょう。
下から4つは「有料プランにアップグレードしてほしいな❤️と言っているだけ」「警告で起動失敗に関するものではなさそう」っぽいことが読み取れます。上の3つがアプリケーションエラーに関わっていそうです。さらに分析するとサーバーエラーは起動に失敗しているがために出ているものと予想されます。ここから「コンテナクラッシュ・コンテナの問題を解決すれば起動できそう!」と算段がつけられますね。
一番怪しげな「コンテナクラッシュ」から見ていくと・・・
原因がひとつ判明。アプリをスタートさせるためのコマンドが設定されていないようです。さっそく設定しにいきましょう。左バーの設定内にある構成をクリックし全般設定を覗いてみます。
確かにスタートアップコマンドが空っぽになってますね。ではここに npm start
ないし yarn start
をいれよう!と通常はなるわけですが、入れても動かないため注意。直接 node コマンドを叩きます。package.json
の start スクリプトを確認してみましょう。
"start": "cross-env-shell DEBUG=express:* node index.js"
となってますね。cross-env-shell DEBUG=express:*
はそれぞれ環境変数・デバッグに関するコマンドなので省略。node index.js
だけ抜き出して入力・保存します。
「再起動しますか?」と聞かれるため了承して保存完了です。
ここで一度アプリを覗いてみましょう。
以前としてアプリケーションエラーは出たまま。まだ対処していないエラーが残っているからですね。もう一つの問題だった「コンテナの問題」を見てみます。
どうやらコンテナがタイムアウトしたせいで起動できていないみたい・・・というのが見て取れます。説明が若干分かり難いのですが、「アプリケーションの設定でWEBSITES_CONTAINER_START_TIME_LIMIT
の設定時間を伸ばしてみてね」ということらしいです。さっそく変更しにいきます。
保存するとアプリケーションが再起動されます。これで正常に起動できるはずッ・・・
で、できた〜〜〜!!!(クソデカボイス)
おつかれさまでした・・・!
トラブルガイド
最後に Next.js で作ったアプリケーションと NestJS で作った API をそれぞれデプロイした時に遭遇したエラーと対処法をご紹介。
同じ症状に苦しんでいるあなたに届きますように・・・!
デプロイに時間がかかりすぎる
アプリケーションの容量が小さくない場合、デプロイまでに凄まじい時間がかかります。私の場合 Next.js で作った簡単な Todo アプリをデプロイするのに50分かかりました。50分待った挙句デプロイに失敗した日には泡吹いて倒れそうになります・・・(実体験)。
対処法として deploy.yml
ファイルの中身をいじってアプリを zip 化→ GitHub Actions にアップロード→ zip を Azure App Service へアップロードするよう変更することが挙げられます。私はこれでデプロイ時間を7分まで短縮できました。恐るべきビフォーアフター!
参考に Next.js をデプロイした時のコードの中身を載せておきます。
name: Build and deploy Node.js app to Azure Web App - fwywdTodoAppApi
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
steps:
- uses: actions/checkout@v2
- name: Set up Node.js version
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: yarn install, build, and test
run: |
yarn install
yarn build
yarn test-setup
yarn test:unit
yarn test:integration
- name: delete node_modules dir
run: rm -rf node_modules
- name: Zip all files for upload between jobs
run: zip --symlinks -r next.zip ./*
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v2
with:
name: node-app
path: next.zip
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v2
with:
name: node-app
- name: yarn install production
run: yarn install --production
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'fwywdtodoapp'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_630E8D07124C436791B1A377E09DC005}}
package: next.zip
- name: Delete zip file
run: rm next.zip
デプロイが途中で止まる
こちらもアプリケーションの容量がそこそこある時に発生。node_modules
を npm install
または yarn install
する際ダウンロードに時間がかかりすぎてタイムアウトするパターンです。プロジェクトフォルダ直下に.yarnrc
ファイルを作って以下を記述することにより回避可能。
network-timeout 600000
スタートアップコマンドに関するエラー
使用するフレームワークによってはデフォルトのスタートアップコマンドが効かないことがあります。私の場合は Azure App Service 内の構成→全般設定→スタートアップコマンドに以下を入力することで解決しました。参考にどうぞ。
Next.js の場合・・・node_modules/next/dist/bin/next start
NestJS の場合・・・node dist/main
何が原因でエラーが起きているのかわからないとき
「アプリケーションエラーが起きてる。問題の診断と解決を見たけどそれっぽいエラーが見つからない。なんでッ!?」
これもよく遭遇します。そんな時はログストリームをみるべし。
アプリを再起動→ログストリーム(プレビュー)でリアルタイムにログ出力ができるためどこで失敗しているのかわかりやすくなります。
番外編: NestJS デプロイで詰まった時の解決策(体験談)
状況としては GitHub Actions で zip 形式でデプロイしただけの状態で Azure Portal で何も設定していない・・・という時間軸です。
経緯まで書くと大ボリュームになるので結論だけ置いときます。
deploy.yml
の中身はこちら(デプロイ時間が短縮できます)。
name: Build and deploy Node.js app to Azure Web App - fwywdAzureDeployNest
on:
push:
branches:
- master
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js version
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: npm install, build, and test
run: |
npm install
npm run build --if-present
npm run test --if-present
- name: Zip all files for upload between jobs
run: zip --symlinks -r nest.zip ./*
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v2
with:
name: node-app
path: nest.zip
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v2
with:
name: node-app
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'fwywdAzureDeployNest'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_2A62A5035C4241D78AC3A13D8D636DCA }}
package: nest.zip
- name: Delete zip file
run: rm nest.zip
- 「Azure App Service 内の構成→全般設定→スタートアップコマンド に
node dist/main
と入力する
- 「Azure App Service 内の構成→ アプリケーションの設定→ 新しいアプリケーション設定」で「名前:WEBSITES_CONTAINER_START_TIME_LIMIT」「値:600」と設定
package.json
の script 内にあるコマンド “start:prod”
を以下に置き換える
"start:prod": "yarn build && node dist/main",
- main.ts のポート設定を変更する
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const port = process.env.PORT || 3000;
await app.listen(port);
}
bootstrap();
この3つを抑えていれば Azure App Service も怖くない(多分)
- スタートアップコマンドが適切に設定されているか
- コンテナがタイムアウトしていないか
- その他、なんらかの理由でコンテナがクラッシュしていないか(ログストリームで確認可能)
特に上2つは Node.js でのデプロイだと再現性高く目にするエラー(当社比)なので真っ先に確認してみる価値アリ。
この記事で Azure App Service アレルギーに罹っている人が1人でも減りますように・・・!