ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

Azure Pipelines で Azure Web Apps のパイプラインを構築する

今年の5月に、Azure DevOps の Build と Release のパイプラインに代わって、Multi-Stage Pipelines で構築できるようになる発表がありました。
devblogs.microsoft.com
現時点では、Multi-Stage Pipelines はプレビュー機能ですが、従来の Build と Release のパイプラインは Classic 扱いとなっているので、そろそろ触っておかねばと思い、試してみました。

前準備

Azure DevOps の右上のユーザーアイコンから、Preview features で Multi-Stage Pipelines を有効にします。

f:id:TonyTonyKun:20191219143946p:plain

あとは、デプロイ先の Azure Web Apps のサブスクリプション情報を service connection として登録しておきます。

パイプラインの全体設計

今回は、リポジトリのブランチとデプロイ先の関係が分かりやすい GitLab flow を想定しています。
パイプラインは、自動デプロイされる開発環境向け(Commit Stage)と承認デプロイされる本番環境向け(Production Stage)を作ります。

f:id:TonyTonyKun:20191219144724p:plain

YAML を Build と Release を分けて、可変部をパラメータで切り替えできるテンプレートを作ることで再利用性を向上させます。

f:id:TonyTonyKun:20191220105519p:plain

開発環境向けパイプライン

まずは、パイプライン起動用の azure-pipelines.yml です。

trigger:
- master

variables:
  imageName: 'windows-2019'
  buildConfiguration: 'Release'
  projects: '**/WebApplication.csproj'
  testProjects: '**/WebApplication.Tests.csproj'
  azureSubscription: 'AzureSponsorships'
  webAppsName: '<Web Apps Name>'
  webAppsType: 'webApp'
  environment: 'Commit-Stage'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      buildConfiguration: $(buildConfiguration)
      projects: $(projects)
      testProjects: $(testProjects)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      webAppsName: $(webAppsName)
      webAppsType: $(webAppsType)
      environment: $(environment)

ポイントは、template で Build と Release のYAMLを指定している箇所です。condition で、Build ステージが成功した場合のみ、Release ステージを実行する条件指定も忘れずに設定します。
リリースの JOB は、Environments を使います。
開発環境向けパイプラインでは、自動デプロイしたいので、空の Environments を Commit-Stage で登録しておきます。

f:id:TonyTonyKun:20191219144756p:plain

次に、ビルド用の build-pipelines.yml です。

parameters:
  imageName: ''
  buildConfiguration: ''
  projects: ''
  testProjects: ''

jobs:
- job: Build
  pool:
    vmImage: ${{parameters.imageName}}
  steps:
  - task: DotNetCoreCLI@2
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: ${{parameters.projects}}
      arguments: '--configuration ${{parameters.buildConfiguration}}'
  - task: DotNetCoreCLI@2
    displayName: 'Test'
    inputs:
      command: 'test'
      projects: ${{parameters.testProjects}}
      arguments: '--configuration ${{parameters.buildConfiguration}}'
  - task: DotNetCoreCLI@2
    displayName: 'Publish'
    inputs:
      command: 'publish'
      publishWebProjects: true
      arguments: '--configuration ${{parameters.buildConfiguration}} --output $(System.DefaultWorkingDirectory)/publish'
      zipAfterPublish: true
  - publish: publish
    displayName: 'Publish artifact'
    artifact: webapp

.NET Core CLI taskDotNetCoreCLI@2 を使って、ビルド→テスト→発行を行っています。

最後に、リリース用の release-pipelines.yml です。

parameters:
  imageName: ''
  azureSubscription: ''
  webAppsName: ''
  webAppsType: ''
  webAppsSlotName: 'production'
  environment: ''
  logLevel: 'Information'

jobs:
- deployment: Deploy_WebApps
  displayName: 'Release'
  pool:
    vmImage: ${{parameters.imageName}}
  environment: ${{parameters.environment}}
  strategy:
    runOnce:
      deploy:
        steps:
        - task: AzureWebApp@1
          displayName: 'Deploy to Azure Web Apps'
          inputs:
            azureSubscription: ${{parameters.azureSubscription}}
            appType: ${{parameters.webAppsType}}
            appName: ${{parameters.webAppsName}}
            slotName: ${{parameters.webAppsSlotName}}
            package: '$(Pipeline.Workspace)/**/*.zip'
            deploymentMethod: runFromPackage
            appSettings: -Logging:LogLevel:Default ${{parameters.logLevel}}

Azure Web App taskAzureWebApp@1 を使って、デプロイを行っています。
Azure Web Apps の Deployment Slot 機能にも対応したいので、slotName を指定しますが、必須にはしたくないので、初期値で production を設定しています。
appSettings では、アプリケーション設定のログレベルを設定しています。Azure CLI の az webapp config コマンドでも設定できますが、リソースグループ名が必要になってしまうので、ここで指定しました。

本番環境向けパイプライン

パイプライン起動用の azure-pipelines-production.yml です。

trigger:
- production

variables:
  imageName: 'windows-2019'
  buildConfiguration: 'Release'
  projects: '**/WebApplication.csproj'
  testProjects: '**/WebApplication.Tests.csproj'
  azureSubscription: 'AzureSponsorships'
  webAppsName: '<Web Apps Name>'
  webAppsType: 'webApp'
  environment: 'Production-Stage'
  webAppsSlotName: 'staging'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      buildConfiguration: $(buildConfiguration)
      projects: $(projects)
      testProjects: $(testProjects)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/production'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      webAppsName: $(webAppsName)
      webAppsType: $(webAppsType)
      environment: $(environment)
      webAppsSlotName: $(webAppsSlotName)

トリガーとなるブランチが production となっている箇所が開発環境向けパイプラインとの違いです。
本番環境では、Azure Web Apps の Deployment Slot 機能を利用したいので、staging を設定しています。また、承認デプロイもしたいので、承認設定した Environments を Production-Stage で登録しておきます。

f:id:TonyTonyKun:20191219144048p:plain

結果確認

リポジトリの master ブランチへの更新がトリガーとなり、ビルドとリリースのパイプラインが実行され、Azure Web Apps へのデプロイまでが自動で実行されます。

f:id:TonyTonyKun:20191219145206p:plain

master ブランチから production ブランチへプルリクエストのマージがトリガーとなり、ビルドとリリースのパイプラインが実行されます。Azure Web Apps へのデプロイは承認が必須となっているので、Waiting になっていることが分かります。

f:id:TonyTonyKun:20191219144408p:plain

この後、リリースを承認すると、Deployment Slot の staging へデプロイされます。最終動作確認後、Azure Portal などからの Swap することを想定しています。万が一、リリースによる障害が発生した場合には、旧バージョンに切り戻すこともできます。

まとめ

Azure DevOps の Multi-Stage Pipelines を使って、Azure Web Apps 向けのパイプラインを構築してみました。
可変部をパラメータで切り替えできるようにテンプレート化しているので、これからの CI/CD が捗りそうです。
こちらから、パイプライン YAML の全体を確認できます。
github.com

追記

今回のユースケースですと、YAML をソースコードのリポジトリで管理する想定なので、環境毎に異なる情報やパスワードなどのシークレットな情報をリポジトリに含めたくありません。
Azure DevOps の Variable groups を使って、パイプラインを書き換えてみましたので、こちらを参照してください。
gooner.hateblo.jp