Vai al contenuto

Casi d'uso#

I casi d'uso provano manent da capo a fondo, come gira in esercizio: server API, worker, client e target SSH in container separati, su Postgres. Si parte da un singolo deploy pianificato e si arriva ai rilasci a ondate su più region, poi ai container su VM e a Kubernetes, ai load balancer, ai rilasci per percentuale, agli step personalizzati, alle integrazioni e agli application server; per ogni caso si verificano anche fallimenti e rollback.

make test-usecases                    # tutti i casi (circa 30 minuti)
make test-usecases CASES="uc1 uc4"    # solo alcuni
make test-usecases KEEP=1             # lascia il laboratorio attivo alla fine

Servono Docker con Compose v2, Go, jq e ssh-keygen. Lo script compila i binari linux sull'host, quindi non serve l'immagine golang. UC6 e UC7 usano container privilegiati (Docker in Docker e k3s) e circa 2 GB di memoria in più per Docker. UC8 usa l'immagine di Playwright (circa 2 GB su disco), UC10 HAProxy (circa 200 MB), UC14 WildFly (circa 800 MB) e UC15 Prometheus (circa 270 MB). UC11 scarica le CRD della Gateway API da GitHub.

Il laboratorio#

flowchart LR
    C[client manent] --> A[API]
    A --> P[(Postgres)]
    W1[worker-1] --> P
    W2[worker-2] --> P
    subgraph eu-1
      EA[eu1-a · eu-1a]
      EB[eu1-b · eu-1b]
      EC[eu1-c · eu-1c]
    end
    subgraph us-1
      UA[us1-a · us-1a]
      UB[us1-b · us-1b]
    end
    W1 --> eu-1
    W1 --> us-1
    W2 --> eu-1
    W2 --> us-1
    subgraph uc67[UC6 · UC7]
      R[(registry)]
      VA[vm-a · Docker]
      VB[vm-b · Docker]
      K[k3s]
    end
    W1 --> uc67
    W2 --> uc67
  • Target: container Debian con SSH (per il plugin remote-copy, layout releases) e un server HTTP che espone la release attiva: http://<target>:8080/<app>/current/healthz. Una release senza healthz simula una versione difettosa.
  • Configurazione di ambiente: target, parametri e segreti arrivano dal provider file (test/usecases/config/envconfig); i descrittori contengono solo l'artefatto.
  • Accesso: token statici con ruoli diversi (ci rilascia, cab approva, admin può derogare ai freeze, viewer legge soltanto), e un identity provider OIDC di prova (test/usecases/oidc) con gli stessi utenti per l'interfaccia web.
  • Guasti simulati: il post_command fallisce o attende se il caso crea un file sul target. Worker e target vengono fermati o terminati con docker compose.
  • Profilo region: per UC5 i worker generici sono sostituiti da un worker per region (dbos.region, region_routing), ciascuno solo sulla rete della sua region. L'orchestratore non raggiunge nessun target.
  • Container e Kubernetes: per UC6 e UC7 il laboratorio avvia un registry delle immagini, due VM con Docker (vm-a, vm-b) e un cluster k3s a un nodo. L'applicazione di prova è una piccola immagine con /healthz; la versione difettosa non ce l'ha, quindi HEALTHCHECK e readinessProbe restano rossi. Questi servizi partono solo per il loro caso e vengono rimossi alla fine.
  • Load balancer: per UC10 un HAProxy vero con la Data Plane API v3 (test/usecases/haproxy) davanti ai target di eu-1: il backend uc10-shop con gli health check HTTP viene svuotato a lotti, il backend uc10-blue contiene i due slot del rilascio per percentuale. Per UC15 e UC16 i backend uc15-web e uc16-web hanno i server chiamati come gli slot, e HAProxy esporta le sue metriche per server a un Prometheus vero (profilo metrics, lettura ogni secondo). UC11 usa lo stesso cluster k3s con le CRD della Gateway API e una HTTPRoute creata in anticipo, come farebbe l'infrastruttura.
  • CI e Terraform: per UC18 un runner alpine con il client (profilo tools) esegue lo script della GitHub Action e le righe del template GitLab come farebbero le due CI; per UC19 l'immagine hashicorp/terraform (profilo terraform) usa il provider compilato da integrations/terraform con dev_overrides, senza registry.
  • Windows: per UC20 un host Windows emulato (test/usecases/windows, profilo windows) con PowerShell 7 vero, OpenSSH, un endpoint WinRM su HTTPS che esegue i comandi con PowerShell, e moduli con i cmdlet di IIS e dei servizi usati dal plugin, su una configurazione in JSON da cui un server web serve le applicazioni IIS. Gli script del plugin girano come su Windows Server; IIS e il gestore dei servizi sono imitati.
  • Windows, secondo host: per UC21 un secondo host emulato (win2) e, su win1, i cmdlet delle attività pianificate e di NLB, un msiexec.exe e la parte ARR di Microsoft.Web.Administration (compilata da C# nell'immagine, sotto $env:windir come su Windows), tutto su uno stato in JSON. Il PowerShell dei pool gira senza modifiche.
  • Azure: per UC22 il servizio fakes serve anche, sulla porta 9400, Azure emulato a livello di protocollo (internal/azurefake, usato anche dai test dei plugin): token di Entra ID con il service principal del laboratorio, Resource Manager per App Service e Container Apps con le operazioni asincrone, Kudu, le web app e le container app stesse (servono la versione rilasciata) e l'API di query di Log Analytics. Le immagini di Container Apps vengono dal registry del laboratorio.
  • Servizi esterni: Slack, Teams, ServiceNow, Datadog, New Relic, Elastic e CloudWatch non hanno una versione locale (o non abbastanza leggera), quindi test/usecases/fakes ne emula le API (più un endpoint HTTP per gli step personalizzati; per i sistemi di metriche le serie da restituire si impostano con POST /control/metrics, e CloudWatch parla il protocollo CBOR dell'SDK AWS con firma SigV4) e registra ogni richiesta, che i casi rileggono. UC14 usa un WildFly vero con l'API HTTP di management.

I casi#

Caso Scenario Cosa verifica
UC1 Deploy pianificato (schedule.at) su un target in una AZ, poi su tre AZ di una region Attesa fino all'orario, annullamento, idempotenza, release difettosa con rollback automatico, fallimento senza rollback e ripristino, rollback manuale, checksum errato, dry-run, target spento
UC2 Configurazione di ambiente e promozione Autenticazione e permessi, conflitti con la configurazione di ambiente, promozione test → prod con approvazione, divieto di auto-approvazione, rifiuto, freeze e deroga
UC3 Più worker sullo stesso database Un deploy alla volta per ambiente e ambienti in parallelo, worker terminato durante un deploy (recovery automatica dall'altro), arresto ordinato che attende il deploy in corso
UC4 Rollout a ondate in una region Canary e approvazione, un'AZ alla volta con bake e health gate, gate rosso sul canary, guasto nell'ultima AZ con rollback-all, rifiuto, onFailure: stop con drift nell'inventario
UC5 Rollout a ondate multi-region Passi e sonde dell'health gate eseguiti dai worker di region, region in parallelo, rollback-wave, worker di region spento (il passo lo attende), freeze di una sola region
UC6 Container su VM con Docker (plugin container) Immagine fissata al digest, promozione test → prod a ondate, dry-run, immagine unhealthy con rollback-all, rollback manuale, VM spenta, tag spostato nel registry (la promozione rifiuta l'immagine diversa)
UC7 Manifest su Kubernetes (plugin kubernetes) Dry-run lato server, stessi manifest in test e prod con configurazione di ambiente diversa, rollout bloccato e rollback, prune degli oggetti rimossi, storico delle release, immagine non fissata al digest, rollback manuale
UC8 Interfaccia web Intestazioni e configurazione, whoami, approvazioni, cursore e stream SSE dell'API; con Playwright: login OIDC, ogni vista verificata con axe in entrambi i temi, dettaglio con log in tempo reale, promozione dopo un dry-run, divieto di auto-approvazione, approvazione da un altro utente seguita in tempo reale, tastiera e italiano
UC9 Catene di deploy Deploy che attende un altro (after) senza toccare il proprio target, avvio dopo la fine della dipendenza, dipendenza fallita (DEPENDENCY_FAILED) e rifiuto all'invio, release composta in ordine topologico con reinvio idempotente, release fallita con onFailure: rollback (deploy riusciti riportati alla versione precedente)
UC10 Load balancer e router su HAProxy Ogni lotto svuotato dal backend durante il suo deploy e rientrato solo una volta sano, attese durevoli, controllo di capacità (min_healthy) che rimanda lo svuotamento mentre un altro server è guasto, nessuna richiesta fallita attraverso HAProxy durante il rilascio; router con due slot di target: primo rilascio con l'approvazione della governance spostata nel rollout (approvals.when: rollout), gradino del 50% con pesi uguali ed entrambe le versioni servite, release difettosa con i pesi tornati allo slot stabile
UC11 Rilascio per percentuale su Kubernetes con un chart Helm Candidata nello slot libero (uc11-web-a/-b) con l'etichetta dello slot, approvazione dopo i test a peso 0, regola dei tester dell'HTTPRoute, gradini sui pesi della regola principale, storico che indica lo slot, slot sostituito a zero repliche dopo scaleDownDelay, release difettosa che non diventa mai pronta con il traffico rimasto sullo slot stabile
UC12 Step personalizzati Piano con tipo e spec_hash ma senza comando né URL, dry-run con il solo comando e i soli segreti di dry_run, comando via SSH su un target con parametro, file del segreto e output dichiarato, segreto redatto nel log del deploy, richiesta HTTP con bearer, Idempotency-Key e corpo JSON costruito da manent, polling del job, exit 75 ritentato, errore permanente senza tentativi e target non toccati
UC13 Chat e change management Messaggio Slack all'avvio aggiornato con l'esito, richiesta di approvazione con i pulsanti su Slack, change aperto in ServiceNow con il deploy come correlation_id, clic di chi ha chiesto il deploy rifiutato (auto-approvazione), clic del CAB accettato (richiesta firmata come Slack), firma non valida rifiutata, deploy avviato solo dopo l'approvazione del change e change chiuso con l'esito, change respinto in ServiceNow, webhook di Teams rifiutato perché non https
UC14 WildFly (plugin jboss) Dry-run, un deployment per versione con lo stesso runtime-name, cambio con il server sospeso, war rifiutato da WildFly con la versione in esercizio intatta, post-test rosso con rollback automatico, rollback manuale senza ricaricare, parametro in conflitto con la configurazione di ambiente rifiutato, restart della JVM, keep_releases
UC15 Analisi canary su Prometheus Carico dall'host attraverso HAProxy, metriche di HAProxy per server (= slot) in un Prometheus vero, confronto di Mann-Whitney tra le serie di candidata e stabile a ogni lettura del gradino del 50%, primo rilascio senza confronti, release sana promossa con punteggio 100, release difettosa (una pagina mancante) respinta dalla metrica critica sulla quota di 4xx con il p-value nel gate e il traffico tornato allo slot stabile
UC16 Analisi canary su Datadog, New Relic, Elastic e CloudWatch Una metrica per sistema, ciascuna letta con la sua API e le sue credenziali (chiavi Datadog, chiave NerdGraph e NRQL TIMESERIES, ES|QL con API key, GetMetricData firmata con SigV4), serie equivalenti promosse con punteggio 100, due metriche non critiche peggiori: punteggio 50, sufficiente durante il bake (marginale) ma rosso alla fine, metrica critica di CloudWatch peggiore: rollout fermato prima della fine del bake, traffico tornato allo slot stabile
UC17 Stato reale dei target Riconciliazione ogni 10 secondi (reconcile nella configurazione del laboratorio): target letti uguali all'inventario con region e AZ, rollback a mano del symlink current su un target segnalato come drifted con la versione trovata, out_of_sync e manent inventory --out-of-sync, log del worker e metrica manent_targets, release rimossa (missing), target spento (unknown con l'errore), ritorno in linea senza un nuovo deploy, lettura del deploy precedente ignorata dopo un nuovo rilascio
UC18 CI: GitHub Action e template GitLab Script dell'action con le variabili di un runner (INPUT_*, GITHUB_OUTPUT, GITHUB_STEP_SUMMARY): deploy con versione e sorgente dalla pipeline, output e riepilogo del job, job rieseguito con lo stesso id senza nuove esecuzioni, dry-run, release difettosa con lo step fallito, state=ERROR e rollback nel riepilogo, validate, env-validate di una configurazione valida e di una con un campo sconosciuto; righe del job GitLab con gli input sostituiti e una variabile della pipeline nell'id, report dotenv
UC19 Terraform (provider manent) terraform apply della prima release e della successiva, plan vuoto con la stessa versione, rollback fatto con la CLI che compare nel plan e viene annullato dall'apply, target cambiato a mano rilasciato di nuovo con redeploy_out_of_sync, configurazione di ambiente candidata verificata nel plan (valida con i target, non valida con il plan fallito), release difettosa con l'apply fallito e il rollback automatico, destroy che lascia la release in esercizio
UC20 Windows (plugin windows) Applicazione IIS via WinRM (HTTPS, basic): dry-run, deploy con l'application pool fermato e riavviato, physicalPath sulla release e applicazione servita, release senza healthz con post-test rosso e rollback, physicalPath cambiato a mano segnalato dalla riconciliazione, rollback manuale senza nuova copia, keep_releases; servizio Windows via OpenSSH: binPath con gli argomenti sull'eseguibile della release, eseguibile che non parte con il deploy fallito e il servizio tornato sulla release precedente
UC21 Windows: ARR, NLB, MSI, attività pianificate (plugin windows) Applicazione IIS su due host dietro una web farm ARR: un server alla volta in Drain e di nuovo Available, step di svuotamento, deploy e rientro per lotto; pacchetto MSI sui nodi di un cluster NLB: un nodo alla volta svuotato e fermato, proprietà pubbliche a msiexec, MajorUpgrade, release letta dal prodotto installato, pacchetto fallito (1603) con l'host intatto, rollback manuale con downgrade dopo la disinstallazione; attività pianificata: azione sull'eseguibile della release con argomenti e cartella di lavoro, password memorizzata dell'account, run_now, istanza in esecuzione che blocca il deploy e stop_task
UC22 Azure (appservice, containerapps, azure-monitor) App Service con slot di staging: pacchetto via Kudu, salute dello slot, swap, versione che passa con lo swap, release difettosa che non arriva mai in produzione, swap fatto a mano segnalato dalla riconciliazione, rollback manuale con un nuovo swap; senza slot: produzione e tag; rilascio per percentuale: 30% del traffico allo slot (rampUpRules) con il gate su Log Analytics (KQL), swap, tasso di errori oltre la soglia con la produzione intatta; Container Apps: revisione per deploy con l'immagine fissata al digest, revisione non sana con il traffico di nuovo sulla precedente
UC23 Governance in Rego (Policy in Rego) Laboratorio su governance.engine: opa (ogni caso gira sulla policy predefinita, che applica le regole di manent.yml); revisione caricata da un autore senza policy.approve, test eseguiti al caricamento, impatto sui deploy reali, auto-approvazione rifiutata (quattro occhi), approvazione da un admin senza riavvii e revisione nelle metriche; approvatori limitati al CAB con l'admin rifiutato, downgrade negato e mostrato da validate; freeze creato a runtime tra due ondate con il rollout in pausa e ripreso dopo l'eliminazione; freeze hard che blocca la deroga, eliminazione rifiutata senza freeze.write e break-glass con manent-server admin; ritorno alla policy predefinita
UC24 Tenant Team proprietari di applicazioni con il registro in tenants.yml; payments e retail hanno entrambi uc24-shop: senza tenant il deploy è rifiutato come ambiguo, con --tenant ogni release legge la configurazione del suo tenant ({tenant} nel template) e ha la sua voce di inventario; un'applicazione che nessun tenant elenca rilasciata come untenanted; la coda dedicata e le restrizioni di un tenant; ID dei deploy, after e release tra tenant rifiutati; il ruolo implicito dei membri, rifiutato sull'omonimo di retail indicato per errore e sulle applicazioni untenanted; i tag delle applicazioni nei selettori e un freeze a runtime su */uc24-*; admin move-application (dry-run); una policy Rego che legge i tag del tenant; ritorno alla policy predefinita
UC25 Permessi come policy Laboratorio su rbac.engine: opa (tutti i casi girano sulla policy predefinita, che applica i binding di manent.yml); utenti OIDC con il claim employee_type; una revisione che dà a un team la sua applicazione e le applicazioni dei tenant con il tag owner=<team>, turni di reperibilità con until e il divieto della produzione ai contractor, simulata con policy access (permessi guadagnati rispetto alla revisione attiva, divieto con il motivo, tenant propri e altrui) prima che una seconda persona la approvi; deploy reali consentiti e negati con motivo e revisione, audit con il permesso usato; un deploy pianificato annullato dall'identità di turno e non da quella con il turno scaduto; una revisione che toglie tutti i permessi corretta da chi ha policy.* statico; ritorno alla policy predefinita
UC26 Export e import Un deploy dell'infrastruttura il cui step personalizzato "crea" due VM ed esporta i loro host (format: targets) e la directory delle release; il deploy della configurazione li importa come target e dest_dir con after, con --import dal CLI e in una release composta che nomina il produttore; un export fuori dal suo pattern che fa fallire il produttore e lascia chi dipende in DEPENDENCY_FAILED; un export inesistente rifiutato all'invio; un ambiente che prende target e parametri dalla release in esercizio (targets_from, params_from) e rifiuta i target del descrittore; una promozione che toglie l'import del deploy di origine
UC27 Repository Git Un secondo server manent legge la configurazione di ambiente dal server Git del laboratorio (smart HTTP con un token, commit firmati con SSH): ogni deploy registra lo SHA del commit; un nuovo commit su main arriva al deploy successivo; un commit non firmato è rifiutato all'invio; --config-ref rilascia con la configurazione di un tag, un branch fuori da allowed_refs e uno SHA non raggiungibile sono rifiutati; il rollback manuale torna allo SHA registrato dal deploy che ripristina, non alla testa di main
UC28 terraform Il plugin Terraform esterno, con OpenTofu nell'immagine del worker, rilascia un modulo con risorse terraform_data in due stati (uno per region) del backend pg sul Postgres del laboratorio: il dry-run mostra le modifiche previste e non scrive stati; il deploy applica in entrambe le region ed esporta le VM come target, su cui uc28-app viene rilasciata dalla configurazione di ambiente (import da current + targets_from); la stessa versione di nuovo non modifica nulla; una versione che distrugge una VM viene rifiutata da max_destroy con un solo piano (errore permanente, nessun retry) e l'inventario resta sulla release precedente
UC29 ansible Una release composta concatena infrastruttura, configurazione e applicazione: il plugin Terraform esporta due VM con le loro etichette, il plugin Ansible le configura sulla sua coda, servita solo da worker-ansible (ansible-core di Debian; i worker distroless conoscono il plugin solo come stub), con un inventario raggruppato per etichetta, ed esporta la directory dell'applicazione con set_stats; remote-copy vi rilascia l'applicazione (targetsFrom + paramsFrom). Il marcatore della release viene scritto sui target, la coda del plugin compare con tipo plugin, il check mode non modifica nulla e un host che fallisce nel playbook fa fallire il deploy con il riepilogo per host
UC30 Target in parallelo Tre host SSH in un lotto con parallel: 100%, limitato a 2 da plugins.remote-copy.max_parallel: i log del post_command mostrano due host aggiornati insieme e il terzo che parte quando si libera un posto, lo step deploy riporta parallel 2 e parallel_requested 3. Senza parallel gli host vanno uno dopo l'altro; un descrittore può chiederlo entro descriptor_policy.max_parallel (4 nel laboratorio, 422 oltre) e non può cambiare quello dell'ambiente. Con un host guasto quello già avviato finisce e il terzo non parte mai. In un rilascio a ondate l'ondata canary resta a parallel: 1 e il lotto successivo di due host è aggiornato insieme
UC31 Più provider Tre provider file in envconfig.providers: uc31-legacy serve le applicazioni uc31-* con on_missing: next, quindi test viene letto lì (lo stesso ambiente nel provider default non viene mai letto) mentre prod, che non c'è più, ripiega su default; lo step config riporta provider e skipped. Il tenant uc31team indica uc31-team (tenant_only) nel registro: i suoi deploy leggono quel provider, e un ambiente che lì manca non ripiega sugli altri (422, nessun target)

Ogni verifica stampa ✔ o ✘. Alla fine lo script riassume i fallimenti e salva i log dei container in test/usecases/.work/compose.log.

Aggiungere un caso#

Un caso è uno script in test/usecases/cases/ (ucN.sh), eseguito in ordine numerico sullo stesso laboratorio. Le funzioni di lib.sh creano artefatti e descrittori, eseguono il client con un ruolo (as cab manent approve <id>), attendono gli stati (wait_state, wait_rollout), leggono i target (current, healthy) e iniettano guasti (fault). Ogni caso usa una propria applicazione, così non dipende dagli altri.