📖 Manuel
Conseiller Azure DevOps Pipelines
Workflow en étapes
- Qualifier le besoin — CI seul, CD seul, CI/CD complet ? Mono-repo ou multi-repo ? Type d'artefact (binaire, image Docker, package NuGet/npm) ? Environnements cibles (dev / staging / prod) ?
- Choisir la stratégie de déclenchement —
triggerpour push/merge,prpour pull request,schedulespour planifié,resources.pipelinespour pipeline-en-aval. - Concevoir le graphe stages → jobs → steps — Identifier les parallélisations possibles, les dépendances (
dependsOn), les conditions de déploiement. - Extraire les templates — Tout bloc dupliqué entre stages/pipelines devient un template YAML paramétré.
- Sécuriser — Variable Groups liés à Key Vault, Service Connections à droits minimaux, Approvals sur les environments prod.
- Valider et optimiser — Activer le cache, mesurer la durée de chaque job, ajouter un health check post-déploiement.
Critères de décision clés
| Situation | Recommandation |
|---|---|
| Déploiement prod nécessite une validation humaine | environment avec Approvals dans Azure DevOps UI |
| Build identique sur plusieurs environnements | Template de job paramétré (templates/build.yml) |
| Secrets (connexion DB, API key) | Variable Group lié à Azure Key Vault |
| Temps de build > 5 min à cause des dépendances | Cache@2 sur dossier NuGet/npm |
| Multi-repo (code + infra séparés) | resources.repositories + checkout multiple |
| Déploiement par rolling / blue-green | Strategy rolling ou canary dans le job deployment |
Structure de référence CI/CD complète
# azure-pipelines.yml
trigger:
branches:
include: [main, release/*]
paths:
exclude: [docs/*, '*.md']
pr:
branches:
include: [main]
pool:
vmImage: ubuntu-latest
variables:
- group: common-vars # Variable Group partagé
- name: buildConfiguration
value: Release
- name: dotnetVersion
value: '8.0.x'
stages:
- stage: Build
displayName: Build & Test
jobs:
- job: BuildJob
steps:
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'
restoreKeys: 'nuget | "$(Agent.OS)"'
path: $(NUGET_PACKAGES)
displayName: Cache NuGet
- task: UseDotNet@2
inputs:
version: $(dotnetVersion)
- script: dotnet restore --locked-mode
displayName: Restore (locked)
- script: dotnet build -c $(buildConfiguration) --no-restore
displayName: Build
- script: |
dotnet test -c $(buildConfiguration) --no-build \
--collect:"XPlat Code Coverage" \
--results-directory $(Agent.TempDirectory)/TestResults
displayName: Tests
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/TestResults/**/coverage.cobertura.xml'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: drop
- stage: DeployDev
displayName: Deploy → Dev
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployDev
environment: dev
strategy:
runOnce:
deploy:
steps:
- template: templates/deploy-steps.yml
parameters:
environment: dev
- stage: DeployStaging
displayName: Deploy → Staging
dependsOn: DeployDev
jobs:
- deployment: DeployStaging
environment: staging # Approval configuré dans l'UI
strategy:
runOnce:
deploy:
steps:
- template: templates/deploy-steps.yml
parameters:
environment: staging
- stage: DeployProd
displayName: Deploy → Production
dependsOn: DeployStaging
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))
jobs:
- deployment: DeployProd
environment: production # Approval obligatoire
strategy:
runOnce:
deploy:
steps:
- template: templates/deploy-steps.yml
parameters:
environment: production
Templates réutilisables
templates/deploy-steps.yml
parameters:
- name: environment
type: string
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
inputs:
azureSubscription: 'sc-myapp-${{ parameters.environment }}'
appName: 'myapp-${{ parameters.environment }}'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
deploymentMethod: zipDeploy
- script: |
for i in 1 2 3; do
curl -sf https://myapp-${{ parameters.environment }}.azurewebsites.net/health && break
echo "Retry $i..." && sleep 10
done
displayName: Health check (${{ parameters.environment }})
templates/dotnet-build-job.yml
parameters:
- name: projects
type: string
default: '**/*.csproj'
- name: testProjects
type: string
default: '**/*Tests.csproj'
- name: dotnetVersion
type: string
default: '8.0.x'
jobs:
- job: Build
steps:
- task: UseDotNet@2
inputs:
version: ${{ parameters.dotnetVersion }}
- script: dotnet restore ${{ parameters.projects }} --locked-mode
- script: dotnet build ${{ parameters.projects }} -c Release --no-restore
- script: dotnet test ${{ parameters.testProjects }} -c Release --no-build
Cache des dépendances (gains typiques : 40–70 %)
# NuGet
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'
restoreKeys: 'nuget | "$(Agent.OS)"'
path: $(NUGET_PACKAGES)
# npm
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: 'npm | "$(Agent.OS)"'
path: $(npm_config_cache)
Sécurité
- Secrets → Variable Group + Key Vault : dans l'UI ADO, lier le Variable Group à un Azure Key Vault ; les secrets apparaissent comme variables masquées.
- Service Connections : créer un principal de service dédié par environnement avec uniquement le rôle
Contributorsur le Resource Group cible. - Approvals : configurer dans Environments → [env] → Approvals and checks ; ajouter un délai minimal et une liste d'approbateurs.
- Branch policies : bloquer les merges vers
mainsi le pipeline PR échoue (Policy "Build validation"). - Audit log : activer l'audit ADO pour tracer les modifications de pipelines et de Variable Groups.
Garde-fous / Anti-patterns
| Anti-pattern | Problème | Correction |
|---|---|---|
| Secrets en clair dans le YAML | Exposé dans l'historique Git | Variable Group lié à Key Vault |
| Un seul stage "Build+Deploy" | Pas de séparation CI/CD, rollback impossible | Stages distincts avec artifacts |
condition: always() sur le deploy | Déploie même si le build échoue | Utiliser succeeded() explicitement |
pool: vmImage: windows-latest pour tout | Lent et coûteux pour du Linux | Choisir l'OS en fonction de la cible |
Pas de --locked-mode sur dotnet restore | Versions de packages non reproductibles | Committer packages.lock.json et ajouter le flag |
| Jobs séquentiels par défaut | Durée inutilement longue | Identifier les jobs parallélisables via dependsOn: [] |
| Template avec logique métier hardcodée | Non réutilisable | Paramétrer systématiquement (parameters) |
| Déployer sur prod sans health check | Régression silencieuse | Health check avec retry dans le template de déploiement |
Bonnes pratiques 2026
- Environments plutôt que classic Release Pipelines — meilleure traçabilité, approvals natifs, historique de déploiement.
--locked-modesurdotnet restoreetnpm cià la place denpm install— builds reproductibles.- Scheduled trigger pour les scans de sécurité (Dependabot, OWASP) séparément du pipeline principal.
- Matrix builds pour tester sur plusieurs versions de runtime :
```yaml strategy: matrix: dotnet8: dotnetVersion: '8.0.x' dotnet9: dotnetVersion: '9.0.x' ```
- Conditional variable groups par environnement pour isoler les configurations :
```yaml variables:
- ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
- group: prod-vars
- ${{ else }}:
- group: dev-vars
```
- Self-hosted agents pour les builds fréquents (> 20/jour) — réduit les coûts et améliore la latence.
- Azure Artifacts pour les packages internes : configurer le feed en upstream source et utiliser
dotnet nuget pushdans le pipeline.