Azure DevOps
CI/CDツール
Azure DevOps
概要
Azure DevOpsはMicrosoftの包括的なDevOpsプラットフォームです。CI/CD、プロジェクト管理、Git、アーティファクト管理を統合し、YAMLパイプラインによる現代的なワークフロー管理を提供します。
詳細
Azure DevOps(アジュール デブオプス)は、Microsoftが提供する包括的なDevOpsプラットフォームとして、2018年にVisual Studio Team Servicesから進化したクラウドサービスです。CI/CD、ソースコード管理、プロジェクト管理、アーティファクト管理、テスト管理を単一のプラットフォームで統合し、開発からデプロイまでの全工程をサポートします。2024-2025年には、マルチステージYAMLパイプラインが主要機能となり、ビルド、テスト、デプロイフェーズを複数ステージに分割する高度なCI/CDワークフローを実現しています。YAMLベースのパイプラインにより、コードファーストでの設定・管理が可能で、バージョン管理、再利用性、複数プロジェクト間での容易な管理を提供します。Azure Kubernetes Service(AKS)やAzure Container Appsとの連携により、マイクロサービスアーキテクチャへの対応を強化し、オンプレミスサーバーからクラウドネイティブプラットフォームまで統一したデプロイメントを実現します。Microsoftは過去数年間、セキュリティ向上に重点を置き、クラシックパイプラインよりもYAMLパイプラインの使用を推奨しています。
メリット・デメリット
メリット
- オールインワンプラットフォーム: CI/CD、Git、Issues、Artifacts統合
- 強力なYAMLサポート: コードファーストでの設定・バージョン管理
- マルチステージパイプライン: 複雑なワークフローの段階的実行
- Azureとの統合: ネイティブなMicrosoft Cloud連携
- エンタープライズ機能: セキュリティ、ガバナンス、スケーラビリティ
- クロスプラットフォーム: Linux、Windows、macOS対応
- 豊富なMarketplace: サードパーティ拡張機能の充実
- 無料プラン: 5ユーザーまで無料利用可能
デメリット
- Microsoft生態系依存: Azure以外での機能制限
- 学習コスト: 機能豊富な分、習得に時間が必要
- UI/UXの複雑さ: 多機能により操作が複雑
- 料金体系: 大規模チームでのライセンスコスト
- パフォーマンス: 大量データ処理時のレスポンス低下
- カスタマイズ制限: Jenkins等と比較した柔軟性の制約
- 競合ツール統合: Microsoft以外のツールとの統合課題
主要リンク
- Azure DevOps公式サイト
- Azure Pipelines Documentation
- Azure DevOps Labs
- Azure DevOps Roadmap
- Azure DevOps REST API
- Azure DevOps CLI
書き方の例
基本的なYAMLパイプライン
# azure-pipelines.yml
trigger:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
dotNetFramework: 'net8.0'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
displayName: 'Build Job'
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
packageType: sdk
version: '8.x'
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration $(buildConfiguration) --collect "Code Coverage"'
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
マルチステージデプロイメント
# azure-pipelines.yml
trigger:
- main
variables:
dockerRegistryServiceConnection: 'myDockerRegistry'
imageRepository: 'myapp'
containerRegistry: 'myregistry.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
stages:
- stage: Build
displayName: 'Build and Push'
jobs:
- job: Build
displayName: 'Build job'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: 'Build and push image'
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
latest
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: 'Deploy to Staging'
pool:
vmImage: 'ubuntu-latest'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'myAzureSubscription'
appName: 'myapp-staging'
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: Deploy
displayName: 'Deploy to Production'
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureRmWebAppDeployment@4
displayName: 'Deploy to Production'
inputs:
ConnectionType: 'AzureRM'
azureSubscription: 'myAzureSubscription'
appType: 'webAppContainer'
WebAppName: 'myapp-production'
DockerNamespace: '$(containerRegistry)'
DockerRepository: '$(imageRepository)'
DockerImageTag: '$(tag)'
Kubernetes デプロイメント
# azure-pipelines.yml
resources:
- repo: self
variables:
kubernetesServiceConnection: 'myK8sConnection'
imageRepository: 'myapp'
containerRegistry: 'myregistry.azurecr.io'
tag: '$(Build.BuildId)'
imagePullSecret: 'myregistrykey'
stages:
- stage: Build
displayName: 'Build stage'
jobs:
- job: Build
displayName: 'Build job'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: 'Build and push image to container registry'
inputs:
command: 'buildAndPush'
repository: '$(imageRepository)'
dockerfile: '**/Dockerfile'
containerRegistry: 'myDockerRegistry'
tags: |
$(tag)
- stage: Deploy
displayName: 'Deploy stage'
dependsOn: Build
jobs:
- deployment: Deploy
displayName: 'Deploy job'
pool:
vmImage: 'ubuntu-latest'
environment: 'production.default'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
displayName: 'Create imagePullSecret'
inputs:
action: 'createSecret'
secretName: '$(imagePullSecret)'
dockerRegistryEndpoint: 'myDockerRegistry'
kubernetesServiceConnection: '$(kubernetesServiceConnection)'
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes cluster'
inputs:
action: 'deploy'
kubernetesServiceConnection: '$(kubernetesServiceConnection)'
manifests: |
$(Pipeline.Workspace)/manifests/deployment.yml
$(Pipeline.Workspace)/manifests/service.yml
imagePullSecrets: '$(imagePullSecret)'
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
複雑なワークフローとテンプレート
# azure-pipelines.yml
trigger:
- main
- feature/*
resources:
repositories:
- repository: templates
type: git
name: MyProject/pipeline-templates
variables:
- template: variables/common.yml@templates
stages:
- stage: Validate
displayName: 'Validation Stage'
jobs:
- template: jobs/code-quality.yml@templates
parameters:
solution: '**/*.sln'
- stage: Build
displayName: 'Build Stage'
dependsOn: Validate
jobs:
- template: jobs/build.yml@templates
parameters:
buildConfiguration: 'Release'
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: UnitTests
displayName: 'Unit Tests'
pool:
vmImage: 'windows-latest'
steps:
- template: steps/dotnet-test.yml@templates
parameters:
testProjects: '**/*UnitTests.csproj'
- job: IntegrationTests
displayName: 'Integration Tests'
pool:
vmImage: 'ubuntu-latest'
services:
postgres: postgres
steps:
- template: steps/dotnet-test.yml@templates
parameters:
testProjects: '**/*IntegrationTests.csproj'
- stage: Deploy
displayName: 'Deploy Stage'
dependsOn:
- Build
- Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- template: jobs/deploy.yml@templates
parameters:
environment: 'production'
serviceConnection: 'myAzureConnection'
セキュリティとガバナンス
# azure-pipelines.yml
trigger: none # 手動実行のみ
pool:
vmImage: 'ubuntu-latest'
variables:
- group: production-secrets
- name: isMain
value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
stages:
- stage: SecurityScan
displayName: 'Security Scanning'
jobs:
- job: CodeScan
displayName: 'Code Security Scan'
steps:
- task: SonarCloudPrepare@1
inputs:
SonarCloud: 'SonarCloud'
organization: 'myorg'
scannerMode: 'MSBuild'
projectKey: 'myproject'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
- task: SonarCloudAnalyze@1
- task: SonarCloudPublish@1
- task: WhiteSource@21
displayName: 'WhiteSource Vulnerability Scan'
inputs:
cwd: '$(System.DefaultWorkingDirectory)'
- stage: Deploy
displayName: 'Production Deploy'
dependsOn: SecurityScan
condition: and(succeeded(), eq(variables.isMain, true))
jobs:
- deployment: DeployProduction
displayName: 'Deploy to Production'
environment: 'production'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: AzureKeyVault@2
inputs:
azureSubscription: 'mySubscription'
KeyVaultName: 'mykeyvault'
SecretsFilter: '*'
RunAsPreJob: true
- task: AzureCLI@2
inputs:
azureSubscription: 'mySubscription'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp config appsettings set \
--resource-group myResourceGroup \
--name myapp \
--settings DATABASE_URL="$(database-url)"