Azure Managed DevOps Pools(MDP)を構成して利用する
はじめに
Azure DevOpsを利用してCICDを行う上で、Azure Pipelinesは欠かせません。これまで、Pipelinesを実行するエージェントとして、以下の選択肢がありました。
- Microsoft Hosted Agents
- Self Hosted Agents (オンプレミスや独自管理の環境)
- VMSS (Virtual Machine Scale Set)を利用したスケーリング
2024年11月18日、これらに新たな選択肢として「Azure Managed DevOps Pools」が正式に一般提供(General Availability: GA)されました。これにより、Azure側で管理・スケールが容易に行えるエージェントプールが新たに加わり、パフォーマンス・スケーラビリティ・コスト最適化などの観点で、これまで難しかった運用がより簡易かつ柔軟になりました。
本記事では、Azure Managed DevOps Poolsを利用したパイプライン構成の例や、その導入によるパフォーマンス改善効果、また運用上のポイントなどをまとめます。
Azure Managed DevOps Poolsとは
Azure Managed DevOps Pools(以下、MDP)は、Azureによってホスト・管理される新しいエージェントプールです。イメージとしては、従来Azure DevOpsで利用できたVMSS(Virtual Machine Scale Set)ベースのエージェントプールをさらに進化させた、Microsoftがフルマネージドで提供する新しいエージェントプールサービスという感じです。従来のVMSSプールがユーザー自身のAzureサブスクリプション内にエージェント用のVMリソースを展開していたのに対し、MDPではMicrosoft側のAzureサブスクリプション上でエージェントが管理・運用されるため、カスタムプールの構成やスケール調整が大幅に簡略化され、拡張性や信頼性の向上が期待できます。
MDPを利用することで、以下のような利点を得られます。
- 簡易な設定と拡張性:わずかな手順でプールを作成でき、VMサイズやイメージを選択することで、エージェントのパフォーマンスを柔軟に調整可能です。
- 最新のMicrosoft公式イメージ利用:Azure Pipeline Hosted Agentsで使われる最新イメージや、標準的なAzure VMイメージ、さらにはユーザー独自のAzure Image Gallery登録イメージを活用できます。
- ネットワーク統合:エージェントを自分のAzureネットワークに直接参加させることが可能となり、プライベートリソースへのセキュアなアクセスが可能になります。これにより、従来はSelf-hostedエージェントが必要だったシナリオでも、ホステッドエージェントに近い運用が実現できます。
- コストの柔軟管理:事前に待機するスタンバイエージェント数や稼働時間をスケジュールで制御でき、必要最低限の状態で維持し、ジョブが来た際にオンデマンドで拡張することで、コストを最小限に抑えられます。
MDPのアーキテクチャ
Microsoft Hosted Agentsからの移行メリット
これまで多くの開発チームは、初期設定の容易さと手軽さからMicrosoft Hosted Agentsを選んできました。しかし、ある程度の規模や要求に達すると、ビルドの待ち時間や環境の再構築にかかるムダが目立ちはじめます。ここで、Azure Managed DevOps Pools(MDP)に切り替えることで、これらの課題を解消し、開発者体験の向上を実現できます。
たとえば、4つのステージをもつマルチステージパイプラインを運用していたチームが、MDPに移行した事例を考えてみましょう。初回のパイプライン実行では、依存ライブラリやツールセットのダウンロード、環境セットアップに時間がかかるため、Microsoft Hosted Agents時代と同様に一定の待ち時間が発生します。しかし、2回目以降は事情が一変します。一度確立されたキャッシュや環境が再利用されるため、後続で記載する例のパイプライン(データベースデプロイ+静的コード解析+ビルド+UT実行+コンテナプッシュ)を例とした場合、1/6の時間で全ての処理を完了しました。開発者は「ビルドが終わるのを待っている間に、ほかのタスクをする」という古いワークフローから解放され、本来のコーディングや問題解決に集中できることになります。
さらに、MDPはAzure側でスケールアウト・スケールインのバックエンド管理を行うため、ワーカーVMを必要なときだけ起動・停止することが可能となり、これに伴い、無用なコストを削減し、ピーク時にはしっかりとパフォーマンスを確保するなど、コストパフォーマンス面でも高い柔軟性を持ちます。
要するに、MDPは「とにかくビルドを走らせる」から「最適な時間と速度でビルドする」への移行を可能にし、チーム全体の効率と満足度を底上げしてくれます。従来のMicrosoft Hosted Agentsに慣れたチームであっても、その一歩先の洗練されたCI/CD体験へスムーズに踏み出せるのです。
運用上の注意点
-
初回は遅い
キャッシュがない初回ビルドは、Microsoft Hosted Agentsと同様にすべての環境準備が必要となるため、ビルド時間が長くなります。 -
常時起動はコスト増
エージェントを起動したままにすると、その待機時間にもコストがかかります。稼働が集中する時間帯だけエージェント数を増やし、オフピークは最小限またはゼロに抑えるなど、スケジューリングでコスト最適化を図る必要があります。 -
VMサイズ選択とクォータ管理
任意のサイズのVMを選べるため、高性能なビルド環境の提供が可能ですが、Azureリソースのクォータ制限により、事前にクォータ引き上げ手続きが必要な場合があります。事前計画やクォータ監視は必要です。 -
マルチテナント非対応
現時点では、MDPはAzure DevOpsと同一テナント内でしか利用できません。他のテナントからの利用や統合は難しいため、アーキテクチャ設計時にはこの制約を考慮する必要があります。
スケジューリングによるコスト効率化
MDPを使う際には、開発者がコードをPushする時間帯があらかじめ予測できるケースがあります。その場合、エージェントの起動をスケジューリングすることで、無駄な待機時間とコストを削減可能です。
たとえば、「月~金の09:00-18:00はエージェント起動、それ以外は停止」といったスケジュールを組むことで、ビジネスアワーに合わせた最適化が可能です。
スケジュールされた時間に合わせてキャッシュ済みの環境を用意しておくことで、開発者がプッシュした際には高速なビルドが実現できます。
MDPを利用したPipelineの例
以下は、MDPを使用したマルチステージパイプラインの一例です。poolセクションに注目してください。同じプール(mdp-demo-osa-001
)から複数の登録済みイメージ(windows-2022やubuntu-22.04)をdemands
で指定している点がポイントです。
trigger:
branches:
include:
- main
paths:
include:
- database/*
- src/*
variables:
- name: buildConfiguration
value: 'Release'
- name: dotnetSdkVersion
value: '8.x'
- name: testProjects
value: '**/*.Tests/*.csproj'
- group: DatabaseRelease
stages:
# Database
- stage: 'Database'
pool:
name: mdp-demo-osa-001
demands:
- ImageOverride -equals windows-2022
jobs:
- job: DatabaseJob
displayName: 'Database Job'
steps:
- task: VSBuild@1
inputs:
solution: 'database/*.sln'
msbuildArgs: '/p:Configuration=Release'
platform: 'Any CPU'
- task: CopyFiles@2
inputs:
SourceFolder: '$(System.DefaultWorkingDirectory)/database/EventTrackerDB/bin/Release'
Contents: '**/*.dacpac'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'drop'
publishLocation: 'Container'
- task: SqlAzureDacpacDeployment@1
inputs:
azureSubscription: $(SERVICE_CONNECTION_NAME)
ServerName: '$(EventAttendeesApp--SqlServerName)'
DatabaseName: '$(EventAttendeesApp--DatabaseName)'
SqlUsername: '$(EventAttendeesApp--SqlUsername)'
SqlPassword: '$(EventAttendeesApp--SqlPassword)'
DacpacFile: '$(Build.ArtifactStagingDirectory)/*.dacpac'
# Analyze
- stage: 'Analyze'
dependsOn: Database
condition: succeeded()
pool:
name: mdp-demo-osa-001
demands:
- ImageOverride -equals ubuntu-22.04
jobs:
- job: AnalyzeJob
displayName: 'Analyze Job'
steps:
- task: UseDotNet@2
displayName: 'Use .NET SDK $(dotnetSdkVersion)'
inputs:
version: '$(dotnetSdkVersion)'
performMultiLevelLookup: true
includePreviewVersions: true
- task: MicrosoftSecurityDevOps@1
displayName: 'Microsoft Security DevOps'
inputs:
categories: 'secrets, code'
break: true
# Build&Test
- stage: 'BuildAndTest'
displayName: 'Unit test the application'
dependsOn: Analyze
condition: succeeded()
pool:
name: mdp-demo-osa-001
demands:
- ImageOverride -equals ubuntu-22.04
jobs:
- job: UnitTestJob
displayName: 'UnitTest Job'
steps:
- task: UseDotNet@2
displayName: 'Use .NET SDK $(dotnetSdkVersion)'
inputs:
version: '$(dotnetSdkVersion)'
performMultiLevelLookup: true
includePreviewVersions: true
- task: DotNetCoreCLI@2
displayName: 'Restore project dependencies'
inputs:
command: 'restore'
projects: $(testProjects)
- task: DotNetCoreCLI@2
displayName: 'Build the project - $(buildConfiguration)'
inputs:
command: 'build'
projects: $(testProjects)
arguments: '--no-restore --configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Execute unit test'
inputs:
command: test
projects: $(testProjects)
arguments: '--configuration $(buildConfiguration) --collect "Code coverage"'
publishTestResults: true
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage report'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
# Publish
- stage: DockerBuildAndPush
displayName: 'Build and Push Docker Image'
dependsOn: BuildAndTest
condition: succeeded()
pool:
name: mdp-demo-osa-001
demands:
- ImageOverride -equals ubuntu-22.04
jobs:
- job: BuildAndPushImage
displayName: 'Build and Push Docker Image Job'
steps:
- task: Docker@2
displayName: 'Build and Push Docker image'
inputs:
command: buildAndPush
repository: $(CONTAINER_IMAGE_NAME)
dockerfile: '$(Build.SourcesDirectory)/src/Dockerfile'
containerRegistry: $(CONTAINER_REGISTORY_SERVICE_CONNECTION_NAME)
tags: |
$(CONTAINER_IMAGE_TAG)
他記事からの参考点
参考として、Azure Viking氏の記事によると、MDPは単なる「エージェントのホスト先を変える」以上の価値を提供します。開発者体験の改善だけではなく、クラウドデプロイメントに革命をもたらすとまで謳われており、以下のような示唆が得られます。
- クラウドリソース管理の自動化とチューニング
- セキュアかつコンプライアンスに準拠したビルド環境の提供
- より高速なDevSecOpsパイプライン実現
また、特定ビルド環境の安定維持や、セキュリティルールの明確な適用が求められる組織では、MDPはより柔軟でコントロール可能な手法を提示します。
まとめ
Azure Managed DevOps Pools(MDP)は、これまでのMicrosoft Hosted Agentsを超えた、よりパフォーマンス、高度なカスタマイズ性、そしてスケールやコスト管理の自由度を提供するソリューションです。初回ビルドは時間を要するものの、後続ビルドでは大幅な時間短縮が期待でき、ビジネスアワーに合わせたスケジュール起動によりコスト最適化も可能です。
MDPはまだ進化中の領域ではありますが、すでにCI/CDパイプラインに大きな恩恵をもたらすことが見込まれます。ぜひ、環境要件やパフォーマンス改善ニーズのあるチームや組織で検討してみてください。
MDPの今後の展望は下記を参照ください。コンテナベースのエージェントや、マネージドデータディスクのサポート、パイプラインの開始が早くなる のような機能をリリース予定なので引き続き注目したいです。
Microsoftのチュートリアルが非常にわかりやすいため、今回は具体的な構築手順は記載しません。リファレンスを参考に試してみてください。また、kkamegawaさんがAzure Managed DevOps Poolsの紹介をされているTFSUGの動画が上がっています。パブリックプレビュー段階の見解ですが、本記事で述べていない知見が得られますので、是非参考にしてみてください。
リファレンス
Discussion