Version : 1.1 · Date : 2026-10-22 · Domaine 3 de l'examen : Design and implement build and release pipelines (50 à 55 %)
En une phrase : un pipeline YAML décrit quoi exécuter (stages, jobs, steps) et quand le lancer (triggers) ; les agents ou runners décident où il tourne (et donc le coût et l'accès réseau) ; les templates évitent de le réécrire ; les approvals and checks, posés hors du YAML sur les ressources protégées, décident s'il a le droit d'avancer.

Ce que l'examen demande#
Texte officiel du guide d'étude AZ-400 (compétences au 27 juillet 2026), bloc Design and implement pipelines :
| # | Compétence mesurée (guide officiel, en anglais) | Section |
|---|---|---|
| 3.3.1 | Select a deployment automation solution, including GitHub Actions and Azure Pipelines | 2 |
| 3.3.2 | Design and implement a GitHub runner or Azure DevOps agent infrastructure, including cost, tool selection, licenses, connectivity, and maintainability | 3 |
| 3.3.3 | Design and implement integration between GitHub repositories and Azure Pipelines | 4 |
| 3.3.4 | Develop and implement pipeline trigger rules | 5 |
| 3.3.5 | Develop pipelines by using YAML | 6 |
| 3.3.6 | Design and implement a strategy for job execution order, including parallelism and multi-stage pipelines | 7 |
| 3.3.7 | Develop and implement complex pipeline scenarios, such as hybrid pipelines, VM templates, and self-hosted runners or agents | 8 |
| 3.3.8 | Create reusable pipeline elements, including YAML templates, task groups, variables, and variable groups | 9 |
| 3.3.9 | Design and implement checks and approvals by using YAML-based environments | 10 |
Ce qui touche au même domaine est traité ailleurs, sans répétition : les tests et quality gates dans 03 · Packages et tests, les stratégies de déploiement (blue-green, canary, feature flags) dans 05 · Déploiements, la migration classic → YAML, la santé, le coût et la rétention des pipelines dans 06 · IaC et maintenance, les service connections, identités, secrets et OIDC dans 07 · Sécurité et conformité.
1. Les bases : vocabulaire et correspondances#
1.1 La hiérarchie d'un pipeline Azure Pipelines#
| Niveau | Rôle | À retenir |
|---|---|---|
| Pipeline | Un fichier YAML versionné avec le code (souvent azure-pipelines.yml) |
Une exécution = un run ; le fichier est lu sur la branche du run |
| Stage | Étape logique (Build, Test, DeployProd) | Séquentiels par défaut ; approvals and checks évalués avant chaque stage |
| Job | Ensemble de steps exécutés sur un agent (ou sans agent : pool: server) |
Chaque job démarre sur un agent propre ; il consomme un parallel job |
| Step | Un script, bash, pwsh, task, checkout, download, publish, template |
Les steps d'un job partagent le disque de l'agent |
| Task | Brique packagée et versionnée : AzureCLI@2, DotNetCoreCLI@2… |
Toujours fixer la version majeure (@2) |
| Artifact | Fichiers publiés par un run (publish) et récupérés plus loin (download) |
Différent d'Azure Artifacts (les feeds de packages, chapitre 03) |
Un pipeline sans stage (juste jobs: ou steps:) est accepté : Azure Pipelines crée un stage implicite. Dès qu'il y a du
déploiement, écrire les stages explicitement (approvals, retry d'un stage, historique par environment).
1.2 Les mêmes idées dans GitHub Actions#

Un workflow GitHub (.github/workflows/*.yml) contient des jobs (parallèles par défaut, ordonnés par needs) faits de
steps (run ou uses d'une action). Il n'y a pas de stage : un job avec environment: joue ce rôle pour un
déploiement. La correspondance sert à lire les questions qui mélangent les deux outils.
# .github/workflows/ci.yml : un workflow GitHub Actions minimal mais réaliste
name: ci
on:
push:
branches: [main]
paths-ignore: ['docs/**']
pull_request:
branches: [main]
workflow_dispatch: # bouton « Run workflow »
permissions:
contents: read # GITHUB_TOKEN au minimum (chapitre 07)
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # un nouveau push annule le run en cours sur la même branche
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy:
needs: test # ordre : après tous les jobs de la matrice
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # protection rules de l'environment (section 10)
steps:
- run: echo "déploiement"
2. Choisir entre GitHub Actions et Azure Pipelines#
Les deux outils savent builder n'importe quel langage, déployer partout, utiliser des agents privés et protéger la production. L'examen ne demande pas « lequel est meilleur » mais lequel colle au contexte.
| Critère | Plutôt GitHub Actions | Plutôt Azure Pipelines |
|---|---|---|
| Où vit le code | GitHub (cloud ou Enterprise) | Azure Repos, ou GitHub avec un existant Azure DevOps |
| Suivi du travail et tests manuels | GitHub Issues et Projects | Azure Boards, Azure Test Plans |
| Contrôle des déploiements | Environments + deployment protection rules (selon le plan, voir 10.5) | Approvals and checks riches : Business Hours, Invoke Azure Function / REST API, Azure Monitor alerts, Required template, Exclusive lock |
| Écosystème | GitHub Marketplace (actions), Dependabot, GitHub Advanced Security | Tasks Microsoft et Marketplace Azure DevOps, Azure Artifacts |
| Classic (UI) existant | Non applicable | Release pipelines, task groups, deployment groups à migrer vers YAML (chapitre 06) |
| Facturation | Minutes (quota inclus selon le plan ; gratuit pour les dépôts publics avec les runners standard) | Parallel jobs (achetés à l'unité) ; minutes seulement pour les GitHub-hosted agents (preview) |
| Hors ligne / serveur | GitHub Enterprise Server | Azure DevOps Server |
Combinaisons fréquentes et valides :
- Code et PR sur GitHub, CI/CD dans Azure Pipelines (section 4) : l'équipe garde Azure Boards et les checks avancés.
- GitHub Actions pour la CI, déclenchement d'un pipeline Azure Pipelines pour la CD (action
Azure/pipelines, section 8.1). - Migration progressive d'Azure Pipelines vers Actions avec GitHub Actions Importer (
gh actions-importer auditpuismigrate), voir chapitre 06.
Réflexe d'examen : « l'entreprise utilise déjà Azure Boards et Test Plans et veut des approbations avec plages horaires » → Azure Pipelines. « Tout est dans GitHub, l'équipe veut réutiliser des actions de la Marketplace » → GitHub Actions. La question précise souvent un élément décisif : le chercher.
3. Agents et runners : coût, licences, connectivité, maintenance#

3.1 Les options côté Azure Pipelines#
| Option | Ce que c'est | Quand la choisir |
|---|---|---|
| Microsoft-hosted agents | VM neuve à chaque job, maintenue par Microsoft (images ubuntu-latest, windows-latest, macOS-latest), taille Standard_DS2_v2 |
Cas par défaut : rien à gérer, outils courants préinstallés |
| GitHub-hosted agents (preview) | Runners GitHub utilisables par Azure Pipelines (pool GitHub-hosted Agents, activé dans Billing), tailles plus grosses, macOS arm64 (les nouveaux macOS n'arrivent plus que là) | Grosses machines ou macOS récents sans rien gérer ; à la minute, sans gratuité, sans consommer de parallel jobs ; 8 agents par taille pendant la preview ; pas de réseau privé ; Azure DevOps Services seulement |
| Managed DevOps Pools (MDP) | Service Azure : agents dans des VM gérées par Microsoft, injectées si besoin dans votre VNet | Accès privé, images personnalisées, grosses VM, montée en charge, sans gérer de VM ; recommandé à la place des scale set agents |
| Azure Virtual Machine Scale Set agents | VMSS dans votre abonnement, que Azure Pipelines agrandit et réduit | Existant à conserver ; Microsoft oriente vers MDP |
| Self-hosted agents | Logiciel agent installé sur vos VM, serveurs physiques, conteneurs Docker (Linux, Windows, macOS) | On-premises, matériel spécifique, licences logicielles liées à une machine, contrôle total |
Les agents sont rangés dans des agent pools (niveau organisation, référencés par les projets). Un pool est une ressource protégée : il faut autoriser chaque pipeline (ou ouvrir l'accès à tous) et on peut y poser des checks (section 10).
Choisir l'agent par ses capacités : chaque agent publie des capabilities (système, outils installés, variables personnalisées) ; le job exprime des demands. Utile dans un pool de self-hosted hétérogène :
pool:
name: pool-onprem-linux
demands:
- Agent.OS -equals Linux
- terraform # l'agent doit déclarer la capability « terraform »
3.2 Le coût et les licences : les parallel jobs#
Dans Azure Pipelines, on n'achète pas des minutes mais des parallel jobs : le nombre de jobs qui peuvent tourner en même temps dans l'organisation. Au-delà, les jobs attendent dans la file.
| Type | Gratuit (projets privés) | Payant |
|---|---|---|
| Microsoft-hosted | 1 parallel job, 60 min par job, 1 800 min par mois ; il faut lier l'organisation à un abonnement Azure pour la gratuité des nouvelles organisations | Chaque parallel job acheté : minutes illimitées, jusqu'à 360 min par job |
| Self-hosted (y compris MDP et scale set) | 1 parallel job, sans limite de durée, + 1 par abonné Visual Studio Enterprise membre de l'organisation | Parallel jobs self-hosted, moins chers que les Microsoft-hosted |
| GitHub-hosted agents (preview) | Aucun | À la minute, selon la taille |
Points qui reviennent à l'examen :
- Les utilisateurs (licences Basic, Stakeholder) n'ont rien à voir avec les parallel jobs : un Stakeholder peut lancer des pipelines.
- Avec MDP ou scale set, on paie deux fois : les parallel jobs self-hosted (Azure DevOps) et les ressources Azure (VM, disques, réseau). Pour réduire la facture : arrêt des agents inactifs, standby agents seulement aux heures ouvrées (planification), images légères.
- Les projets publics d'Azure DevOps sont en cours de retrait par Microsoft (conversion en privé à prévoir) : ne plus compter sur leur gratuité.
- Côté GitHub : les runners standard sont gratuits pour les dépôts publics ; pour les dépôts privés, un quota de minutes est inclus par plan (2 000 en Free, 3 000 en Pro et Team, 50 000 en Enterprise Cloud), puis facturation à la minute, plus chère pour Windows et macOS que pour Linux ; les larger runners sont toujours facturés, même en public ; l'usage de self-hosted runners est gratuit côté Actions (on paie ses machines).
3.3 La connectivité : l'agent appelle, personne ne l'appelle#

- L'agent initie toujours la communication : il s'enregistre, puis interroge la file en HTTPS 443 sortant (long polling). Aucun port entrant à ouvrir, ni sur un serveur on-premises ni dans un VNet.
- Il lui faut la « ligne de vue » vers ce qu'il déploie : un Key Vault en Private Endpoint, un AKS privé ou un serveur on-premises ne sont joignables que par un agent placé dans le réseau (MDP injecté dans le VNet, self-hosted, scale set).
- Un Microsoft-hosted agent sort par des IP publiques larges et changeantes (plages Azure de la région de l'organisation) : ouvrir un pare-feu à ces plages est fragile. Préférer un agent privé.
- Derrière un proxy d'entreprise : l'agent se configure avec l'URL du proxy et les adresses Azure DevOps à autoriser en
sortie (
dev.azure.com,*.visualstudio.com… liste officielle « Allowed address lists »). - Enregistrement d'un self-hosted agent : par PAT (scope Agent Pools : Read & manage, utilisé seulement à l'enregistrement), par service principal ou par device code. Ensuite l'agent utilise son propre jeton, et chaque job reçoit un jeton limité au job.
# Enregistrer un agent Linux self-hosted (exemple ; le PAT est saisi au prompt, jamais écrit dans un script)
./config.sh --unattended --url https://dev.azure.com/contoso --auth pat \
--pool pool-onprem-linux --agent "$(hostname)" --replace
sudo ./svc.sh install && sudo ./svc.sh start # agent en service, redémarre avec la machine
3.4 La maintenance et le choix des outils#
| Sujet | Microsoft-hosted / GitHub-hosted | MDP | Self-hosted |
|---|---|---|---|
| Mises à jour de l'OS et des outils | Microsoft (images mises à jour toutes les 3 semaines environ) | Images Azure Pipelines tenues par Microsoft, ou votre image (Azure Compute Gallery) | Vous |
| Version de l'agent | Automatique | Automatique | Mise à jour automatique quand une task l'exige (désactivable au niveau du pool) |
| État entre deux jobs | Machine neuve | Stateless (neuf à chaque job) ou stateful (réutilisé, jusqu'à 7 jours) | Persistant : caches chauds, mais risque de résidus et de fuite entre jobs |
| Outils spécifiques | Installer à chaque run (tasks Use…, Cache@2) |
Dans l'image personnalisée | Installés une fois |
Règle de choix des outils : si l'outil s'installe en quelques secondes, l'installer dans le job (versions explicites, reproductible) ; s'il est lourd, l'intégrer à une image (MDP, section 8.2) ; s'il dépend d'une licence liée à la machine ou d'un matériel, self-hosted.
Bonnes pratiques Microsoft pour les self-hosted : un agent par machine (deux agents sur la même machine se gênent :
disque, ports, caches), compte de service à privilèges minimaux, agents éphémères quand c'est possible (conteneur lancé avec
--once : un job puis l'agent s'arrête), jamais d'agent self-hosted partagé avec des pipelines de PR venant de forks.
3.5 Managed DevOps Pools en détail#
- Ressource Azure (
Microsoft.DevOpsInfrastructure/pools) rattachée à un Dev Center et un projet Dev Center ; les VM sont dans un abonnement Microsoft, vous ne voyez que la ressource pool. - Réseau : injection dans un subnet délégué de votre VNet (accès privé, UDR, pare-feu), ou réseau isolé géré par Microsoft.
- Images : images Azure Pipelines (les mêmes que Microsoft-hosted), images Marketplace sélectionnées, ou images de votre
Azure Compute Gallery ; plusieurs images par pool, choisies par le job (
demands: ImageOverride -equals …). - Montée en charge : nombre maximal d'agents (jusqu'à des milliers), standby agents manuels ou automatiques selon une planification ; jobs jusqu'à 2 jours.
- Identité : managed identity attachable aux agents (accès à Key Vault, ACR) ; certificats depuis Key Vault.
- Un pool peut servir plusieurs organisations Azure DevOps.
3.6 Côté GitHub : runners, larger runners, runner groups, ARC#
| Option | À retenir |
|---|---|
| GitHub-hosted runners | VM neuve par job ; job de 6 h maximum ; runs-on: ubuntu-latest |
| Larger runners | Plans Team et Enterprise ; plus de CPU, GPU, ARM ; IP statiques ; Azure private networking (injection dans un VNet Azure) ; images personnalisées ; facturés à la minute |
| Self-hosted runners | Vos machines ; job jusqu'à 5 jours ; ciblés par labels (runs-on: [self-hosted, linux, x64]) ; --ephemeral pour un job par runner |
| Runner groups | Niveau organisation ou entreprise : limiter quels dépôts et workflows peuvent utiliser quels runners ; un runner appartient à un seul groupe |
| Actions Runner Controller (ARC) | Opérateur Kubernetes (AKS) qui crée des runners éphémères à la demande (runner scale sets), voir AKS · GitOps |
Piège de sécurité : jamais de self-hosted runner sur un dépôt public : n'importe qui peut ouvrir une PR depuis un fork et faire exécuter du code sur votre machine. GitHub le déconseille explicitement.
Arbre de décision pour l'examen :
flowchart TD
A["Besoin d'accès à des ressources privées ?"] -->|non| B["Outils standards suffisent ?"]
B -->|oui| C["Microsoft-hosted agents / GitHub-hosted runners"]
B -->|"non, grosses machines"| D["GitHub-hosted agents ou larger runners"]
A -->|oui| E["Machines on-premises ou matériel spécifique ?"]
E -->|oui| F["Self-hosted agents / runners"]
E -->|"non, Azure"| G["Managed DevOps Pools (VNet) / larger runners avec Azure private networking"]
4. Relier un dépôt GitHub à Azure Pipelines#
4.1 Les trois façons de s'authentifier à GitHub#
| Méthode | Identité qui écrit les statuts | Quand l'utiliser |
|---|---|---|
| Azure Pipelines GitHub App (recommandée) | L'app Azure Pipelines | Cas par défaut : statuts riches en GitHub Checks, droits par dépôt choisis à l'installation, builds de PR de forks gérés |
| OAuth | Votre compte GitHub | Quand on ne peut pas installer l'app dans l'organisation GitHub ; statuts simples |
| Personal access token (PAT) | Le propriétaire du PAT | À éviter : jeton à faire tourner, lié à une personne |
Créer le pipeline : Pipelines → New pipeline → GitHub (YAML), choisir le dépôt, installer l'app si demandé, partir d'un
modèle ou d'un fichier existant ; Azure Pipelines peut committer azure-pipelines.yml dans le dépôt. Pour un dépôt GitHub
référencé dans resources.repositories (templates, deuxième dépôt), il faut une service connection GitHub (endpoint:).
4.2 Protéger main avec le pipeline#
- Le YAML contient un trigger
prversmain(section 5.2). - Dans GitHub, une branch protection rule ou un ruleset sur
mainexige le status check du pipeline avant le merge. - Résultat : chaque PR est validée par Azure Pipelines, le résultat apparaît dans l'onglet Checks de la PR.
Commentaires de PR utiles : /azp run (relancer tous les pipelines liés), /azp run <nom> (un seul) ; seuls les membres
autorisés peuvent les utiliser.
4.3 Les PR venant de forks#
Dans Edit → Triggers du pipeline (onglet Pull request validation) :
| Réglage | Valeur prudente | Pourquoi |
|---|---|---|
| Build pull requests from forks of this repository | Activé si contributions externes | Sinon aucune validation des PR de forks |
| Make secrets available to builds of forks | Désactivé (défaut) | Un fork pourrait exfiltrer les secrets |
| Make fork builds have the same permissions as regular builds | Désactivé | Jeton du job limité |
| Require a team member's comment before building a pull request | Activé pour les dépôts publics | Un humain relit avant d'exécuter du code inconnu |
Et toujours : pas d'agent self-hosted pour ces builds.
5. Les règles de déclenchement (triggers)#

5.1 Le trigger CI#
trigger:
batch: true # pendant un run, les pushes suivants sont regroupés en un seul run
branches:
include: [main, releases/*]
exclude: [releases/old*]
paths:
include: [src/*] # sensible à la casse ; pas de variables
exclude: [docs/*]
tags:
include: [v*] # les tags ne déclenchent rien sans cette ligne
| Règle | Détail |
|---|---|
Pas de section trigger |
CI implicite sur toutes les branches, sauf si le réglage Disable implied YAML CI trigger est activé (organisation ou projet) |
trigger: none |
Plus aucun déclenchement CI (pipeline manuel, planifié ou déclenché par une ressource) |
exclude sans include |
Équivaut à include: ['*'] puis exclusion |
| Branches et tags | Un push qui correspond à l'un ou l'autre déclenche |
| Templates | Les triggers ne se déclarent que dans le fichier principal, jamais dans un template |
| Version du YAML utilisée | CI : celle de la branche poussée ; PR : celle de la branche source de la PR |
| Messages de commit | [skip ci], [ci skip], ***NO_CI***… dans le message : pas de run CI |
5.2 Le trigger PR#
pr:
branches:
include: [main]
paths:
exclude: [docs/*]
drafts: false # GitHub : ne pas valider les PR en brouillon
Le mot-clé pr ne vaut que pour GitHub et Bitbucket Cloud. Pour Azure Repos, la validation de PR se configure par
une branch policy → Build validation sur la branche cible (chapitre 02) ; un pr: dans le YAML y est ignoré.
5.3 Les schedules#
schedules:
- cron: '0 2 * * Mon-Fri' # toujours en UTC
displayName: Nightly
branches:
include: [main]
always: false # défaut : ne tourne que si le code a changé depuis le dernier run planifié réussi
- Les horaires sont en UTC : un « 2 h à Paris » s'écrit
0 0ou0 1selon l'heure d'été. - Si des schedules sont définis dans l'UI du pipeline, ils remplacent ceux du YAML.
Build.CronSchedule.DisplayNameindique quel schedule a lancé le run ;Build.ReasonvautSchedule.
5.4 Les triggers par ressources#
resources:
pipelines:
- pipeline: appci # alias local
source: App-CI # nom du pipeline amont dans le projet
trigger:
branches:
include: [main]
stages: [Build] # se déclencher quand ce stage amont est terminé
tags: [release] # seulement les runs amont portant ce tag
repositories:
- repository: config
type: git
name: Platform/app-config
trigger:
branches:
include: [main] # un push dans un autre dépôt Azure Repos déclenche ce pipeline
Autres ressources déclenchantes : containers (nouvelle image dans Azure Container Registry), packages (nouveau
package), webhooks (appel HTTP entrant d'un système externe via une service connection Incoming WebHook). Dans le pipeline
aval, les artifacts du pipeline amont se récupèrent avec - download: appci.
5.5 Les équivalents GitHub Actions#
| Besoin | GitHub Actions |
|---|---|
| Push, PR, filtres | on: push / pull_request avec branches, branches-ignore, paths, paths-ignore, tags |
| Planifié | on: schedule: - cron: '…' (UTC, branche par défaut) |
| Manuel avec saisie | on: workflow_dispatch: inputs: |
| Après un autre workflow | on: workflow_run: workflows: [ci] types: [completed] |
| Événement externe | on: repository_dispatch (appel d'API) |
| Appelé par un autre workflow | on: workflow_call (section 9.5) |
6. Écrire un pipeline en YAML#
6.1 Un pipeline complet, commenté#
# azure-pipelines.yml : build .NET puis déploiement App Service en dev puis prod
trigger:
branches:
include: [main]
parameters:
- name: runTests
displayName: Lancer les tests
type: boolean
default: true
variables:
- group: app-common # variable group de la Library (section 9.4)
- name: buildConfiguration
value: Release
stages:
- stage: Build
pool:
vmImage: ubuntu-latest
jobs:
- job: Build
steps:
- task: DotNetCoreCLI@2
inputs:
command: publish
projects: src/**/*.csproj
arguments: -c $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)
- ${{ if parameters.runTests }}: # insertion à la compilation : le step n'existe pas si false
- task: DotNetCoreCLI@2
inputs:
command: test
projects: tests/**/*.csproj
- publish: $(Build.ArtifactStagingDirectory)
artifact: drop
- stage: DeployDev
dependsOn: Build
jobs:
- deployment: Web
environment: dev
pool:
vmImage: ubuntu-latest
strategy:
runOnce:
deploy:
steps: # l'artifact « drop » est téléchargé automatiquement
- task: AzureWebApp@1
inputs:
azureSubscription: sc-dev-wif # service connection (workload identity federation)
appName: app-contoso-dev
package: $(Pipeline.Workspace)/drop/**/*.zip
- stage: DeployProd # même deployment job que DeployDev, avec environment: prod,
dependsOn: DeployDev # sc-prod-wif et app-contoso-prod (en vrai : un template, section 9)
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
Les approvals and checks de la prod ne s'écrivent pas ici : ils sont posés sur l'environment prod (section 10).
6.2 Variables : trois syntaxes, trois moments#

| Syntaxe | Évaluée | Où l'utiliser | Si la variable n'existe pas |
|---|---|---|---|
${{ variables.x }} (template expression) |
À la compilation du YAML, avant la mise en file | Structure : if, each, choix de template, clé ou valeur |
Chaîne vide |
$[ variables.x ] (runtime expression) |
Au démarrage du job ou du stage | Conditions, variables calculées à partir de sorties | Chaîne vide |
$(x) (macro) |
Juste avant chaque task | Entrées de tasks, scripts | Reste écrit $(x) |
- Une variable est une chaîne, modifiable par les steps :
echo "##vso[task.setvariable variable=x]valeur". La nouvelle valeur n'est visible qu'aux steps suivants. - Dans les scripts, les variables non secrètes sont aussi des variables d'environnement (nom en majuscules,
.remplacé par_:$BUILD_SOURCEBRANCH). Les secrets ne le sont pas : il faut les passer explicitement (env:), voir chapitre 07. readonly: trueempêche un step de modifier une variable.- Priorité quand le même nom existe à plusieurs endroits (du plus fort au plus faible) : job (YAML) > stage (YAML) > racine du pipeline (YAML) > valeur saisie au lancement (queue time) > variable de l'UI du pipeline. Une variable du YAML ne peut donc pas être changée au lancement : pour cela, la définir dans l'UI avec Let users override this value when running this pipeline, ou mieux, utiliser un parameter.
- Deux variable groups liés au même niveau qui ont une variable de même nom : résultat imprévisible, éviter les doublons.
6.3 Parameters#
parameters:
- name: environment
type: string
default: dev
values: [dev, qa, prod] # liste déroulante dans « Run pipeline »
- name: regions
type: object
default: [westeurope, northeurope]
stages:
- ${{ each region in parameters.regions }}: # un stage par région, généré à la compilation
- stage: Deploy_${{ region }}
jobs:
- job: Deploy
steps:
- script: echo "Déploiement ${{ parameters.environment }} en ${{ region }}"
| Parameters | Variables | |
|---|---|---|
| Type | Typés : string, number, boolean, object, step, stepList, job, jobList, stage, stageList… |
Toujours des chaînes |
| Modifiables pendant le run | Non (immuables après la mise en file) | Oui |
| Disponibles à la compilation | Oui : pilotent if et each |
Seulement les variables définies dans le YAML et certaines variables prédéfinies |
| Secrets | Jamais | Oui (variables secrètes, variable groups, Key Vault) |
Microsoft recommande les parameters plutôt que les variables settable at queue time pour les choix de l'utilisateur : valeurs contrôlées, typées, non modifiables par un step.
6.4 Conditions et expressions#
- job: Notify
dependsOn: Deploy
condition: failed() # seulement si Deploy a échoué
- job: Cleanup
dependsOn: Deploy
condition: always() # même en cas d'échec ou d'annulation
- job: Release
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'))
- Condition par défaut :
succeeded(). Écrire une condition remplace la condition par défaut : une conditioneq(variables['Build.Reason'], 'Manual')seule fait tourner le job même si le précédent a échoué. Toujours combiner avecsucceeded()si c'est voulu. - Fonctions de statut :
succeeded(),failed(),succeededOrFailed(),always(),canceled(). - Fonctions utiles :
eq,ne,and,or,not,in,contains,startsWith,endsWith,coalesce,format,counter(numéro de version auto-incrémenté). - Noms de variables avec un point : syntaxe index obligatoire dans les expressions :
variables['Build.SourceBranch'].
6.5 Les variables prédéfinies à connaître#
| Variable | Contenu |
|---|---|
Build.SourceBranch / Build.SourceBranchName |
refs/heads/main / main |
Build.Reason |
IndividualCI, BatchedCI, PullRequest, Schedule, Manual, ResourceTrigger… |
Build.BuildId, Build.BuildNumber |
Identifiant du run, numéro affiché (personnalisable avec name:) |
Build.ArtifactStagingDirectory |
Dossier local où préparer les artifacts |
Pipeline.Workspace |
Racine de travail ; artifacts téléchargés dans $(Pipeline.Workspace)/<artifact> |
System.PullRequest.TargetBranch |
Branche cible d'une PR |
System.AccessToken |
Jeton du job (identité Build Service), à mapper explicitement dans env: |
7. Ordre d'exécution, parallélisme et multi-stage#

7.1 Les règles d'ordre#
| Niveau | Par défaut | Changer l'ordre |
|---|---|---|
| Stages | Séquentiels, dans l'ordre du fichier | dependsOn: X ; dependsOn: [] pour démarrer tout de suite (parallèle) ; liste pour un fan-in |
| Jobs d'un stage | Parallèles (si assez d'agents et de parallel jobs) | dependsOn entre jobs |
| Steps d'un job | Séquentiels | Impossible de paralléliser des steps : en faire des jobs |
stages: # jobs omis pour la lisibilité
- stage: Build
- stage: DeployUS
dependsOn: Build
- stage: DeployEU
dependsOn: Build # en parallèle de DeployUS (fan-out)
- stage: Smoke
dependsOn: [DeployUS, DeployEU] # attend les deux (fan-in)
- stage: Lint
dependsOn: [] # indépendant, démarre dès le début
- stage: Prod
dependsOn: Smoke
trigger: manual # stage lancé à la main, quand on veut
- Un pipeline doit avoir au moins un stage sans dépendance.
trigger: manualsur un stage : il ne démarre qu'à la demande ; le run se termine quand il ne reste que des stages manuels.isSkippable: false: le stage ne peut pas être décoché dans Stages to run (ex. scan de sécurité imposé).- On peut relancer un stage échoué (Rerun failed jobs) sans tout reconstruire.
7.2 Paralléliser : matrix et multi-agent#
jobs:
- job: Test
strategy:
matrix:
linux:
imageName: ubuntu-latest
windows:
imageName: windows-latest
mac:
imageName: macOS-latest
maxParallel: 2 # au plus 2 copies en même temps
pool:
vmImage: $(imageName)
steps:
- script: dotnet test
matrix: une copie du job par entrée (jusqu'à 256 jobs par stage) ;maxParallellimite la consommation de parallel jobs.parallel: 4: 4 copies identiques du job, pour découper une suite de tests (variablesSystem.JobPositionInPhase,System.TotalJobsInPhase).- Une variable ne peut pas alimenter une
matrix(elle n'existe qu'au début de chaque job) ; une sortie d'un job précédent le peut. - Le vrai plafond est le nombre de parallel jobs de l'organisation : 10 jobs parallèles possibles, 1 parallel job acheté → ils passent un par un.
7.3 Passer une valeur d'un job ou d'un stage à l'autre#
stages:
- stage: Build
jobs:
- job: Package
steps:
- bash: echo "##vso[task.setvariable variable=version;isOutput=true]1.4.$(Build.BuildId)"
name: setver # le nom du step fait partie de la référence
- job: Sign
dependsOn: Package
variables:
version: $[ dependencies.Package.outputs['setver.version'] ] # même stage
steps:
- script: echo $(version)
- stage: Deploy
dependsOn: Build # obligatoire pour lire les sorties de Build
variables:
version: $[ stageDependencies.Build.Package.outputs['setver.version'] ] # autre stage
jobs:
- job: Show
steps:
- script: echo $(version)
isOutput=trueest obligatoire pour qu'une variable sorte du job.- Même stage :
dependencies.<job>.outputs['<step>.<var>']; autre stage :stageDependencies.<stage>.<job>.outputs[...]. - Pour une sortie d'un deployment job, la référence inclut le nom du hook ou du job (
outputs['<job>.<step>.<var>'], ououtputs['<hook>_<ressource>.<step>.<var>']selon la stratégie) : vérifier la doc avant d'écrire. - Le stage lecteur doit dépendre (
dependsOn) du stage producteur.
7.4 Jobs sans agent, timeouts, artifacts et cache#
Agentless jobs (pool: server) : ils tournent sur le service Azure Pipelines, sans agent. Tasks possibles : Delay,
InvokeRESTAPI, AzureFunction, ManualValidation, AzureMonitor (alertes), PublishToAzureServiceBus, queryWorkItems.
Durée maximale : 30 jours.
- job: WaitForDba
pool: server
timeoutInMinutes: 1440 # 1 jour
steps:
- task: ManualValidation@1
inputs:
notifyUsers: '[email protected]'
instructions: 'Valider le script de migration avant la mise en production'
onTimeout: reject
Timeouts : timeoutInMinutes (défaut 60) ; 0 = le maximum possible (illimité en self-hosted, 60 ou 360 min en
Microsoft-hosted selon gratuit ou payant) ; cancelTimeoutInMinutes pour laisser le temps de nettoyer.
Artifacts : publish / download (pipeline artifacts, rapides, liés au run). Dans un deployment job, les artifacts du run
sont téléchargés automatiquement (- download: none pour l'éviter).
Cache (Cache@2) : restaure un dossier (packages npm, NuGet, Maven) à partir d'une clé ; mise à jour en fin de job
réussi seulement si la clé était absente ; le cache expire après 7 jours sans usage ; pour le vider, changer la clé.
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json' # change dès que le lock change
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
- script: npm ci
7.5 Côté GitHub Actions#
| Besoin | GitHub Actions |
|---|---|
| Ordre des jobs | needs: [build, lint] (sinon parallèles) |
| Condition | if: ${{ success() }}, failure(), always(), cancelled() ; if: always() pour un job de nettoyage |
| Matrice | strategy.matrix (max 256 jobs), max-parallel, fail-fast, include / exclude |
| Sortie d'un job | step : echo "version=1.4" >> "$GITHUB_OUTPUT" ; job : outputs: version: ${{ steps.setver.outputs.version }} ; lecture : needs.package.outputs.version |
| Limiter les exécutions simultanées | concurrency: group: … cancel-in-progress: true ; queue: max garde une file (100 en attente au plus) au lieu d'annuler |
| Durées | job GitHub-hosted 6 h, self-hosted 5 jours, run complet 35 jours |
| Cache | actions/cache (10 Go par dépôt), cache intégré des actions setup-* |
8. Scénarios complexes : hybride, images de VM, agents self-hosted#
8.1 Pipelines hybrides#
« Hybride » recouvre trois réalités à l'examen :
Cloud + on-premises dans le même pipeline : build sur Microsoft-hosted (rien à gérer), déploiement sur un pool self-hosted dans le datacenter (ligne de vue vers les serveurs). Chaque job choisit son
pool.Même structure que la section 6.1 ; seul le
poolchange d'un stage à l'autre :vmImage: ubuntu-latestpour le build,name: pool-datacenter-paris(self-hosted, HTTPS sortant seulement) pour le deployment job vers l'environmentonprem-prod.GitHub Actions + Azure Pipelines : la CI dans Actions, la CD (avec ses checks) dans Azure Pipelines. L'action
Azure/pipelinesdéclenche un pipeline Azure Pipelines depuis un workflow (elle demande un PAT Azure DevOps rangé dans les secrets GitHub, scope minimal, rotation) ; dans l'autre sens, un pipeline peut lancer un workflow par l'APIworkflow_dispatch.Classic + YAML : un pipeline YAML déclenché par la fin d'un ancien build classic (ressource
pipelines), le temps de la migration (chapitre 06).
8.2 Les « VM templates » : images d'agents et déploiements sur VM#
Le guide ne définit pas le terme ; deux sens sont utiles :
Images personnalisées pour les agents : partir des modèles Packer publiés par Microsoft et GitHub (dépôt
actions/runner-images, qui produit les images des Microsoft-hosted agents et GitHub-hosted runners), y ajouter ses outils, publier l'image dans une Azure Compute Gallery (versionnée, répliquée), puis la donner à un Managed DevOps Pool ou à un scale set. Un pipeline planifié reconstruit l'image chaque mois : correctifs de sécurité et outils à jour, sans toucher aux agents à la main.Déployer sur des VM : ajouter les VM comme resources de type Virtual machine dans un environment (un script installe un agent sur chaque VM, avec des tags :
web,api), puis utiliser un deployment job avec la stratégie rolling :- deployment: Web environment: name: prod-vms resourceType: VirtualMachine tags: web # seulement les VM marquées « web » strategy: rolling: maxParallel: 25% # un quart des VM à la fois deploy: steps: - script: sudo systemctl restart contoso-webLes deployment groups font la même chose en classic (release pipelines) ; en YAML, ce sont les environments.
Sens plus large, si la question parle de créer les VM : templates Bicep / ARM ou modules Terraform déployés par le pipeline (IaC, chapitre 06).
8.3 Agents et runners self-hosted dans un scénario#
| Contrainte de l'énoncé | Réponse |
|---|---|
| Déployer sur un AKS privé ou un SQL en Private Endpoint | Agent dans le VNet : MDP avec injection VNet (ou self-hosted) |
| Serveurs on-premises sans entrée depuis Internet | Self-hosted agent on-premises : HTTPS sortant uniquement |
| Logiciel sous licence installé sur un poste précis | Self-hosted, capability personnalisée + demands |
| Builds iOS / macOS | Microsoft-hosted macOS-latest, GitHub-hosted (arm64), ou Mac self-hosted |
| Beaucoup de jobs courts, coûts à maîtriser, isolation | Agents éphémères : MDP stateless, conteneur --once, runners ARC sur AKS |
| GitHub, dépôts privés, accès à un VNet Azure | Larger runners avec Azure private networking, ou self-hosted / ARC dans le VNet |
| Isoler les runners d'une équipe sensible | Runner group limité à ses dépôts (GitHub) ; pool dédié avec permissions (Azure DevOps) |
Container jobs : faire tourner les steps d'un job dans un conteneur (outils figés, même environnement partout), sur un
agent Linux ou Windows qui a Docker ; services: démarre des conteneurs annexes (base de test).
pool:
vmImage: ubuntu-latest
container: mcr.microsoft.com/dotnet/sdk:8.0 # les steps tournent dans ce conteneur
steps:
- script: dotnet --info
9. Éléments réutilisables : templates, task groups, variables, variable groups#

