Module 7 : CI/CD avec Azure DevOps
Durée estimée : 30 minutes
Objectifs du Module
À la fin de ce module, vous serez capable de :
- Créer et configurer un projet Azure DevOps
- Mettre en place des pipelines CI (Build)
- Configurer des pipelines CD (Release)
- Implémenter des Service Connections sécurisées
- Gérer les environnements et les approbations
1. Azure DevOps Overview
1.1 Architecture
graph TB
subgraph "Azure DevOps Organization"
subgraph "Project: Phoenix"
REPOS["📂 Repos<br/>(Git)"]
BOARDS["📋 Boards<br/>(Work Items)"]
PIPELINES["🔄 Pipelines<br/>(CI/CD)"]
ARTIFACTS["📦 Artifacts<br/>(Packages)"]
TESTS["🧪 Test Plans"]
end
subgraph "Service Connections"
SC_AZURE["🔗 Azure RM"]
SC_ACR["🔗 Docker Registry"]
SC_K8S["🔗 Kubernetes"]
end
end
subgraph "Azure Resources"
ACR["📦 ACR"]
AKS["☸️ AKS"]
SQL["🗄️ SQL Database"]
end
REPOS -->|"Trigger"| PIPELINES
PIPELINES -->|"Build"| ACR
PIPELINES -->|"Deploy"| AKS
SC_AZURE --> ACR
SC_K8S --> AKS
style PIPELINES fill:#0078d4,color:#fff
style AKS fill:#326ce5,color:#fff
1.2 Composants Clés
| Composant | Description |
|---|---|
| Organization | Conteneur top-level |
| Project | Regroupe repos, pipelines, boards |
| Repos | Repositories Git |
| Pipelines | CI/CD automatisé |
| Artifacts | Packages NuGet, npm, etc. |
| Service Connection | Authentification vers Azure/K8s |
2. Configuration Initiale
2.1 Créer une Organisation et un Projet
# Installer l'extension Azure DevOps CLI
az extension add --name azure-devops
# Se connecter
az devops login
# Configurer l'organisation par défaut
az devops configure --defaults organization=https://dev.azure.com/myorg
# Créer un projet
az devops project create \
--name Phoenix \
--description "Phoenix Application" \
--visibility private \
--source-control git \
--process Agile
# Configurer le projet par défaut
az devops configure --defaults project=Phoenix
2.2 Service Connections
# Créer une Service Connection Azure RM
az devops service-endpoint azurerm create \
--name "Azure-Prod" \
--azure-rm-service-principal-id $SP_APP_ID \
--azure-rm-subscription-id $SUBSCRIPTION_ID \
--azure-rm-subscription-name "Production" \
--azure-rm-tenant-id $TENANT_ID
# Via le portail Azure DevOps (recommandé pour Workload Identity Federation)
# Project Settings > Service Connections > New > Azure Resource Manager
# Choisir "Workload Identity federation (automatic)"
3. Pipeline CI (Build)
3.1 Structure YAML
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
paths:
include:
- src/**
- Dockerfile
pr:
branches:
include:
- main
paths:
include:
- src/**
pool:
vmImage: 'ubuntu-latest'
variables:
- group: phoenix-variables
- name: imageRepository
value: 'phoenix/backend'
- name: dockerfilePath
value: '$(Build.SourcesDirectory)/Dockerfile'
- name: tag
value: '$(Build.BuildId)'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
displayName: 'Build Application'
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
version: '8.x'
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration Release --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Run Tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration Release --no-build --collect:"XPlat Code Coverage"'
- task: PublishCodeCoverageResults@1
displayName: 'Publish Code Coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
- stage: Docker
displayName: 'Build and Push Docker'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: DockerJob
displayName: 'Docker Build & Push'
steps:
- task: Docker@2
displayName: 'Build Docker Image'
inputs:
containerRegistry: 'ACR-Phoenix'
repository: '$(imageRepository)'
command: 'build'
Dockerfile: '$(dockerfilePath)'
tags: |
$(tag)
latest
- task: Docker@2
displayName: 'Push Docker Image'
inputs:
containerRegistry: 'ACR-Phoenix'
repository: '$(imageRepository)'
command: 'push'
tags: |
$(tag)
latest
3.2 Multi-Stage Build avec Cache
# azure-pipelines-advanced.yml
trigger:
- main
variables:
DOCKER_BUILDKIT: 1
acrName: 'phoenixacr2024'
stages:
- stage: CI
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Cache@2
displayName: 'Cache Docker layers'
inputs:
key: 'docker | "$(Agent.OS)" | Dockerfile'
path: '$(Pipeline.Workspace)/docker-cache'
restoreKeys: |
docker | "$(Agent.OS)"
- task: AzureCLI@2
displayName: 'Build with ACR Tasks'
inputs:
azureSubscription: 'Azure-Prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az acr build \
--registry $(acrName) \
--image phoenix/backend:$(Build.BuildId) \
--image phoenix/backend:latest \
--file Dockerfile \
.
- task: AzureCLI@2
displayName: 'Scan Image for Vulnerabilities'
inputs:
azureSubscription: 'Azure-Prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Utiliser Microsoft Defender for Containers ou Trivy
az acr task run \
--registry $(acrName) \
--name security-scan \
--context /dev/null
4. Pipeline CD (Release)
4.1 Déploiement sur AKS
# azure-pipelines-cd.yml
trigger: none
resources:
pipelines:
- pipeline: CI
source: 'Phoenix-CI'
trigger:
branches:
include:
- main
variables:
- group: phoenix-prod-variables
- name: aksCluster
value: 'phoenix-aks'
- name: aksResourceGroup
value: 'phoenix-aks-rg'
- name: namespace
value: 'phoenix'
stages:
- stage: DeployDev
displayName: 'Deploy to Dev'
jobs:
- deployment: DeployDev
displayName: 'Deploy to Dev Environment'
environment: 'phoenix-dev'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
displayName: 'Create imagePullSecret'
inputs:
action: 'createSecret'
kubernetesServiceConnection: 'AKS-Dev'
secretType: 'dockerRegistry'
secretName: 'acr-secret'
dockerRegistryEndpoint: 'ACR-Phoenix'
namespace: '$(namespace)'
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes'
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Dev'
namespace: '$(namespace)'
manifests: |
$(Pipeline.Workspace)/CI/manifests/*.yaml
containers: |
phoenixacr2024.azurecr.io/phoenix/backend:$(resources.pipeline.CI.runID)
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployDev
condition: succeeded()
jobs:
- deployment: DeployProd
displayName: 'Deploy to Production'
environment: 'phoenix-prod'
pool:
vmImage: 'ubuntu-latest'
strategy:
canary:
increments: [10, 50, 100]
preDeploy:
steps:
- script: echo "Pre-deploy validation"
deploy:
steps:
- task: KubernetesManifest@0
displayName: 'Deploy Canary'
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Prod'
namespace: '$(namespace)'
strategy: 'canary'
percentage: '$(strategy.increment)'
manifests: |
$(Pipeline.Workspace)/CI/manifests/*.yaml
postRouteTraffic:
steps:
- task: AzureCLI@2
displayName: 'Validate Health'
inputs:
azureSubscription: 'Azure-Prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Vérifier les métriques
ERROR_RATE=$(az monitor metrics list \
--resource /subscriptions/.../phoenix-appgw \
--metric "Failed Requests" \
--query "value[0].timeseries[0].data[-1].total" -o tsv)
if [ "$ERROR_RATE" -gt 5 ]; then
echo "##vso[task.logissue type=error]High error rate detected: $ERROR_RATE%"
exit 1
fi
on:
failure:
steps:
- task: KubernetesManifest@0
displayName: 'Rollback'
inputs:
action: 'reject'
kubernetesServiceConnection: 'AKS-Prod'
namespace: '$(namespace)'
4.2 Environnements et Approbations
# Configuration via l'interface Azure DevOps
# Pipelines > Environments > phoenix-prod > Approvals and checks
# Exemple de checks disponibles :
# - Approval: Requiert approbation manuelle
# - Branch control: Limite les branches autorisées
# - Business hours: Déploiement uniquement en heures ouvrées
# - Required template: Force l'utilisation d'un template
5. Templates et Réutilisabilité
5.1 Template de Job
# templates/build-dotnet.yml
parameters:
- name: solution
type: string
default: '**/*.sln'
- name: buildConfiguration
type: string
default: 'Release'
jobs:
- job: Build
displayName: 'Build .NET Application'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
displayName: 'Use .NET SDK'
inputs:
version: '8.x'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: '${{ parameters.solution }}'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '${{ parameters.solution }}'
arguments: '--configuration ${{ parameters.buildConfiguration }} --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Test'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration ${{ parameters.buildConfiguration }} --no-build'
5.2 Template de Stage
# templates/deploy-aks.yml
parameters:
- name: environment
type: string
- name: aksConnection
type: string
- name: namespace
type: string
- name: imageTag
type: string
stages:
- stage: Deploy_${{ parameters.environment }}
displayName: 'Deploy to ${{ parameters.environment }}'
jobs:
- deployment: Deploy
environment: '${{ parameters.environment }}'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
displayName: 'Deploy to AKS'
inputs:
action: 'deploy'
kubernetesServiceConnection: '${{ parameters.aksConnection }}'
namespace: '${{ parameters.namespace }}'
manifests: |
$(Pipeline.Workspace)/manifests/*.yaml
containers: |
phoenixacr2024.azurecr.io/phoenix/backend:${{ parameters.imageTag }}
5.3 Utilisation des Templates
# azure-pipelines.yml
trigger:
- main
extends:
template: templates/pipeline-main.yml
parameters:
environments:
- name: dev
aksConnection: 'AKS-Dev'
- name: staging
aksConnection: 'AKS-Staging'
- name: prod
aksConnection: 'AKS-Prod'
requiresApproval: true
6. Secrets et Variables
6.1 Variable Groups
# Créer un variable group
az pipelines variable-group create \
--name phoenix-variables \
--variables \
ACR_NAME=phoenixacr2024 \
AKS_CLUSTER=phoenix-aks
# Lier au Key Vault
az pipelines variable-group create \
--name phoenix-secrets \
--authorize true \
--type Vsts \
--variables dummy=value
# Ensuite via l'UI : Link secrets from Azure Key Vault
6.2 Secure Files
# Utiliser un fichier sécurisé (kubeconfig, certificat)
steps:
- task: DownloadSecureFile@1
name: kubeconfig
displayName: 'Download kubeconfig'
inputs:
secureFile: 'prod-kubeconfig'
- script: |
export KUBECONFIG=$(kubeconfig.secureFilePath)
kubectl get nodes
displayName: 'Use kubeconfig'
7. Exercice : À Vous de Jouer
Mise en Pratique
Objectif : Créer un pipeline CI/CD complet pour déployer une application sur AKS
Contexte : Vous devez mettre en place une chaîne CI/CD complète pour une application microservices. Le pipeline doit builder l'application, créer des images Docker, les pousser dans ACR, exécuter des tests, et déployer sur AKS avec des environnements Dev et Prod séparés nécessitant des approbations.
Tâches à réaliser :
- Créer un projet Azure DevOps avec repository Git
- Créer des Service Connections pour Azure et Kubernetes
- Créer un pipeline CI (build) en YAML qui compile, teste et crée les images Docker
- Publier les artifacts et images dans ACR
- Créer un pipeline CD (release) en YAML avec stages Dev et Prod
- Configurer des environnements avec approbations pour Prod
- Implémenter des variables groups pour les configurations
- Ajouter des quality gates (tests, security scanning)
Critères de validation :
- [ ] Le repository Git contient le code source et les pipelines YAML
- [ ] Les Service Connections sont configurées avec Workload Identity Federation
- [ ] Le pipeline CI build et teste l'application avec succès
- [ ] Les images Docker sont poussées dans ACR avec tags appropriés
- [ ] Le pipeline CD déploie automatiquement sur Dev
- [ ] Le déploiement sur Prod nécessite une approbation manuelle
- [ ] Les variables sensibles sont stockées dans des variable groups
- [ ] Les quality gates bloquent le déploiement en cas d'échec
Solution
Pipeline CI (azure-pipelines-ci.yml) :
trigger:
branches:
include:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
- group: phoenix-variables
- name: imageRepository
value: 'phoenix/backend'
- name: tag
value: '$(Build.BuildId)'
stages:
- stage: Build
jobs:
- job: BuildAndTest
steps:
- task: Docker@2
inputs:
containerRegistry: 'ACR-Connection'
repository: $(imageRepository)
command: 'buildAndPush'
Dockerfile: '**/Dockerfile'
tags: |
$(tag)
latest
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: 'k8s-manifests'
ArtifactName: 'manifests'
Pipeline CD (azure-pipelines-cd.yml) :
trigger: none
resources:
pipelines:
- pipeline: ci-pipeline
source: 'Phoenix-CI'
trigger:
branches:
include:
- main
stages:
- stage: Dev
jobs:
- deployment: DeployDev
environment: 'development'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Dev'
namespace: 'dev'
manifests: '$(Pipeline.Workspace)/manifests/*.yaml'
- stage: Prod
dependsOn: Dev
jobs:
- deployment: DeployProd
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Prod'
namespace: 'prod'
manifests: '$(Pipeline.Workspace)/manifests/*.yaml'
8. Exercices Pratiques Additionnels
Exercice 1 : Pipeline CI/CD Complet
Objectif
Créer un pipeline complet avec build, tests, scan de sécurité et déploiement.
Solution
# phoenix-complete-pipeline.yml
trigger:
branches:
include:
- main
- develop
variables:
- group: phoenix-variables
- name: vmImageName
value: 'ubuntu-latest'
stages:
# Stage 1: Build & Test
- stage: Build
displayName: 'Build & Test'
jobs:
- job: BuildAndTest
pool:
vmImage: $(vmImageName)
steps:
- task: UseDotNet@2
inputs:
version: '8.x'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Test'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
# Stage 2: Security Scan
- stage: SecurityScan
displayName: 'Security Scan'
dependsOn: Build
jobs:
- job: Scan
pool:
vmImage: $(vmImageName)
steps:
- task: SnykSecurityScan@1
inputs:
serviceConnectionEndpoint: 'Snyk'
testType: 'code'
failOnIssues: true
# Stage 3: Build & Push Image
- stage: Docker
displayName: 'Build Docker Image'
dependsOn: SecurityScan
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: DockerBuild
pool:
vmImage: $(vmImageName)
steps:
- task: Docker@2
inputs:
containerRegistry: 'ACR-Phoenix'
repository: 'phoenix/backend'
command: 'buildAndPush'
tags: |
$(Build.BuildId)
latest
# Stage 4: Deploy Dev
- stage: DeployDev
displayName: 'Deploy to Dev'
dependsOn: Docker
jobs:
- deployment: DeployDev
environment: 'phoenix-dev'
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Dev'
namespace: 'phoenix-dev'
manifests: 'manifests/*.yaml'
# Stage 5: Deploy Prod (avec approbation)
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployDev
jobs:
- deployment: DeployProd
environment: 'phoenix-prod'
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
inputs:
action: 'deploy'
kubernetesServiceConnection: 'AKS-Prod'
namespace: 'phoenix-prod'
manifests: 'manifests/*.yaml'
8. Résumé
| Concept | Description | Usage |
|---|---|---|
| Pipeline YAML | Définition as-code | azure-pipelines.yml |
| Stages | Phases de déploiement | Build, Test, Deploy |
| Jobs | Unités d'exécution | Parallélisation |
| Tasks | Actions atomiques | Build, Push, Deploy |
| Templates | Réutilisabilité | DRY principle |
| Environments | Cibles de déploiement | Dev, Staging, Prod |
| Service Connections | Authentification | Azure RM, ACR, AKS |
Navigation
| Précédent | Suivant |
|---|---|
| ← Module 6 : TP Final | Module 8 : Serverless → |
Navigation
| ← Module 6 : TP Final - Infrastructure ... | Module 8 : Serverless - Azure Functio... → |