Infrastructure
Azure DevOps : diagnostiquer un job en attente avant d'ajouter des agents auto-hébergés
Un runbook de production pour séparer capacité parallèle, autorisation du pool, demands, capabilities et éligibilité des agents avant de dimensionner un pool Azure DevOps auto-hébergé.
Un pipeline de production attend un agent depuis vingt minutes. Le pool affiche plusieurs agents auto-hébergés : ajouter une VM semble donc être la correction la plus rapide. Pourtant, cette action peut augmenter le coût sans débloquer le job. L’organisation peut avoir consommé tous ses jobs parallèles, le pipeline peut ne pas être autorisé sur le pool, ou aucun agent en ligne ne peut satisfaire toutes les demands.
Le cas fil rouge est un pipeline de déploiement vers un environnement Azure privé depuis le pool prod-linux. Les autres jobs continuent de tourner, mais une étape reste en file après une modification du pipeline. Le runbook doit identifier la contrainte d’ordonnancement, restaurer une exécution bornée, puis décider entre capacité, autorisation, réparation d’une capability, mise à jour d’agent ou rollback du pipeline.
Figer le contrat d’ordonnancement
Partez du run en attente, pas du dashboard du pool. Relevez l’identifiant du run, le job, l’heure de mise en file, le pool demandé, la branche et la révision du pipeline. Conservez le message exact : « waiting for an agent », « waiting for a parallel job » et « resource authorization required » ne désignent pas le même plan de contrôle.
organization: https://dev.azure.com/example
project: platform
pipeline: deploy-orders
run_id: 18427
job: deploy_prod
queued_at_utc: 2026-09-22T05:42:18Z
pool: prod-linux
pipeline_revision: 4d83c7a
expected_demands:
- Agent.OS -equals Linux
- terraform
- prod-network
change_window:
start_utc: 2026-09-22T05:30:00Z
end_utc: 2026-09-22T07:00:00Z
success: un canari puis un job de production sont affectes a des agents eligibles
rollback: restaurer la revision precedente et le contrat de capabilities Ne redémarrez pas les agents et ne modifiez pas leurs capabilities à ce stade. Ces actions changeraient les preuves utilisées par le scheduler. Comparez plutôt ce run avec la dernière exécution réussie du même pipeline : YAML du job, demands, tâches, nom et version de l’agent, durée d’attente.
Séparer autorisation, concurrence et éligibilité
La présence de machines inactives dans un pool auto-hébergé ne prouve pas qu’un job peut démarrer. Azure DevOps doit franchir trois contrôles :
- le pipeline YAML est autorisé à utiliser le pool ;
- l’organisation dispose d’un job parallèle ;
- au moins un agent actif et en ligne satisfait toutes les demands explicites ou ajoutées par les tâches.
Commencez par les détails du job et la sécurité du pool. Une demande d’autorisation n’est pas un problème de capacité. Si l’accès est légitime, autorisez ce pipeline précisément. Ouvrir le pool à tous les pipelines élargit la frontière d’exécution et ne constitue pas une correction d’incident.
Contrôlez ensuite Organization settings > Pipelines > Parallel jobs et la consommation du pool. Lorsque tous les slots sont occupés, enregistrer un agent supplémentaire ne crée pas de concurrence. Identifiez les jobs actifs, leur owner et leur fin attendue. N’annulez un job que s’il est démontré comme abandonné ou rejouable sans risque.
Enfin, traitez l’éligibilité séparément. Un agent compatible peut être occupé pendant que cinq agents incompatibles restent libres. Le symptôme ressemble à un manque de machines, mais la contrainte porte sur le matching.
Inventorier les agents et leurs capabilities réelles
Utilisez la CLI Azure DevOps pour lire le pool avec les capabilities et les requêtes affectées. Cette collecte doit rester sans écriture.
ORG="https://dev.azure.com/example"
POOL="prod-linux"
az extension add --name azure-devops --only-show-errors
az devops configure --defaults organization="$ORG"
POOL_ID=$(az pipelines pool list --pool-name "$POOL" --query "[0].id" --output tsv)
az pipelines agent list --pool-id "$POOL_ID" --include-capabilities true --include-assigned-request true --include-last-completed-request true --output json > agent-pool-snapshot.json
jq '.[] | {
id,
name,
enabled,
status,
version,
assignedRequest,
lastCompletedRequest,
systemCapabilities,
userCapabilities
}' agent-pool-snapshot.json Lisez le résultat comme le scheduler. Un agent doit être activé, en ligne, libre et compatible avec toutes les demands. Agent.Version, Agent.OS et Agent.OSArchitecture sont aussi des capabilities. Les capabilities utilisateur comme terraform ou prod-network sont des déclarations d’opérateur : elles ne prouvent ni la présence du binaire, ni le routage, ni les droits effectifs.
Ne publiez pas de secret comme capability. Certaines capabilities système sont dérivées des variables d’environnement au démarrage. Utilisez VSO_AGENT_IGNORE pour exclure les variables sensibles ou volatiles qui ne doivent pas être stockées ni réinjectées.
Comparer demands et capabilities
Les demands peuvent être écrites dans le bloc pool ou imposées automatiquement par une tâche. Construisez une matrice explicite entre le job et chaque agent candidat.
Demand agent-01 agent-02 agent-03
Agent.OS = Linux match match match
terraform existe match absent match
prod-network existe match match absent
version minimale de la tache obsolete match match
active et en ligne oui oui non
requete deja affectee oui non non
Eligible maintenant
aucun
Contrainte d'ordonnancement
agent-01 correspond mais il est occupe
agent-02 ne declare pas terraform
agent-03 est hors ligne et ne porte pas prod-network Relisez le diff du pipeline qui précède l’incident. Une demand renommée, une valeur modifiée, une nouvelle tâche ou une comparaison exacte sur Agent.Version peut exclure le pool entier. Les opérations pertinentes sont exists et equals ; encoder une plage de versions ou de la logique shell dans une chaîne ne crée pas une règle de scheduling fiable.
Si un logiciel a été installé après le démarrage de l’agent, conservez ses diagnostics avant de le relancer : la détection des capabilities intervient au démarrage. N’ajoutez jamais une capability utilisateur pour satisfaire artificiellement le scheduler lorsque le binaire sous-jacent manque.
Vérifier la compatibilité des tâches et de l’agent
Une tâche peut nécessiter un agent plus récent sans demand de version visible dans le YAML. Comparez les tâches ajoutées au job avec la capability Agent.Version des agents candidats. Vérifiez également la politique de mise à jour automatique et la compatibilité de l’OS avec la génération d’agent visée.
Mettez d’abord à jour un agent non critique. Replacez-le dans le pool, contrôlez ses capabilities, exécutez le canari, puis avancez par vagues. Une mise à jour simultanée de tout le pool supprime le chemin connu fonctionnel. Si la nouvelle tâche impose la version et que la fenêtre ne permet pas la migration, rollbackez la tâche ou la révision du pipeline au lieu de falsifier la capability.
Prouver le scheduling avec un canari sans effet
Le canari utilise le même pool et les demands voulues, mais n’effectue aucun déploiement. Il valide l’autorisation et l’affectation sans mélanger la reprise du scheduler avec une écriture en production.
trigger: none
pool:
name: prod-linux
demands:
- Agent.OS -equals Linux
- terraform
- prod-network
steps:
- checkout: none
- bash: |
set -euo pipefail
echo "agent=$AGENT_NAME"
echo "version=$AGENT_VERSION"
command -v terraform
terraform version
displayName: Validate agent scheduling contract Le canari doit passer à l’état Running, identifier l’agent attendu et vérifier la réalité derrière chaque label important. Son succès n’autorise pas automatiquement le déploiement. Relancez un seul job de production borné et observez le temps d’affectation, l’identité de l’agent et son nettoyage final avant de libérer le backlog.
Choisir la correction la plus petite
La preuve doit conduire à une seule branche :
Autoriser le pipeline
Le job demande explicitement l'autorisation du pool
Le pipeline est approuve pour cette frontiere d'execution
L'acces reste limite a ce pipeline
Liberer ou acheter de la capacite parallele
Des agents eligibles sont libres
Tous les slots de concurrence sont consommes
L'historique montre une pression durable et non un run bloque
Reparer une capability ou mettre a jour un agent
La capability demandee est legitime
Le binaire ou la version compatible manque reellement
Un canari unitaire et un rollback sont disponibles
Rollbacker le pipeline
Un changement recent de YAML ou de tache a cree une demand accidentelle
La revision precedente fonctionnait sur le pool actuel
La restaurer est moins risquee que modifier tous les agents
Ajouter ou autoscaler des agents
De la capacite parallele reste disponible
Les agents compatibles sont satures sur une fenetre representative
Image, reseau, identite et nettoyage sont deja valides Le scaling arrive en dernier. Tout nouvel agent doit respecter les mêmes routes réseau, outils, frontières d’identité, mises à jour et règles de nettoyage que le parc existant. Sinon, le pool grandit tandis que son contrat diverge.
Valider la reprise et conserver le rollback
La reprise est complète lorsque le job initial est affecté pour une raison comprise, pas après une série de clics sur Run pipeline. Conservez les demands retenues, l’agent choisi, le temps de file, l’état des slots parallèles et la décision d’autorisation.
Rollbackez la couche la plus petite. Restaurez la révision YAML précédente pour retirer une demand accidentelle. Revenez sur la mise à jour d’une tâche si la compatibilité agent n’est pas prête. Désactivez une nouvelle image d’agent si son canari échoue. Révoquez une autorisation seulement si elle visait la mauvaise frontière. Ne supprimez pas des agents et n’ouvrez pas globalement le pool pour faire disparaître la file.
Conclusion
Un job Azure DevOps peut rester en attente alors que des agents sains semblent libres : le scheduling dépend de l’autorisation, de la capacité parallèle et de l’éligibilité exacte des agents. Ajouter des machines n’aide que si les agents compatibles sont réellement saturés et qu’un slot parallèle reste disponible.
La décision de production doit suivre la contrainte prouvée : autoriser étroitement, libérer une capacité identifiée, restaurer une capability réelle, mettre à jour par canari, rollbacker le pipeline ou dimensionner un pool déjà validé. Lorsque cause et chemin de retour sont explicites, la reprise de file devient un changement exploitable plutôt qu’une hausse aveugle du nombre de runners.