9.1 Templates d'inclusion (includes)#
Un fichier YAML de stages, jobs, steps ou variables, avec des parameters, inséré là où on l'appelle.
# templates/build-dotnet.yml
parameters:
- name: configuration
type: string
default: Release
- name: projects
type: string
steps:
- task: DotNetCoreCLI@2
inputs:
command: build
projects: ${{ parameters.projects }}
arguments: -c ${{ parameters.configuration }}
# azure-pipelines.yml
steps:
- template: templates/build-dotnet.yml
parameters:
projects: src/**/*.csproj
- Template d'un autre dépôt : déclarer le dépôt dans
resources.repositorieset appeler- template: build-dotnet.yml@templates. Ancrer sur un tag ou un commit (ref: refs/tags/v1) pour qu'une modification du dépôt central ne casse pas tous les pipelines d'un coup. - Limites : 100 fichiers YAML inclus au plus, 100 niveaux d'imbrication, 20 Mo de mémoire pour analyser le YAML (en pratique 0,6 à 2 Mo de fichiers).
- Templates de variables :
variables: - template: vars/prod.yml.
9.2 Templates d'extension (extends) : imposer la structure#
# secure.yml, dans le dépôt Platform/pipeline-templates
parameters:
- name: buildSteps
type: stepList # l'équipe fournit ses steps, le template les encadre
default: []
stages:
- stage: Build
jobs:
- job: Build
steps:
- script: echo "contrôles imposés avant le build"
- ${{ each step in parameters.buildSteps }}:
- ${{ step }}
- script: echo "scan imposé après le build"
target:
commands: restricted # ce step ne peut pas lancer de logging commands dangereuses
# azure-pipelines.yml de l'équipe
resources:
repositories:
- repository: templates
type: git
name: Platform/pipeline-templates
ref: refs/tags/v1
extends:
template: secure.yml@templates
parameters:
buildSteps:
- task: DotNetCoreCLI@2
inputs:
command: build
- Un seul
extendspar pipeline, au niveau racine. - Le template peut parcourir les steps reçus et refuser certains types (par exemple interdire
scriptet n'accepter que des tasks), injecter des scans avant et après. - Pour le rendre obligatoire : check Required template sur la service connection ou l'environment de production
(section 10) ; un pipeline qui n'étend pas
secure.yml@templatesà la bonne référence est bloqué.
| Includes | Extends | |
|---|---|---|
| But | Réutiliser, éviter le copier-coller | Imposer une structure et des contrôles |
| Combien | Autant qu'on veut, à tous les niveaux | Un par pipeline |
| Qui garde la main | L'auteur du pipeline | L'auteur du template |
| Contrôle possible | Aucun | Required template check |
9.3 Task groups (classic)#
Un task group regroupe plusieurs tasks d'un pipeline classic en une seule, avec des paramètres, dans Pipelines → Task groups. Ils n'existent pas en YAML : leur équivalent est le template de steps. Lors d'une migration, un task group devient un template (chapitre 06). Les nouvelles organisations peuvent désactiver la création de pipelines classic (et donc des task groups).
9.4 Variables partagées et variable groups#
Variable group : ensemble de variables stocké dans Pipelines → Library, utilisable par plusieurs pipelines.
variables:
- group: app-prod # le pipeline doit être autorisé (Pipeline permissions du groupe)
- name: region
value: westeurope
- C'est une ressource protégée : autorisation par pipeline, sécurité par rôle, et checks possibles (approval, branch control).
- Option Link secrets from an Azure key vault as variables : les secrets restent dans Key Vault, le groupe n'en garde que
les noms ; valeurs lues à l'exécution par la service connection (droits Get et List sur les secrets, ou rôle RBAC
Key Vault Secrets User). Détails et alternatives (task
AzureKeyVault@2, OIDC) dans le chapitre 07. - Variables secrètes : masquées dans les logs, non exposées en variables d'environnement, non transmises aux builds de forks ; jamais dans un parameter ni dans le YAML.
- Lier un groupe à un seul stage : déclarer
- group:dans lesvariablesde ce stage (ex.app-prodseulement dans DeployProd), ou utiliser une insertion conditionnelle${{ if }}.
9.5 Côté GitHub : reusable workflows et composite actions#

