Governance dei rilasci#
manent tiene traccia di cosa è in esercizio in ogni ambiente e applica le regole con cui un artefatto ci arriva:
- inventario: la release in esercizio per applicazione e ambiente;
- promozione obbligatoria: in un ambiente si rilascia solo ciò che è già passato da un altro;
- approvazioni: il deploy parte solo dopo l'approvazione di altre persone;
- finestre di freeze: periodi in cui i deploy sono bloccati, con deroga per le emergenze;
- rollback manuale: si torna a una versione precedente con un comando;
- change management: un ticket in ServiceNow o Jira per ogni deploy, con l'eventuale attesa della sua approvazione (
changenella regola, vedi Chat e change management).
Le regole sono nella sezione governance della configurazione e valgono per CLI e API. Con governance.engine: opa si possono anche scrivere in Rego e cambiare a runtime, con revisioni testate, misurate e approvate da un'altra persona: vedi Policy in Rego. Con entrambi i motori le finestre di freeze si possono creare e chiudere a runtime (freeze a runtime).
governance:
rules:
- name: pagamenti
applications: ["pay-*"]
environments: [prod]
requires: [preprod]
approvals: {required: 2, timeout: 48h}
change: {approval: true} # ticket di change approvato nell'ITSM
- name: produzione
applications: ["*"]
environments: [prod]
requires: [preprod]
approvals: {required: 1}
freezes:
- name: fine-anno
reason: chiusura contabile
applications: ["*"]
environments: [prod]
from: 2026-12-20T00:00:00+01:00
to: 2027-01-07T00:00:00+01:00
- name: weekend
applications: ["*"]
environments: [prod]
cron: "0 18 * * FRI"
duration: 62h
timezone: Europe/Rome
Per ogni deploy vale la prima regola (in ordine) la cui applications (pattern qualificati: payments/* per un tenant, i pattern senza prefisso solo per le applicazioni untenanted) e environments corrispondono (e, se presenti, tenants sul tenant proprietario, artifacts e selector sui tag delle applicazioni). Senza regole non ci sono vincoli. I dry-run non sono soggetti alle regole, perché non modificano gli ambienti.
Inventario#
L'inventario si ricostruisce dallo storico dei deploy: per ogni applicazione e ambiente, l'ultimo deploy concluso con successo.
[{
"application": "myapp",
"environment": "prod",
"current": {
"id": "7c0e…", "state": "SUCCESS", "artifact": "myapp", "version": "1.4.0",
"sha256": "2d27fbdf…", "kind": "promotion", "source": "b3a1…",
"requested_by": "token:ci", "updated_at": "2026-10-10T09:12:00Z"
},
"last_failure": {"id": "91aa…", "state": "ERROR", "version": "1.5.0", "rollback": "completed"}
}]
| Campo | Significato |
|---|---|
current |
Ultimo deploy riuscito: versione, sha256 dell'artefatto effettivamente rilasciato, chi l'ha richiesto. |
kind |
deploy, promotion o rollback. Per promozioni e rollback source è il deploy da cui derivano. |
last_failure |
Presente se dopo current c'è un deploy fallito o annullato. Con rollback: completed l'ambiente è tornato a current; altrimenti il suo stato va verificato. |
targets, out_of_sync |
Stato letto dai target, se la riconciliazione è attiva: vedi stato reale dei target. |
drift |
true se last_failure è un rilascio a ondate fermato dopo aver aggiornato una parte delle region o AZ (rollout_progress, es. 2/4): l'ambiente ha versioni diverse. |
Lo storico completo di un ambiente si legge con l'elenco dei deploy:
Note
L'inventario si ricostruisce dagli ultimi 10.000 deploy all'avvio del server e ogni 10 minuti, e nel frattempo segue i deploy nuovi e quelli in corso. Un'applicazione che non viene rilasciata da molto più tempo può non comparire: in quel caso fa fede il sistema di destinazione.
Stato reale dei target#
L'inventario dice cosa manent ha rilasciato. Con reconcile.interval (vedi configurazione) i worker leggono anche cosa c'è davvero sui target, e segnalano quelli cambiati fuori da manent: un deploy manuale, un rollback a mano, un nodo ricostruito con un'immagine vecchia.
- A ogni intervallo, per ogni applicazione e ambiente con una release in esercizio, il worker prende la richiesta del deploy in
current, la fonde con la configurazione di ambiente attuale (così vede i target aggiunti o tolti dopo il deploy) e chiede al plugin la release in esercizio un target alla volta, conreconcile.concurrencyletture insieme. - Gli ambienti con un deploy in corso o in attesa di approvazione dentro un rollout vengono saltati: i loro target stanno cambiando.
- La lettura non modifica nulla: è la stessa che il rollback fa prima del deploy (
CurrentRelease). Serve un plugin che la supporti:remote-copyconlayout: releases,container,kubernetes,ecs,lambda,cloudrun,appservice,containerapps,jboss,windowse i plugin esterni che dichiaranocurrent_release. - Un solo worker per volta legge (lease
reconcile). Condbos.region_routingogni region ha il proprio lease e i suoi worker leggono solo i target della propria region; i target senza region li legge un worker senza region.
L'inventario riporta il risultato in targets, e out_of_sync: true se almeno un target è drifted o missing:
{
"application": "myapp", "environment": "prod",
"current": {"id": "7c0e…", "version": "1.4.0"},
"out_of_sync": true,
"targets": {
"checked_at": "2026-10-17T09:20:00Z", "deployment": "7c0e…", "version": "1.4.0",
"targets": [
{"host": "web-1", "region": "eu-1", "az": "eu-1a", "version": "1.4.0", "state": "in-sync"},
{"host": "web-2", "region": "eu-1", "az": "eu-1b", "version": "1.3.2", "state": "drifted"}
]
}
}
state |
Significato |
|---|---|
in-sync |
Il target ha la versione di current. |
drifted |
Il target ha un'altra versione. |
missing |
Il target non ha alcuna release (ad esempio un nodo nuovo, mai rilasciato). |
standby |
Nei rilasci per percentuale con il router dell'ambiente: lo slot senza traffico, con la versione sostituita. Non è un'anomalia. |
unknown |
Il target non è leggibile (detail riporta l'errore, ad esempio SSH irraggiungibile). |
Una lettura conta solo per il deploy con cui è stata fatta: dopo un nuovo deploy l'inventario non mostra targets finché la riconciliazione successiva non ha confrontato i target con la nuova versione. Se l'intero ambiente non è leggibile (plugin senza CurrentRelease, configurazione di ambiente non valida) targets.error spiega perché.
Ogni target che passa a drifted o missing produce un log di livello warning (target changed outside manent, con host, versione attesa e trovata), inviato anche al SIEM; il ritorno in linea un log informativo. La metrica manent_targets conta i target per stato (vedi Osservabilità). Nell'interfaccia web la cella dell'inventario elenca i target cambiati.
Promozione#
requires elenca gli ambienti in cui lo stesso artefatto deve essere già stato rilasciato con successo: stessa applicazione, stesso nome e stessa versione e, se il descrittore indica artifact.sha256, stesso hash. Un deploy che non rispetta la regola viene rifiutato all'accodamento (422):
invalid request: myapp 2.0.0 was not released successfully in preprod: it must be promoted from there (governance)
Il modo più semplice di rispettarla è promuovere:
La promozione:
- trova in
--froml'ultimo deploy riuscito (o quello della versione indicata); - ne riprende il descrittore, cambia l'ambiente e fissa
artifact.sha256all'hash registrato: lo stepqaverifica che venga rilasciato esattamente lo stesso file; - non copia
deploy.targets, che sono propri di ogni ambiente: arrivano dalla configurazione di ambiente di destinazione o da--deploy-file, un file YAML con la sezionedeploy(policy,plugin,targets,params) che sostituisce quella di origine; - accoda il nuovo deploy con
kind: promotion, soggetto come tutti a policy, approvazioni e freeze.
| Permesso | Ambiente |
|---|---|
deployment.read |
origine |
deployment.submit (o deployment.dry-run con --dry-run) |
destinazione |
Approvazioni#
Con approvals.required il deploy non viene eseguito subito: resta AWAITING_APPROVAL finché non riceve il numero richiesto di approvazioni da identità diverse.
| Chiave | Default | Descrizione |
|---|---|---|
required |
0 |
Approvazioni necessarie. |
allow_self |
false |
Ammette l'approvazione di chi ha richiesto il deploy. |
approvers |
groups e subjects (pattern con *): solo queste identità possono approvare, oltre al permesso deployment.approve. |
|
timeout |
72h |
Scaduto il tempo la richiesta passa a EXPIRED e il deploy non viene eseguito. |
when |
before |
before: approvazioni prima del deploy. rollout: nessuna approvazione prima del deploy, quelle della regola si chiedono nei punti di approvazione del rollout (vedi sotto). |
Con un rilascio a ondate o un rilascio per percentuale che ha approvalsBetween, una regola con approvals chiede per default le approvazioni due volte: prima del deploy e in ogni punto del rollout. Con when: rollout la prima ondata (o la candidata a peso 0) parte senza approvazione, e le approvazioni della regola (numero, allow_self, timeout) si chiedono solo nei punti elencati in approvalsBetween. Un deploy soggetto a una regola così deve avere un rollout che si ferma davvero: una voce di approvalsBetween dopo un'ondata che non sia l'ultima, o un gradino del rilascio per percentuale. Altrimenti viene rifiutato all'invio (422), o fallisce nello step config, prima di toccare i target, se nel frattempo la configurazione di ambiente è cambiata. Il primo rilascio per percentuale non ha una versione stabile e salta i gradini intermedi: l'approvazione viene allora chiesta prima che la candidata riceva traffico. L'approvazione del change nell'ITSM (change.approval) resta prima del deploy.
manent deploy -f prod.yml
# deploy awaiting approval: manent approve 7c0e…
manent list --state AWAITING_APPROVAL # richieste in attesa
manent approve 7c0e… -m "CHG-1234" # chi ha il permesso deployment.approve
manent reject 7c0e… -m "manca il change" # un rifiuto chiude la richiesta
Lo stato del deploy riporta le decisioni:
{
"id": "7c0e…", "state": "AWAITING_APPROVAL", "requested_by": "token:ci",
"approval": {
"status": "pending", "required": 2, "expires_at": "2026-10-13T09:00:00Z",
"decisions": [{"approver": "oidc:anna", "approve": true, "comment": "CHG-1234", "time": "…"}]
}
}
| Stato | Significato |
|---|---|
AWAITING_APPROVAL |
In attesa delle approvazioni. |
REJECTED |
Respinto: il deploy non viene eseguito. |
EXPIRED |
Non approvato entro il timeout. |
CANCELLED |
Ritirato con manent cancel. |
Raggiunte le approvazioni, il deploy viene accodato con lo stesso ID e prosegue come gli altri. Un rilascio a ondate può chiedere anche approvazioni tra un'ondata e l'altra (approvalsBetween): si danno con lo stesso manent approve. Se era pianificato con schedule.at attende comunque l'orario.
Regole:
- Permesso. Approvare e respingere richiede
deployment.approvesull'applicazione e sull'ambiente, concesso dal ruolo predefinitoapprover. I ruolideployereoperatornon lo includono. - Auto-approvazione. Chi ha richiesto il deploy non può approvarlo (
403), salvoallow_self: true. Può però respingerlo, per ritirarlo. - Una decisione per persona. Una seconda decisione della stessa identità è rifiutata.
- Approvatori. Con
approversun'identità fuori dai gruppi o dai soggetti della regola non può decidere (403), e la richiesta non compare nei suoimanent approvals. - Schedule ricorrenti. Ogni esecuzione valuta la governance in vigore in quel momento: una regola con approvazioni aggiunta dopo la creazione della schedule vale per le esecuzioni successive. Le schedule create dalle versioni precedenti mantengono le approvazioni della loro creazione.
- Regola al momento della richiesta. Le approvazioni richieste sono quelle valide quando il deploy viene accodato: una regola più severa arrivata dopo non le invalida (salvo
governance.start: strict, vedi Policy in Rego). - Coda separata. L'attesa avviene su una coda dedicata (
<dbos.queue>-approvals): non occupa slot dei worker e non blocca altri deploy dello stesso ambiente.
Ogni decisione è registrata nell'audit (deployment.approve, deployment.reject), così come l'esito finale (approval.finish).
Finestre di freeze#
Durante una finestra di freeze i deploy reali sull'applicazione e sull'ambiente sono bloccati.
| Chiave | Descrizione |
|---|---|
name, reason |
Nome e motivo, riportati nel messaggio di errore. |
applications |
Pattern qualificati con *: shop solo untenanted, payments/* le applicazioni di un tenant, */* tutte (dettagli). |
environments |
Pattern con *. |
regions |
Facoltativo: limita la finestra ai passi dei rilasci a ondate su queste region (pattern con *). Una finestra con regions non blocca l'accodamento: il rollout si mette in pausa prima della region e riparte a finestra chiusa. |
from, to |
Finestra singola (RFC 3339). |
cron, duration, timezone |
Finestra ricorrente: ogni occorrenza inizia a cron (5 campi) e dura duration. |
hard |
La finestra non può essere esentata da una policy Rego né superata con freezeOverride. |
tenants |
Selettore facoltativo: la finestra vale solo per le applicazioni dei tenant corrispondenti. |
artifacts |
Pattern facoltativi su artifact.name. |
selector |
Selettore facoltativo sui tag delle applicazioni (es. criticality=high). |
Il freeze è verificato tre volte:
- all'accodamento: un deploy immediato, o pianificato con
schedule.atdentro una finestra, viene rifiutato con422; - all'avvio sul worker, nello step
config: un deploy accodato prima (ad esempio da una schedule o approvato tardi) che parte durante una finestra fallisce subito, senza retry; - prima di ogni stadio di un rollout, in uno step dedicato: il rollout si mette in pausa fino alla fine della finestra.
manent freezes [-a myapp] [-e prod] # finestre attive e prossime occorrenze
manent freeze add --name incidente-881 -e prod --for 4h [--hard] # freeze d'emergenza, senza riavvii
manent freeze delete incidente-881
Le finestre create a runtime (permesso freeze.write) sono descritte in Freeze a runtime.
Deroga per le emergenze#
Un deploy urgente può derogare indicando il motivo, ad esempio il numero dell'incidente:
oppure --freeze-override "INC-4711 …" con promote e rollback. La deroga:
- richiede il permesso
deployment.freeze-override, concesso solo daadmino da un ruolo dedicato; - non supera le finestre
hard; - viene registrata nell'audit (campo
comment) e nell'output dello stepconfig; - non esonera dalle approvazioni;
- non è ammessa con
schedule.cron, perché vale per un singolo deploy.
Rollback manuale#
Il rollback automatico interviene quando un deploy fallisce. Quando invece un deploy riuscito si rivela difettoso, si torna a una versione precedente:
Il rollback manuale:
- sceglie nello storico dell'ambiente l'ultimo deploy riuscito di una versione diversa da quella in esercizio (o quello di
--to). Se dopo l'ultimo deploy riuscito un deploy è fallito lasciando i target modificati (senza rollback automatico, con un rollback fallito o con un rollout interrotto dopo il primo stadio), la versione in esercizio non è più garantita: il rollback ripristina quella dell'ultimo deploy riuscito, che si può anche indicare con--to; - lo rilascia di nuovo con il suo descrittore e il suo
sha256, come nuovo deploy conkind: rollback; - è ammesso solo per i tipi di artefatto per cui
rollback.artifact_types.<tipo>.allowedètrue. Le migration sono escluse di default: rilasciare la versione precedente di una migration non annulla le modifiche allo schema.
Richiede il permesso deployment.rollback (ruoli deployer e operator) ed è soggetto ad approvazioni e freeze come ogni deploy. Il plugin riceve un normale deploy della versione precedente: l'artefatto viene scaricato, verificato e copiato di nuovo sui target, quindi deve essere ancora disponibile alla sorgente.
API#
| Metodo | Percorso | Descrizione |
|---|---|---|
GET |
/api/v1/inventory?application=&environment= |
Inventario. |
POST |
/api/v1/promotions |
Promozione. |
POST |
/api/v1/rollbacks |
Rollback manuale. |
POST |
/api/v1/deployments/{id}/approve |
Approva. |
POST |
/api/v1/deployments/{id}/reject |
Respinge. |
GET |
/api/v1/freezes?application=&environment= |
Finestre di freeze. |
POST |
/api/v1/freezes |
Crea una finestra di freeze a runtime. |
DELETE |
/api/v1/freezes/{name} |
Elimina una finestra di freeze creata a runtime. |
Vedi API HTTP.