5. Rilascio progressivo con health gate#
In questo tutorial
Trasformi un deploy "tutto insieme" in uno progressivo: prima un nodo canary, poi una zona di disponibilità alla volta, con tempi di osservazione e un health gate su sonde HTTP e sul tasso di errori in Prometheus. Dopo il canary una persona approva; se il gate diventa rosso, tutti i nodi tornano alla release precedente.
Tempo: 30 minuti · Serve: quattro server in due AZ raggiungibili via SSH (o il laboratorio dei casi d'uso), Prometheus che legge le metriche dell'applicazione
flowchart LR
K["canary<br/>web-1 · eu-1a"] --> G1{"gate<br/>10 min"}
G1 -->|verde| A{{"approvazione"}}
A --> Z1["rest/eu-1a<br/>web-2"]
Z1 --> G2{"gate<br/>5 min"}
G2 -->|verde| Z2["rest/eu-1b<br/>web-3, web-4"]
Z2 --> G3{"gate<br/>5 min"}
G3 -->|verde| OK([SUCCESS])
G1 & G2 & G3 -->|rosso| RB(["rollback-all"])
Vederlo girare prima
Il laboratorio end-to-end esegue esattamente questo tipo di rollout su due region e cinque AZ, compresi un gate rosso e un rollback: make test-usecases CASES="uc4 uc5" (e uc15 per l'analisi canary su Prometheus). Vedi Casi d'uso.
1. Descrivi la topologia con le etichette#
Region e AZ non sono ambienti separati: sono etichette dei target di un solo ambiente, così governance, inventario e promozione continuano a ragionare su prod. region e az sono riservate; ogni altra etichetta (qui canary) è tua.
plugin: remote-copy
targets:
- {host: web-1.prod.internal, user: deploy, labels: {region: eu-1, az: eu-1a, canary: "true"}}
- {host: web-2.prod.internal, user: deploy, labels: {region: eu-1, az: eu-1a}}
- {host: web-3.prod.internal, user: deploy, labels: {region: eu-1, az: eu-1b}}
- {host: web-4.prod.internal, user: deploy, labels: {region: eu-1, az: eu-1b}}
params:
dest_dir: /opt/hello
layout: releases
ssh_key_secret: deploy-key
prepare_command: 'tar -xzf "$MANENT_ARTIFACT" -C "$MANENT_RELEASE_DIR"'
post_command: "sudo systemctl restart hello"
secrets:
- {name: deploy-key, ref: "vault:secret/data/hello/prod#ssh_key"}
rollback:
enabled: true
2. Definisci le ondate#
La strategia sta nella stessa configurazione di ambiente, che è autorevole: i team applicativi non possono allentarla dal descrittore.
rollout:
waves:
- {name: canary, select: "canary=true", bake: 10m}
- {name: rest, select: "canary!=true", by: az, bake: 5m}
maxUnavailable: 1 # un nodo alla volta dentro ogni AZ
onFailure: rollback-all # stop | rollback-wave | rollback-all
approvalsBetween: [canary] # dopo il canary decide una persona
health:
interval: 30s
failuresToStop: 2 # due letture rosse di fila fermano il rollout
probes:
- {url: "http://{host}:8080/healthz"}
metrics:
- name: error-rate
query: 'sum(rate(http_requests_total{app="{application}",code=~"5..",az="{az}"}[5m])) / sum(rate(http_requests_total{app="{application}",az="{az}"}[5m])) or vector(0)'
max: 0.01
selectsceglie i target di un'ondata con condizioni sulle etichette; ogni target deve appartenere a una sola ondata.by: azdivide l'ondata in uno stadio per AZ. Dentro uno stadio i target vengono aggiornati a lotti dimaxUnavailable.- Dopo ogni stadio il worker attende
bake, leggendo il gate ogniinterval. L'ultima lettura deve essere verde. probeschiamano ogni target dello stadio; l'URL deve iniziare conhttp(s)://{host}, quindi le sonde raggiungono solo i target.metricsesegue una query PromQL per stadio, con{application},{environment},{region}e{az}sostituiti. Una query senza dati è rossa: un gate che non vede traffico non può dire che la release è sana.or vector(0)accetta esplicitamente il silenzio.
rollback-all usa le stesse compensazioni del rollback automatico, quindi richiede rollback.enabled e, per remote-copy, layout: releases.
3. Collega Prometheus#
rollout:
prometheus_url: http://prometheus.monitoring:9090
alertmanager_url: http://alertmanager.monitoring:9093 # facoltativo, per health.alerts
check_timeout: 30s
Senza prometheus_url, un rollout con metriche Prometheus viene rifiutato all'invio. Per usare invece Datadog, New Relic, Elastic, CloudWatch o Azure Monitor vedi Sistemi di metriche.
4. Verifica il piano#
{"name": "canary", "hosts": ["web-1.prod.internal"]}
{"name": "rest/eu-1a", "hosts": ["web-2.prod.internal"]}
{"name": "rest/eu-1b", "hosts": ["web-3.prod.internal", "web-4.prod.internal"]}
Il piano viene calcolato e verificato all'invio: un'ondata che non seleziona nulla, un target in due ondate o un by: az su target senza etichette danno 422, prima di toccare qualunque cosa.
5. Rilascia e segui#
manent deploy -f hello-prod.yml --no-wait
manent status <id> | jq '{state, rollout_progress, rollout: {status: .rollout.status, stage: .rollout.stage}}'
secrets,artifactseqagirano una volta; gli step che toccano i target (dadeployadactivation) girano per ogni lotto, con il nome del passo e del lotto:deploy@canary#0.- Dopo il canary il gate legge sonde e metrica ogni 30 secondi per 10 minuti. Le attese sono durevoli: se il worker riparte, il tempo di osservazione già trascorso non si ripete.
-
Il rollout si mette in pausa:
rollout.statusèpaused-approvale il deploy appare comeAWAITING_APPROVAL. Una persona condeployment.approve(non il richiedente) lo fa proseguire: -
L'ondata
restprocede AZ per AZ, con i suoi gate. Alla fine il deploy èSUCCESSerollout_progressè3/3.
L'interfaccia web mostra le stesse informazioni in tempo reale: stadi, passi, il gate di ogni stadio e il pulsante di approvazione.
6. Quando il gate diventa rosso#
Supponi che la 1.3.0 aumenti il tasso di errori in eu-1a. Alla seconda lettura rossa consecutiva lo stadio fallisce, e lo stato dice perché:
{
"state": "ERROR",
"rollout_progress": "1/3",
"rollout": {
"status": "failed",
"stage": "rest/eu-1a",
"on_failure": "rollback-all",
"stages": [
{"name": "canary", "state": "succeeded"},
{"name": "rest/eu-1a", "state": "failed", "gate": "metric error-rate (eu-1/eu-1a): value 0.034 out of threshold"},
{"name": "rest/eu-1b", "state": "pending"}
]
}
}
Con rollback-all lo stadio fallito e il canary tornano alla release precedente, in ordine inverso; eu-1b non è mai stata toccata. Con stop i nodi già aggiornati resterebbero sulla 1.3.0, e l'inventario segnalerebbe drift.
Per andare oltre#
| Esigenza | Usa |
|---|---|
| Verificare ogni lotto, non solo l'intera AZ | waves[].batchBake (gate per lotto) |
| Lotti di dimensioni diverse per ondata | waves[].maxUnavailable |
| Togliere ogni lotto dal load balancer | traffic.pool (Svuotamento dal load balancer) |
| Region con reti separate | dbos.region e dbos.region_routing (esecuzione nelle region) |
| Un freeze su una sola region | regions nella finestra di freeze: il rollout si ferma prima di quella region |
| Spostare una percentuale di traffico invece dei nodi | strategy: traffic (Rilasci per percentuale), con analisi canary statistica |
Cosa hai imparato#
- La topologia si esprime con le etichette, e un rollout è ondate → stadi → passi → lotti.
- Un health gate combina sonde, metriche e allarmi, e considera rossa l'assenza di dati.
- Approvazioni e freeze possono fermare un rollout tra un'ondata e l'altra.
onFailuredecide cosa succede agli stadi già rilasciati.
Prossimo passo#
Il tutorial 6 rilascia da una pipeline di CI.