# contoso/ci/.github/workflows/deploy.yml : le reusable workflow
on:
workflow_call:
inputs:
environment:
type: string
required: true # outputs: possibles aussi (on:workflow_call:outputs, relayés depuis un job)
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- run: echo "déploiement en ${{ inputs.environment }}"
# workflow appelant
jobs:
prod:
uses: contoso/ci/.github/workflows/deploy.yml@<sha-du-commit> # SHA : le plus sûr ; pas d'expression dans uses
with:
environment: production
secrets: inherit
# .github/actions/setup-tf/action.yml : une composite action
name: setup-tf
inputs:
version:
required: true
runs:
using: composite
steps:
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ inputs.version }}
- run: terraform version
shell: bash # shell obligatoire pour un run dans une composite action
| Reusable workflow | Composite action | |
|---|---|---|
| Contient | Des jobs complets (runner, environment, permissions) | Des steps seulement |
| Appelé | Au niveau d'un job (jobs.<id>.uses) |
Comme un step (steps[*].uses) |
| Secrets | Oui (secrets: ou secrets: inherit) |
Non (passer des inputs) |
| Journaux | Chaque job et step visible | Vu comme un seul step |
| Imbrication | Jusqu'à 10 niveaux | Jusqu'à 10 composite actions imbriquées |
| Partage | Dépôt public, ou interne / privé avec accès accordé | Idem, et Marketplace |
Pour partager des variables : variables de configuration au niveau organisation, dépôt ou environment (${{ vars.NOM }}) ;
pour des secrets : secrets d'organisation, de dépôt ou d'environment.
10. Environments, approvals and checks#

