今年の5月に、Azure DevOps の Build と Release のパイプラインに代わって、Multi-Stage Pipelines で構築できるようになる発表がありました。
devblogs.microsoft.com
現時点では、Multi-Stage Pipelines はプレビュー機能ですが、従来の Build と Release のパイプラインは Classic 扱いとなっているので、そろそろ触っておかねばと思い、試してみました。
前準備
Azure DevOps の右上のユーザーアイコンから、Preview features で Multi-Stage Pipelines を有効にします。
あとは、デプロイ先の Azure Web Apps のサブスクリプション情報を service connection として登録しておきます。
パイプラインの全体設計
今回は、リポジトリのブランチとデプロイ先の関係が分かりやすい GitLab flow を想定しています。
パイプラインは、自動デプロイされる開発環境向け(Commit Stage)と承認デプロイされる本番環境向け(Production Stage)を作ります。
YAML を Build と Release を分けて、可変部をパラメータで切り替えできるテンプレートを作ることで再利用性を向上させます。
開発環境向けパイプライン
まずは、パイプライン起動用の 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
で登録しておきます。
次に、ビルド用の 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 task の DotNetCoreCLI@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 task の AzureWebApp@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
で登録しておきます。
結果確認
リポジトリの master ブランチへの更新がトリガーとなり、ビルドとリリースのパイプラインが実行され、Azure Web Apps へのデプロイまでが自動で実行されます。
master ブランチから production ブランチへプルリクエストのマージがトリガーとなり、ビルドとリリースのパイプラインが実行されます。Azure Web Apps へのデプロイは承認が必須となっているので、Waiting になっていることが分かります。
この後、リリースを承認すると、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