しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure App Service と GitHub Actions を使って Pull Request のプレビュー環境を自動で作成する

Azure Static Web Apps がリリースされた時に羨ましかった機能として、Pull Request を作成する度にプレビュー用の環境を自動で作成してくれる、というものがありました。レビュー中に実際のアプリケーションを確認できるのは重要なので、App Service でも欲しいと思っていました。

App Service にも Deployment slot という機能があるので、これを使えばいい感じに Pull Request が作成されたタイミングでデプロイまで出来るのではと考えていたので、そろそろ試してみることにしました。

Pull Request が作成された時に Deployment slot の作成とデプロイを行い、Pull Request が閉じられた時に Deployment slot を削除すれば、理論上は 20 個まで同時にプレビュー環境を作成出来るはずです。

実のところ、同じ仕組みは App Service Team Blog で既に運用されているので、もはや目新しいことでもないのですが、最近リリースされた GitHub Actions の Environments と組み合わせてみたかったのです。

早速 GitHub Actions の Workflow をいろいろと出していくので、まずはベースとなる部分を載せておきます。Pull Request トリガーの設定が良く使われるものより多いです。

デプロイするアプリケーションは ASP.NET Core 3.1 で適当に作ったもので、App Service はこの例では Windows ですが Linux でも同じような考え方で実現できるはずです。

name: Staging

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches: [ main ]

env:
  DOTNET_VERSION: 3.1.x
  AZURE_WEB_APP_NAME: webapp-deploytest-1
  AZURE_RESOURCE_GROUP_NAME: deploytest-rg

jobs:
  build:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    - name: Use .NET Core ${{ env.DOTNET_VERSION }}
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: ${{ env.DOTNET_VERSION }}

    - name: Publish projects
      run: dotnet publish -c Release

    - name: Upload artifact
      uses: actions/upload-artifact@v2
      with:
        name: webapp
        path: WebApplication1/*.zip

ビルドしたアプリケーションは自動的に zip にするように設定しておくと CI / CD を構築する上で便利です。MSBuild の定義を置いておくだけで実現できます。方法は以下のエントリを参照してください。

作成した zip は一旦 Artifact としてアップロードしておきます。今回のように Job が分かれている時には前に Job のビルド結果は保持されないはずなので、アップロードしておかないと後続 Job で使えません。

PR オープン時に Deployment slot の作成とデプロイ

Pull Request のオープン時にはビルドが必要なので、先に build Job を実行して完了後に Deployment slot の作成とデプロイを行うようにします。

Deployment slot の作成には Azure CLI を使います。このコマンドは同じパラメータで複数回実行してもエラーとならないので、こういうケースの際に使い勝手が良いです。

基本的な構成は元々の App Service と同じにしておきたいので --configuration-source を明示的に指定します。Deployment slot の作成は同期処理なので、コマンドが終了した時点でデプロイが可能な状態です。

従って処理としては通常のデプロイに Deployment slot の作成を挟んだだけです。

deploy:
  runs-on: ubuntu-latest
  environment:
    name: staging
    url: https://${{ env.AZURE_WEB_APP_NAME }}-pr-${{ github.event.number }}.azurewebsites.net
  needs: build
  steps:
  - name: Download artifact
    uses: actions/download-artifact@v2
    with:
      name: webapp

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

  - name: Create staging slot
    run: az webapp deployment slot create -n ${{ env.AZURE_WEB_APP_NAME }} -g ${{ env.AZURE_RESOURCE_GROUP_NAME }} -s pr-${{ github.event.number }} --configuration-source ${{ env.AZURE_WEB_APP_NAME }}

  - name: Deploy staging
    uses: azure/webapps-deploy@v2
    with:
      app-name: ${{ env.AZURE_WEB_APP_NAME }}
      slot-name: pr-${{ github.event.number }}
      package: ./WebApplication1.zip

Job には environment を指定して、デプロイの履歴を GitHub 側でも確認出来るようにしています。ここで URL を付けておくと、GitHub から簡単に飛べるようになるので結構便利です。

Environments については以前にまとめたので以下のエントリを参照してください。

ここまでの Job で PR が作成されたタイミングでプレビュー用の Deployment slot が作成された後に、アプリケーションがデプロイされて確認出来るようになります。

PR クローズ時に Deployment slot を削除

プレビュー環境は役目を終えた後には削除される必要があるので、PR が閉じられた時に Deployment slot を削除する Job を追加します。削除も Azure CLI で行うだけなので簡単です。

Job の条件だけが変わっていますが、それ以外は普通に Azure CLI を使っているだけです。

delete-slot:
  if: github.event_name == 'pull_request' && github.event.action == 'closed'
  runs-on: ubuntu-latest
  steps:
  - name: Login to Azure
    uses: azure/login@v1
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}

  - name: Delete staging slot
    run: az webapp deployment slot delete -n ${{ env.AZURE_WEB_APP_NAME }} -g ${{ env.AZURE_RESOURCE_GROUP_NAME }} -s pr-${{ github.event.number }}

特に説明は必要ないぐらいのシンプルな Job です。これが PR が閉じられた時に実行されます。

動作確認を行う

適当に Pull Request を作成して動作確認をします。Pull Request を作成すると通常通り GitHub Action によってビルドが行われますが、Environments を使っているのでデプロイステータスも同時に表示されます。

以下は実際の例ですが、ステータスチェック以外にもデプロイの情報も表示されています。

f:id:shiba-yan:20201218213332p:plain

Environments で URL を同時に指定したので "View Deployment" からプレビュー環境へ飛ぶことが出来ます。

実際にプレビュー環境を表示してみると、Deployment slot が作成されてデプロイまで行われていることが確認できます。ちゃんと Pull Request で行った変更が確認できています。

f:id:shiba-yan:20201218213419p:plain

複数の Pull Request を作成すると、それぞれ別の Deployment slot が作成されていることが確認できます。

少し残念だったのが Environments が複数同時にデプロイ済み状態に出来ないので、PR にデプロイステータスが表示されるのは常に 1 つだけという点でした。このままだとプレビュー環境の URL が失われてしまうので、今はコメントに同時に書き込むしかなさそうです。

f:id:shiba-yan:20201219015227p:plain

PR をマージなどして閉じると、Deployment slot を削除する Job が実行されます。

f:id:shiba-yan:20201218224732p:plain

これで一通りのプレビュー環境を作成する Workflow の作成と検証が出来ました。Environments だけ少し残念な点が残りましたが、それ以外はかなり実用的だなと感じました。

実際の環境に組み込んでフローを作る場合は、プレビュー環境はどのアプリケーション設定を参照するべきなのか考慮する必要があります。本番とステージングそれぞれとの関係も整理した方が良いです。