Progettare una pipeline di build in YAML significa decidere quando parte, in che ordine esegue, quanto si parallelizza e chi controlla cosa. Su AZ-400 la domanda non è “quale keyword scrivo”, ma “dato questo requisito di feedback loop, isolamento e compliance, quale struttura lo soddisfa e a quale costo”. Diamo per acquisita la sintassi di base e concentriamoci sulle decisioni di design e sui trade-off.
Trigger: scegliere l’evento, non subirlo
Il primo errore di design è lasciare i default. Se non configuri esplicitamente i trigger, CI e PR trigger sono abilitati per impostazione predefinita: ogni push su qualsiasi branch e ogni pull request accendono un run. Per una pipeline solo notturna devi dichiarare trigger: none e pr: none.
La differenza architetturalmente più importante riguarda il PR trigger. In YAML la keyword pr è supportata per GitHub e Bitbucket Cloud; Azure Repos Git non la usa: lì la validazione delle pull request si implementa con le branch policy del repository (build validation). Se il requisito è “nessuna PR può essere completata senza build verde” e il codice sta in Azure Repos, la risposta non è YAML ma una policy di branch. È una scelta di governance, non di sintassi.
I path filter evitano che un monorepo ricostruisca tutto a ogni commit. Trappola: in Git i percorsi sono case-sensitive, e vale anche per i path filter di CI e PR.
Gli scheduled trigger usano sintassi cron, sono valutati in UTC e non tengono conto dell’ora legale: una nightly “alle 3” slitta di un’ora due volte l’anno. Tre vincoli pesano sul design:
- Per impostazione predefinita una schedule non parte se non ci sono state modifiche dall’ultimo run schedulato riuscito. Per un security scan che deve girare comunque, serve
always: true. - Le schedule vanno dichiarate nel file YAML principale, non nei template, e le variabili di pipeline non sono utilizzabili nella loro definizione.
- Le schedule definite nella UI hanno la precedenza su quelle YAML: se esistono entrambe, girano solo quelle della UI. È la causa classica di “la mia cron non parte”.
Sono inoltre valutate per branch a partire dal YAML presente in quel branch: aggiungere release ai branch filter nel file di main non schedula nulla finché lo stesso filtro non compare nel file del branch release. Quando invece una pipeline deve partire al completamento di un’altra, il costrutto è il pipeline resource.
Ordine di esecuzione: dependsOn e condition come contratto
I job dentro uno stage girano in parallelo per default; è dependsOn a imporre un ordine, e ogni pipeline deve contenere almeno un job senza dipendenze. Il design tipico è un grafo, non una catena: fan-out (test, lint e analisi statica in parallelo dopo la build) e fan-in (packaging che attende tutti). Allungare la catena serializza e gonfia il lead time.
Il punto che l’esame ama è il rapporto fra dependsOn e condition. Per default un job gira se tutte le dipendenze, dirette e indirette, sono terminate con successo. Nel momento in cui specifichi una condition personalizzata sostituisci la condizione implicita succeeded(). Un job condizionato solo sul branch di origine gira anche se la dipendenza è fallita e anche se il run è stato annullato: la condizione è vera e nessuno controlla più lo stato del padre. La forma corretta è comporre: and(succeeded(), eq(...)). Le funzioni di stato includono succeeded(), succeededOrFailed() (che non gira su cancellazione), always() (che gira anche su cancellazione) e failed().
Due corollari: se lo stage padre viene saltato, i figli non girano comunque, qualunque sia la loro condizione. E poiché le condizioni sono valutate prima di avviare l’elemento, per passare informazioni fra job serve una output variable marcata isOutput=true, leggibile solo da job che dipendono direttamente da quello che l’ha prodotta.
Parallelismo, matrix e pipeline multi-stage
Le strategie matrix e parallel sono mutuamente esclusive e rispondono a problemi diversi. Matrix moltiplica lo stesso job su combinazioni di variabili — piattaforme, versioni di runtime, region — ed è la risposta a “verifica la stessa build su N configurazioni”; maxParallel limita quante leg girano insieme ed è valido solo con matrix. Parallel duplica il job N volte esponendo System.JobPositionInPhase e System.TotalJobsInPhase, ed è la risposta a “distribuisci una suite lunga su piu agenti”, ma richiede task consapevoli dello slicing.
Il vincolo economico è reale: ogni agente esegue un job alla volta, quindi il fan-out è limitato dai parallel job disponibili. Una matrix 4x3 su un’organizzazione con due parallel job non accorcia il lead time, lo accoda. Quando le combinazioni non sono note in anticipo, la matrix accetta un’espressione runtime con un oggetto JSON serializzato prodotto da un job precedente.
Gli stage sono l’unità di separazione logica e di controllo: separano build da deploy e abilitano approvazioni e check sugli environment. Con agenti ospitati da Microsoft ogni job ottiene un agente nuovo: nulla del filesystem sopravvive fra job, e lo stato passa solo tramite artifact pubblicati e output variable.
Template e variabili: riuso oppure governance
I template servono a due scopi che è bene non confondere. Gli includes template inseriscono contenuto — step, job, stage, variabili — e riducono la duplicazione. Gli extends template definiscono invece uno schema che la pipeline deve rispettare: sono lo strumento di sicurezza e compliance, perché il template centrale può ispezionare i parametri ricevuti e rifiutare costrutti non ammessi, oltre a iniettare step obbligatori di firma o scansione attorno al codice del team. Il presidio si chiude con l’approvazione required template.
I parametri sono espansi a compile time con la sintassi ${{ }}, prima che il run inizi, e supportano tipi come string, boolean, object e stepList. La trappola: uno scalare senza tipo è trattato come stringa, quindi un confronto booleano su un parametro dichiarato senza type: boolean restituisce vero anche quando il valore è la parola false, perché una stringa non vuota è vera in contesto booleano. Dichiara sempre il tipo.
I template ospitati in un altro repository si referenziano tramite resources.repositories. Qui la decisione è il pinning: senza ref esplicito la pipeline usa il branch predefinito, quindi ogni modifica al repo dei template cambia il comportamento di tutte le pipeline consumatrici. Per stabilità si pinna a un tag o a uno SHA. Nota che ne vengono usati solo i file di template: non puoi eseguire script che vivono in quel repo.
Per le variabili, la keyword group collega un variable group. Il punto di design è che un gruppo con segreti è una risorsa protetta: la pipeline deve essere autorizzata a usarlo, altrimenti chiunque possa fare push nel repository potrebbe estrarne il contenuto. I segreti, inclusi quelli di un gruppo collegato ad Azure Key Vault, non sono accessibili direttamente negli script: vanno passati come argomenti o mappati come variabili d’ambiente del task. Dove il valore decide la forma della pipeline anziché configurare un task, preferisci un parametro tipizzato.
Il modello si trasferisce a GitHub Actions, che AZ-400 tratta in parallelo: i reusable workflow invocati con workflow_call sono l’analogo dei template e ammettono fino a dieci livelli di catena. Anche lì i secret raggiungono solo il workflow chiamato direttamente: in catena vanno inoltrati a ogni salto.
Come cade all’esame
- “La build deve validare ogni pull request su un repository Azure Repos Git” → branch policy con build validation; la keyword
prin YAML copre GitHub e Bitbucket Cloud, non Azure Repos. - “La pipeline deve girare solo di notte, mai su push” → schedule cron piu
trigger: noneepr: none, verificando che non esistano schedule nella UI, che hanno la precedenza. Se deve girare anche senza commit, servealways: true. - “Uno stage di notifica deve eseguire anche se il precedente fallisce, ma non quando il run viene annullato” →
succeededOrFailed();always()girerebbe anche su cancellazione. - “Uno stage deve girare solo su main e solo a build riuscita” →
and(succeeded(), eq(...)); la solaeq(...)sostituisce la condizione implicita e lo farebbe partire anche dopo un fallimento. - “Stessa suite su tre versioni di runtime, al massimo due agenti impegnati” →
strategy: matrixconmaxParallel: 2;maxParallelnon si applica aparallel. - “Tutti i team devono usare gli stessi step di firma e non poter eseguire task arbitrari” → extends template con parametro
stepListe required template approval; un includes template non impedisce nulla. - “I segreti di produzione devono essere consumabili solo dalla pipeline di release” → variable group con segreti come risorsa protetta piu pipeline permission, non un gruppo ad accesso aperto.