10.1 Les environments Azure Pipelines#
Un environment (Pipelines → Environments) est la cible logique d'un déploiement : dev, qa, prod. Il apporte :
- l'historique des déploiements (quel run, quels commits et work items sont arrivés en prod) : traçabilité ;
- des resources optionnelles : namespaces Kubernetes (AKS ou autre) et Virtual machines (agent installé) ;
- des approvals and checks et une sécurité par rôle ;
- il est créé automatiquement au premier déploiement s'il n'existe pas (sans checks : le créer avant pour la prod).
Un environment n'est utilisé que par un deployment job (- deployment: + environment:), qui ajoute une strategy :
| Strategy | Hooks | Usage |
|---|---|---|
runOnce |
preDeploy, deploy, routeTraffic, postRouteTraffic, on: success / failure |
Cas courant |
rolling |
Idem | VM seulement, maxParallel (nombre ou %) |
canary |
Idem, avec increments: [10, 20] |
Kubernetes, montée progressive (chapitre 05) |
10.2 Approvals and checks : qui, où, dans quel ordre#
- Posés hors du YAML, par le propriétaire de la ressource, sur : environments, service connections, agent pools, repositories, variable groups, secure files. L'auteur du YAML ne peut ni les voir ni les contourner en modifiant le fichier.
- Évalués avant le stage qui utilise la ressource ; tous les checks de toutes les ressources du stage doivent passer.
- Cinq catégories, dans cet ordre ; dans une catégorie, les checks tournent dans leur ordre de création et sont réévalués à leur intervalle de retry ; un échec définitif (approbation refusée) ou un timeout = stage non exécuté (on peut relancer le stage après un timeout).
| Catégorie | Checks | Exemple d'usage |
|---|---|---|
| 1. Static checks | Branch control (branches autorisées, branche protégée exigée), Required template, Evaluate artifact (politique sur une image de conteneur) | Seule main déploie en prod ; seuls les pipelines qui étendent le template sécurisé |
| 2. Pre-check approvals | Approval évaluée avant les checks dynamiques | Valider avant de consommer des ressources de test |
| 3. Dynamic checks | Approval, Invoke Azure Function, Invoke REST API, Business Hours, Query Azure Monitor alerts | Ticket de change ServiceNow ouvert ; pas d'alerte active ; seulement en heures ouvrées |
| 4. Post-check approvals | Approval après les checks dynamiques | L'humain valide en voyant le résultat des checks automatiques |
| 5. Exclusive lock | Un seul run à la fois sur la ressource | Deux pipelines ne déploient pas en même temps sur la même prod |
L'approval : approbateurs (utilisateurs ou groupes : un seul membre du groupe suffit), nombre minimal, instructions, option Approvers can't approve their own runs, timeout (30 jours par défaut ; sans réponse, le stage est sauté) ; l'approbateur peut différer (Defer approval) jusqu'à une heure donnée. En urgence (hotfix), Approval, Business Hours, Invoke Azure Function et Invoke REST API peuvent être contournés (Bypass check), seulement par un utilisateur qui a la permission Administrator sur la ressource ; le nom de celui qui a contourné reste visible dans le panneau des checks.
L'exclusive lock se règle dans le YAML, au niveau pipeline ou stage :
lockBehavior: sequential # chaque run attend son tour ; défaut runLatest : seul le plus récent passe, les anciens sont annulés
Au niveau d'un stage, runLatest n'annule que les runs de la même branche.
10.3 ManualValidation, checks ou conditions : que choisir ?#
| Besoin | Outil |
|---|---|
| Une personne doit valider avant la prod, quel que soit le pipeline qui y déploie | Approval sur l'environment ou la service connection |
| Une pause en plein milieu d'un pipeline, décidée par l'auteur | Task ManualValidation dans un agentless job |
| Une règle technique (branche, heure, alerte, ticket) | Check sur la ressource |
| Un choix logique du pipeline (branche, raison du run) | condition du stage ou du job |
| Lancer la prod plus tard, à la main | Stage trigger: manual (+ approval si contrôle humain obligatoire) |
Séquence d'un run vers la production :
sequenceDiagram
participant P as Pipeline run
participant E as Environment prod
participant A as Approbateur
participant G as Agent
P->>E: le stage DeployProd demande la ressource
E->>E: Branch control et Required template
E->>E: Business Hours et Azure Monitor alerts
E->>A: Approval demandée
A-->>E: Approve
E->>E: Exclusive lock obtenu
E-->>P: feu vert
P->>G: deployment job runOnce
G-->>E: déploiement enregistré dans l'historique
10.4 Sécurité des environments#
Rôles sur un environment : Reader (voir), User (l'utiliser dans un pipeline YAML), Creator (rôle de niveau projet : créer des environments ; les Contributors l'ont par défaut), Administrator (gérer les permissions et l'environment ; le créateur l'est d'office). Comme les autres ressources protégées, un environment a des Pipeline permissions : autoriser seulement les pipelines prévus.
10.5 Les environments GitHub#
| Fonction | Détail |
|---|---|
| Required reviewers | Jusqu'à 6 personnes ou équipes ; une approbation suffit ; option Prevent self-review |
| Wait timer | 1 à 43 200 minutes (30 jours) avant le démarrage du job |
| Deployment branches and tags | Toutes, seulement les branches protégées, ou une liste de motifs |
| Custom deployment protection rules (preview) | GitHub Apps tierces qui valident (ex. outil de change, monitoring) ; jusqu'à 6 règles |
| Secrets et variables d'environment | Disponibles seulement aux jobs qui ciblent l'environment, après validation des règles |
| Contournement | Par défaut, les administrateurs peuvent passer outre ; décocher Allow administrators to bypass configured protection rules |
Disponibilité selon le plan (GitHub Docs, 22/10/2026) : sur les dépôts publics, tout est disponible ; en Free, rien sur les dépôts privés ; en Pro et Team, les dépôts privés ont les environments, leurs secrets et les deployment branches and tags, mais pas les required reviewers, le wait timer, l'admin bypass ni les custom rules ; il faut GitHub Enterprise pour les avoir sur des dépôts privés. Un classique des questions « pourquoi l'option n'apparaît pas ».
Dans le workflow : environment: { name: production, url: … } sur le job (exemple en section 1.2), et concurrency: avec
cancel-in-progress: false au niveau du job pour un seul déploiement prod à la fois (l'équivalent de l'exclusive lock).
11. Labs conseillés#
Organisation Azure DevOps (1 parallel job Microsoft-hosted gratuit si liée à un abonnement Azure) et compte GitHub Free. Coût Azure nul sauf les labs 3 et 4 (quelques euros si on supprime tout le jour même). Toujours dans un abonnement de test.
- Pipeline multi-stage et triggers (gratuit) : importer un petit projet dans Azure Repos, écrire le pipeline de la
section 6.1 avec
triggerfiltré parpaths, unscheduleset un parameter booléen. Attendu : un push dansdocs/ne déclenche rien ; le parameter fait apparaître ou disparaître le step de test. Ajouter[skip ci]à un commit. - GitHub + Azure Pipelines (gratuit) : depuis un dépôt GitHub, créer un pipeline avec l'Azure Pipelines GitHub App,
ajouter
pr:surmain, puis une branch protection rule qui exige le check. Attendu : la PR est bloquée tant que le check échoue. Tester/azp run. - Environment protégé : créer
prodavec une Approval, un Business Hours et un Branch control (refs/heads/main), déployer une Web App (plan F1 gratuit ou B1) avec une service connection en workload identity federation. Attendu : le run depuis une autre branche est bloqué au check statique, celui demainattend l'approbation. Nettoyage : supprimer le resource group et la service connection. - Managed DevOps Pool dans un VNet : enregistrer le resource provider
Microsoft.DevOpsInfrastructure, créer un Dev Center et un pool de 1 agent (taille la plus petite, image Azure Pipelines, subnet délégué), lancer un job qui lit un secret d'un Key Vault en Private Endpoint. Attendu : réussi depuis le pool, échec depuisubuntu-latest. Nettoyage : supprimer le resource group (pool, Dev Center, VNet, Key Vault) le jour même. - Réutilisation (gratuit) : extraire le build dans un template d'un dépôt
pipeline-templatesancré sur le tagv1, puis faire un templateextendset un check Required template sur l'environmentprod. Côté GitHub, transformer le job de déploiement en reusable workflow appelé par SHA.
12. Pièges d'examen#
| Situation | Réponse attendue | Pourquoi |
|---|---|---|
pr: dans un YAML sur Azure Repos, rien ne se passe |
Branch policy → Build validation | pr ne vaut que pour GitHub et Bitbucket |
Pipeline qui tourne sur toutes les branches sans trigger |
CI implicite ; écrire trigger ou activer Disable implied YAML CI trigger |
Comportement par défaut |
Variable utilisée dans trigger.branches ou paths |
Impossible | Triggers évalués avant toute variable |
| Trigger déclaré dans un template | Ignoré : le mettre dans le fichier principal | Règle documentée |
| Un push de tag ne lance rien | Ajouter tags: include: |
Les tags ne déclenchent pas par défaut |
| Schedule à 2 h « heure de Paris » décalé | Cron en UTC | Pas de fuseau dans schedules |
| Schedules du YAML ignorés | Des schedules existent dans l'UI | L'UI l'emporte |
| Condition personnalisée : le job tourne alors que le précédent a échoué | Ajouter succeeded() dans and(...) |
La condition remplace la condition par défaut |
| Lire une sortie d'un autre stage | stageDependencies.Stage.Job.outputs['step.var'] + dependsOn + isOutput=true |
Les trois sont nécessaires |
${{ variables.x }} vide alors que x est définie dans un script |
La valeur n'existe qu'à l'exécution : $[ ] ou $( ) |
${{ }} est évalué à la compilation |
| Un secret passé en parameter | Interdit : variable secrète ou Key Vault | Parameters visibles et non masqués |
| Changer une variable du YAML au lancement | Impossible : parameter, ou variable de l'UI overridable | Le YAML a priorité et n'est pas settable at queue time |
| Jobs « parallèles » qui passent un par un | Pas assez de parallel jobs | Le parallélisme est une licence |
| Stage indépendant qui attend le précédent | dependsOn: [] |
Stages séquentiels par défaut |
| Microsoft-hosted agent n'atteint pas un AKS privé | MDP dans le VNet ou self-hosted | Pas de ligne de vue |
| Ouvrir un port entrant pour l'agent on-premises | Aucun : HTTPS sortant | L'agent tire les jobs |
| Self-hosted runner sur un dépôt GitHub public | Jamais | Code de forks exécuté sur la machine |
| Required reviewers absents sur un dépôt privé GitHub Team | Plan Enterprise nécessaire | Disponibilité par plan |
| Approbation exigée par le YAML | Les approvals se posent sur la ressource (environment…), pas dans le YAML | Contrôle du propriétaire, non contournable |
| Deux runs déploient en même temps en prod | Exclusive lock (Azure) ; concurrency (GitHub) |
Sérialiser l'accès |
| Task groups en YAML | N'existent pas : templates de steps | Classic uniquement |
| Deployment groups en YAML | Environments avec resources VM | Classic uniquement |
Plusieurs extends dans un pipeline |
Un seul | Règle du schéma |
| Template central modifié qui casse tous les pipelines | Référencer un tag (ref: refs/tags/v1) ou un SHA |
Versionner les templates |
Reusable workflow appelé avec uses: …@${{ inputs.ref }} |
Interdit : pas d'expression dans uses |
Référence figée |
| Composite action qui a besoin d'un secret | Le passer en input | Pas de contexte secrets dans une composite action |
13. Scénarios corrigés#
1. Le code est sur GitHub. L'entreprise suit tout dans Azure Boards, exige que les déploiements en prod n'aient lieu qu'en heures ouvrées après approbation du CAB, et veut voir le résultat du build dans la PR.
Azure Pipelines avec l'Azure Pipelines GitHub App (statuts en GitHub Checks),
pr:surmain+ branch protection qui exige le check ; environmentprodavec Approval (groupe CAB) et Business Hours.
2. Un pipeline doit déployer sur un AKS privé et lire des secrets dans un Key Vault en Private Endpoint. L'équipe ne veut gérer aucune VM.
Managed DevOps Pool injecté dans un subnet du VNet (images Azure Pipelines, agents stateless). Pas de Microsoft-hosted (pas de ligne de vue), pas de self-hosted (VM à maintenir).
3. Le pipeline a 3 jobs de test indépendants qui durent 20 minutes chacun et s'exécutent l'un après l'autre. Le YAML ne
contient aucun dependsOn entre eux.
Le YAML est déjà parallèle : il manque des parallel jobs. En acheter (ou en ajouter en self-hosted) ;
maxParallelpermet ensuite de plafonner la consommation.
4. Un stage Integration doit démarrer en même temps que Build, et Release doit attendre Build et Integration.
Integration:dependsOn: [];Release:dependsOn: [Build, Integration].
5. Le stage Deploy doit utiliser la version calculée par un script du job Package du stage Build.
Dans le script :
##vso[task.setvariable variable=version;isOutput=true]…, step nommé (name: setver) ; dansDeploy:dependsOn: Buildetversion: $[ stageDependencies.Build.Package.outputs['setver.version'] ].
6. La sécurité veut que tous les pipelines qui déploient en production passent un scan de dépendances, sans dépendre de la bonne volonté des équipes.
Template extends central (scan injecté avant et après les steps de l'équipe,
isSkippable: falsesi stage dédié), ancré sur un tag ; check Required template sur la service connection et l'environment de prod.
7. Un pipeline nocturne doit tourner à 22 h heure de Paris du lundi au vendredi, même sans changement de code.
schedulesavec un cron en UTC (0 20 * * Mon-Frien été,0 21en hiver) etalways: true; vérifier qu'aucun schedule n'est défini dans l'UI.
8. Le pipeline App-CD doit démarrer automatiquement quand le stage Build du pipeline App-CI réussit sur main, et
récupérer son artifact.
resources.pipelinesavecsource: App-CI,trigger: branches: include: [main]etstages: [Build];- download: <alias>.
9. Une organisation sur GitHub Team veut que deux personnes de l'équipe Ops valident les déploiements en production d'un dépôt privé. L'option Required reviewers n'apparaît pas.
Limite de plan : les required reviewers sur dépôt privé demandent GitHub Enterprise. Sinon : passer par Azure Pipelines (approvals), ou une custom deployment protection rule si le plan la permet.
10. Plusieurs équipes copient les mêmes 6 steps de build Node.js dans leurs workflows GitHub, et une équipe impose en plus tout le déploiement (jobs, environment, runner).
Les steps → composite action dans un dépôt partagé ; le déploiement complet → reusable workflow (
on: workflow_call) appelé au niveau du job, référencé par SHA,secrets: inheritou secrets explicites.
11. Une application doit être déployée sur 40 VM Linux on-premises, par quart, en sautant les VM de test.
Environment avec resources Virtual machine (agent installé sur chaque VM, tags
webettest), deployment jobstrategy: rollingavecmaxParallel: 25%ettags: web. Deployment groups seulement en classic.
12. Deux runs successifs du même pipeline se retrouvent à déployer en même temps sur la prod, et l'équipe veut que seul le plus récent passe.
Check Exclusive lock sur l'environment ;
lockBehavior: runLatest(défaut) annule les runs plus anciens en attente ;sequentials'il faut les passer tous, dans l'ordre.
14. Pour aller plus loin#
- GitHub Actions Importer :
gh actions-importer audit azure-devopspuisdry-runetmigratepour estimer et convertir des pipelines Azure DevOps en workflows (chapitre 06). - Templates et sécurité :
templateContextpour passer des propriétés aux stages ou jobs d'un templateextends, injection de decorators (extensions qui ajoutent des steps à tous les pipelines d'une organisation). - Azure DevOps MCP Server : interroger runs, logs et échecs en langage naturel depuis un agent IA (analyse de causes, suggestions de correctifs YAML).
- Historique de déploiement par groupe de stages : propriété
group:d'un stage (Sprint 271). - Runner scale sets (ARC) sur AKS avec runners éphémères et nœuds Spot pour réduire le coût des runners self-hosted.
- Merge queue GitHub et déclencheur
merge_grouppour valider les PR dans l'ordre de fusion.
15. Fiche à retenir#
- Hiérarchie : stage → job (un agent, un parallel job) → step → task ; GitHub : jobs → steps, pas de stage.
- Choix : tout sur GitHub → Actions ; Boards, Test Plans, checks avancés, existant Azure DevOps → Azure Pipelines (qui builde aussi GitHub).
- Agents : Microsoft-hosted (neuf, 60 min gratuit, 360 payant) ; MDP pour le privé sans gérer de VM (recommandé à la place des scale sets) ; self-hosted pour l'on-premises ; GitHub-hosted agents en preview, à la minute.
- Licences : parallel jobs ; gratuit = 1 Microsoft-hosted (1 800 min/mois) + 1 self-hosted (+1 par abonné VS Enterprise).
- L'agent sort en HTTPS 443, aucun port entrant ; il lui faut la ligne de vue vers les cibles privées.
- GitHub : larger runners (IP statiques, Azure private networking), runner groups, ARC ; jamais de self-hosted sur dépôt public.
- GitHub + Azure Pipelines : GitHub App,
pr:+ branch protection exigeant le check ; forks sans secrets. - Triggers : pas de
trigger= CI implicite ;trigger: none; exclude seul = include*; tags non déclenchants par défaut ; pas de variables ; pas dans un template ; cron en UTC, l'UI l'emporte ;pr= GitHub/Bitbucket seulement. -
${{ }}compilation,$[ ]runtime,$( )macro ; parameters typés, immuables, jamais secrets. - Condition personnalisée = remplace
succeeded(). - Stages séquentiels (
dependsOn: []pour paralléliser), jobs parallèles, matrix 256 /maxParallel; sorties :isOutput=true+dependencies/stageDependencies;trigger: manualetisSkippable: falsesur un stage. - Agentless job (
pool: server) : ManualValidation, InvokeRESTAPI, Delay… ;timeoutInMinutes60 par défaut. - Templates : includes (réutiliser, stages/jobs/steps/variables) vs extends (un seul, imposer) + Required template ; ancrer sur un tag ; task groups et deployment groups = classic.
- Variable groups : ressource protégée, liaison Key Vault ; secrets à passer explicitement aux scripts.
- Checks posés hors YAML sur environments, service connections, agent pools, repositories, variable groups, secure files ;
ordre : static → pre-check approvals → dynamic → post-check approvals → exclusive lock (
runLatest/sequential). - Deployment job :
runOnce,rolling(VM),canary; artifacts téléchargés automatiquement. - GitHub environments : 6 reviewers max, wait timer 30 jours, branches autorisées, prevent self-review ; privé = Enterprise.
- Reusable workflow (jobs, secrets, appel au niveau job, 10 niveaux) vs composite action (steps, pas de secrets, Marketplace).
Références#
Pages consultées le 21/10/2026.
Microsoft Learn (Azure Pipelines) :
- Study guide for Exam AZ-400
- Key Azure Pipelines concepts
- Azure Pipelines agents
- Microsoft-hosted agents
- GitHub-hosted agents preview (pay-as-you-go)
- Configure and pay for parallel jobs
- Self-hosted Linux agents
- Run a self-hosted agent in Docker
- Azure Virtual Machine Scale Set agents
- What is Managed DevOps Pools?
- Compare Managed DevOps Pools to Azure Virtual Machine Scale Set agents
- Build GitHub repositories
- Specify events that trigger pipelines
- Configure schedules for pipelines
- Define resources in YAML
- Define variables
- Expressions
- Pipeline conditions
- Use runtime parameters
- Set variables in scripts
- Securely use pipeline variables and parameters
- Add stages, dependencies, and conditions
- stages.stage definition (YAML schema)
- Specify jobs in your pipeline
- jobs.job.strategy definition
- ManualValidation@1
- Pipeline caching
- Publish and download pipeline artifacts
- Container jobs
- Template usage reference
- Use templates for security
- Task groups
- Manage variable groups
- Define approvals and checks
- Create and target an environment
- Deployment jobs
- Deployment groups (classic)
- Secure your Azure Pipelines
- Azure Pipelines, Sprint 243 update (stages manuels, exclusive lock par stage)
GitHub Docs :