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 —
trigger pour push/merge, pr pour pull request, schedules pour planifié, resources.pipelines pour 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
Contributor sur 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
main si 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-mode sur dotnet restore et npm ci à la place de npm 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 :
strategy:
matrix:
dotnet8:
dotnetVersion: '8.0.x'
dotnet9:
dotnetVersion: '9.0.x'
- Conditional variable groups par environnement pour isoler les configurations :
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 push dans le pipeline.
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.
1---2name: dev-azure-devops-pipeline-advisor3description: Conception de pipelines CI/CD avec Azure DevOps (YAML pipelines, stages, templates, déploiement multi-environnement). À utiliser quand l'utilisateur travaille avec Azure DevOps, configure des pipelines YAML ou gère des releases. Se déclenche aussi avec "azure devops", "pipeline YAML", "azure pipeline", "CI/CD azure", "release pipeline", "azure artifacts". Also triggers on "Azure DevOps pipeline", "multi-stage deployment".4---56# Conseiller Azure DevOps Pipelines78## Workflow en étapes9101. **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) ?112. **Choisir la stratégie de déclenchement** — `trigger` pour push/merge, `pr` pour pull request, `schedules` pour planifié, `resources.pipelines` pour pipeline-en-aval.123. **Concevoir le graphe stages → jobs → steps** — Identifier les parallélisations possibles, les dépendances (`dependsOn`), les conditions de déploiement.134. **Extraire les templates** — Tout bloc dupliqué entre stages/pipelines devient un template YAML paramétré.145. **Sécuriser** — Variable Groups liés à Key Vault, Service Connections à droits minimaux, Approvals sur les environments prod.156. **Valider et optimiser** — Activer le cache, mesurer la durée de chaque job, ajouter un health check post-déploiement.1617---1819## Critères de décision clés2021| Situation | Recommandation |22|---|---|23| Déploiement prod nécessite une validation humaine | `environment` avec **Approvals** dans Azure DevOps UI |24| Build identique sur plusieurs environnements | Template de job paramétré (`templates/build.yml`) |25| Secrets (connexion DB, API key) | Variable Group lié à **Azure Key Vault** |26| Temps de build > 5 min à cause des dépendances | `Cache@2` sur dossier NuGet/npm |27| Multi-repo (code + infra séparés) | `resources.repositories` + checkout multiple |28| Déploiement par rolling / blue-green | Strategy `rolling` ou `canary` dans le job deployment |2930---3132## Structure de référence CI/CD complète3334```yaml35# azure-pipelines.yml36trigger:37 branches:38 include: [main, release/*]39 paths:40 exclude: [docs/*, '*.md']4142pr:43 branches:44 include: [main]4546pool:47 vmImage: ubuntu-latest4849variables:50 - group: common-vars # Variable Group partagé51 - name: buildConfiguration52 value: Release53 - name: dotnetVersion54 value: '8.0.x'5556stages:57 - stage: Build58 displayName: Build & Test59 jobs:60 - job: BuildJob61 steps:62 - task: Cache@263 inputs:64 key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'65 restoreKeys: 'nuget | "$(Agent.OS)"'66 path: $(NUGET_PACKAGES)67 displayName: Cache NuGet6869 - task: UseDotNet@270 inputs:71 version: $(dotnetVersion)7273 - script: dotnet restore --locked-mode74 displayName: Restore (locked)7576 - script: dotnet build -c $(buildConfiguration) --no-restore77 displayName: Build7879 - script: |80 dotnet test -c $(buildConfiguration) --no-build \81 --collect:"XPlat Code Coverage" \82 --results-directory $(Agent.TempDirectory)/TestResults83 displayName: Tests8485 - task: PublishCodeCoverageResults@286 inputs:87 summaryFileLocation: '$(Agent.TempDirectory)/TestResults/**/coverage.cobertura.xml'8889 - task: PublishBuildArtifacts@190 inputs:91 PathtoPublish: '$(Build.ArtifactStagingDirectory)'92 ArtifactName: drop9394 - stage: DeployDev95 displayName: Deploy → Dev96 dependsOn: Build97 condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))98 jobs:99 - deployment: DeployDev100 environment: dev101 strategy:102 runOnce:103 deploy:104 steps:105 - template: templates/deploy-steps.yml106 parameters:107 environment: dev108109 - stage: DeployStaging110 displayName: Deploy → Staging111 dependsOn: DeployDev112 jobs:113 - deployment: DeployStaging114 environment: staging # Approval configuré dans l'UI115 strategy:116 runOnce:117 deploy:118 steps:119 - template: templates/deploy-steps.yml120 parameters:121 environment: staging122123 - stage: DeployProd124 displayName: Deploy → Production125 dependsOn: DeployStaging126 condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))127 jobs:128 - deployment: DeployProd129 environment: production # Approval obligatoire130 strategy:131 runOnce:132 deploy:133 steps:134 - template: templates/deploy-steps.yml135 parameters:136 environment: production137```138139---140141## Templates réutilisables142143### `templates/deploy-steps.yml`144145```yaml146parameters:147 - name: environment148 type: string149150steps:151 - download: current152 artifact: drop153154 - task: AzureWebApp@1155 inputs:156 azureSubscription: 'sc-myapp-${{ parameters.environment }}'157 appName: 'myapp-${{ parameters.environment }}'158 package: '$(Pipeline.Workspace)/drop/**/*.zip'159 deploymentMethod: zipDeploy160161 - script: |162 for i in 1 2 3; do163 curl -sf https://myapp-${{ parameters.environment }}.azurewebsites.net/health && break164 echo "Retry $i..." && sleep 10165 done166 displayName: Health check (${{ parameters.environment }})167```168169### `templates/dotnet-build-job.yml`170171```yaml172parameters:173 - name: projects174 type: string175 default: '**/*.csproj'176 - name: testProjects177 type: string178 default: '**/*Tests.csproj'179 - name: dotnetVersion180 type: string181 default: '8.0.x'182183jobs:184 - job: Build185 steps:186 - task: UseDotNet@2187 inputs:188 version: ${{ parameters.dotnetVersion }}189 - script: dotnet restore ${{ parameters.projects }} --locked-mode190 - script: dotnet build ${{ parameters.projects }} -c Release --no-restore191 - script: dotnet test ${{ parameters.testProjects }} -c Release --no-build192```193194---195196## Cache des dépendances (gains typiques : 40–70 %)197198```yaml199# NuGet200- task: Cache@2201 inputs:202 key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'203 restoreKeys: 'nuget | "$(Agent.OS)"'204 path: $(NUGET_PACKAGES)205206# npm207- task: Cache@2208 inputs:209 key: 'npm | "$(Agent.OS)" | package-lock.json'210 restoreKeys: 'npm | "$(Agent.OS)"'211 path: $(npm_config_cache)212```213214---215216## Sécurité217218- **Secrets → Variable Group + Key Vault** : dans l'UI ADO, lier le Variable Group à un Azure Key Vault ; les secrets apparaissent comme variables masquées.219- **Service Connections** : créer un principal de service dédié par environnement avec uniquement le rôle `Contributor` sur le Resource Group cible.220- **Approvals** : configurer dans *Environments → [env] → Approvals and checks* ; ajouter un délai minimal et une liste d'approbateurs.221- **Branch policies** : bloquer les merges vers `main` si le pipeline PR échoue (Policy "Build validation").222- **Audit log** : activer l'audit ADO pour tracer les modifications de pipelines et de Variable Groups.223224---225226## Garde-fous / Anti-patterns227228| Anti-pattern | Problème | Correction |229|---|---|---|230| Secrets en clair dans le YAML | Exposé dans l'historique Git | Variable Group lié à Key Vault |231| Un seul stage "Build+Deploy" | Pas de séparation CI/CD, rollback impossible | Stages distincts avec artifacts |232| `condition: always()` sur le deploy | Déploie même si le build échoue | Utiliser `succeeded()` explicitement |233| `pool: vmImage: windows-latest` pour tout | Lent et coûteux pour du Linux | Choisir l'OS en fonction de la cible |234| Pas de `--locked-mode` sur `dotnet restore` | Versions de packages non reproductibles | Committer `packages.lock.json` et ajouter le flag |235| Jobs séquentiels par défaut | Durée inutilement longue | Identifier les jobs parallélisables via `dependsOn: []` |236| Template avec logique métier hardcodée | Non réutilisable | Paramétrer systématiquement (`parameters`) |237| Déployer sur prod sans health check | Régression silencieuse | Health check avec retry dans le template de déploiement |238239---240241## Bonnes pratiques 2026242243- **Environments** plutôt que classic Release Pipelines — meilleure traçabilité, approvals natifs, historique de déploiement.244- **`--locked-mode`** sur `dotnet restore` et `npm ci` à la place de `npm install` — builds reproductibles.245- **Scheduled trigger** pour les scans de sécurité (Dependabot, OWASP) séparément du pipeline principal.246- **Matrix builds** pour tester sur plusieurs versions de runtime :247 ```yaml248 strategy:249 matrix:250 dotnet8:251 dotnetVersion: '8.0.x'252 dotnet9:253 dotnetVersion: '9.0.x'254 ```255- **Conditional variable groups** par environnement pour isoler les configurations :256 ```yaml257 variables:258 - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:259 - group: prod-vars260 - ${{ else }}:261 - group: dev-vars262 ```263- **Self-hosted agents** pour les builds fréquents (> 20/jour) — réduit les coûts et améliore la latence.264- **Azure Artifacts** pour les packages internes : configurer le feed en upstream source et utiliser `dotnet nuget push` dans le pipeline.265266267## Communication Rules — MANDATORY268269- Ultra-concise. No filler, no preamble, no pleasantries.270- Never say "happy to help", "sure!", "great question", "let me", or similar.271- Tool first, talk second. Act before explaining.272- Result first. Lead with outcome, not process.273- Stop when done. No summary, no recap, no trailing commentary.274- No politeness wrappers. Direct and blunt.275- Minimum words. If one word works, do not use ten.276- No unsolicited explanations.277- No emoji unless asked.