Failover e Disaster Recovery
Flo protegge ogni tenant con una coppia di VPS primario + standby. PostgreSQL replica in streaming WAL dal primario allo standby su una mesh privata WireGuard, e il control plane MON è l'unica autorità che può concedere autorità di scrittura a un nodo. Il failover può essere manuale (comando operatore) o automatico (coordinatore MON), ma è sempre fail-closed: un nodo senza lease valido smette di servire.
Il runbook operativo completo vive in docs/DisasterRecoveryDrill.md nel repository della
piattaforma; questa pagina è il riferimento tecnico.
Architettura
MON (control plane, fuori banda)
│ store DR durevole: epoche, lease, incidenti, policy
│ lease firmati di breve durata
▼
┌──────────────────┐ Mesh WireGuard (wg0, 10.88.0.0/24, UDP 51820) ┌──────────────────┐
│ VPS primario │◄─────────── Replica streaming PostgreSQL ────►│ VPS standby (UK) │
│ app (attiva) │ │ app (ferma) │
│ postgres primario│ │ postgres replica │
│ failover agent │ │ failover agent │
└──────────────────┘ └──────────────────┘
▲ ▲
└────────────────── traffico tenant (DNS → Traefik) ──────────────────────┘
- Mesh WireGuard:
flo vps link <primary> <standby>costruisce una rete privata a due nodi (wg0, RFC191810.88.0.0/24, porta UDP predefinita 51820). Sono registrate solo chiavi pubbliche e IP mesh; le chiavi private non lasciano mai i nodi. Il proxy PostgreSQL espone solo l'indirizzo WireGuard. - Replica in streaming:
flo failover replication setup <tenant>esegue un backup base dello standby dal primario e avvia lo streaming. La modalità predefinita èasync;--mode syncè supportata ma deve essere una modifica approvata. - Autorità: MON assegna un'epoca monotonicamente crescente per tenant. Al
massimo un nodo può detenere un lease primario valido per un'epoca. Il lease firmato
contiene tenant, nodo, ruolo, epoca, tempi di emissione/scadenza, ID incidente e
ID chiave di firma. Intervallo di rinnovo 10 s, durata lease 30 s, skew orario consentito 5 s
(
FLO_CONTROL_DR_LEASE_TTL_MS=30000,FLO_CONTROL_DR_LEASE_CLOCK_SKEW_MS=5000). - Barriera RPO: sul percorso controllato MON revoca le nuove scritture, attende lo svuotamento degli
scrittori attivi, registra una barriera LSN di flush WAL sorgente e attende che lo
standby la replichi prima di fermare la vecchia sorgente e autorizzare il target. Sul
percorso con sorgente irraggiungibile MON attende la scadenza del lease più lo skew e marca
RPO come
unknown. - Ordine di promozione: commit epoca → autorità al target → avvio applicazione target → verifica readiness PostgreSQL/applicazione/origine diretta → spostamento DNS. I controlli di readiness sono consapevoli del database, e il DNS si sposta solo dopo che passano.
- Failback manuale: dopo un failover automatico MON mantiene attivo lo standby;
il failback è sempre un comando operatore esplicito (
flo failover back).
Anti-split-brain
L'anti-split-brain è garantito dal modello a singola autorità di MON: un'epoca, un lease
primario valido, agent e guardie backend fail-closed. Il precedente disegno witness/quorum
(un terzo VPS che votava sulla liveness del primario, failover setup --witness,
configurazione --arm/auto-promozione) è stato ritirato nel completamento della sicurezza DR;
quelle opzioni sono assenti dai contratti pubblico e remoto, e il
campo legacy witnessVps del registry è rimosso al caricamento. flo failover watch è
solo osservativo: senza un provider di fencing esterno segnala un sospetto guasto
ma non può promuovere, cambiare il DNS né inviare comandi.
Protezioni lato tenant
Quando un tenant è armato, sia il backend Flo sia l'agent del nodo falliscono in modo chiuso:
PrimaryLeaseGuard(IPrimaryLeaseGuard) è l'unica decisione di autorità per i percorsi di esecuzione del backend. Con lease assente, scaduto, invalido o obsoleto blocca migrazioni di avvio, comandi database, lavoro in background e nuovi effetti collaterali esterni (media, email, job); anche controller CRUD e health check lo consultano. Anche scritture dell'editor di configurazione e pulizie sono protette da lease, così uno standby è di fatto in sola lettura.- Hosted service avvolti da
PrimaryLeaseHostedService<T>attendono un lease valido prima di partire, lo interrogano ogni secondo, fermano il servizio interno alla perdita del lease e fermano l'host se la pulizia non termina in tempo — un processo fresco attende quindi un nuovo lease. - Agent del nodo (
flo failover agent ...) è l'applicatore locale fail-closed: alla scadenza del lease o a errore di firma ferma i container applicativi del tenant (non ferma mai il VPS) e preserva l'epoca massima accettata tra i riavvii. - Gate di avvio:
FAILOVER_ENABLEDvincola l'intero sottosistema lease. Quando è assente o falso, nessun middleware lease, intercettore EF o superficie health lease è attivo, e l'app si comporta esattamente come una build pre-DR. Un valore invalido rifiuta l'avvio (exit 78) invece di disarmare silenziosamente un nodo arruolato.
Interruttori principali
| Interruttore | Superficie | Predefinito | Effetto |
|---|---|---|---|
FAILOVER_ENABLED | Backend tenant | spento | Abilita middleware lease, intercettore EF, health check lease |
FLO_CONTROL_DR_ENABLED | MON | false | Vincola store DR, chiave di firma, worker e rotte /internal/dr/* + /api/dr/* |
FLO_CONTROL_DR_AUTOMATION_ENABLED | MON | false | Promozione automatica; impostato solo da flo control deploy mon --enable-dr-automation |
Disabilitare un interruttore non disarma mai implicitamente: --disable-dr-automation non
sostituisce un disarmo completo del tenant.
Operatività
Tutti i comandi puntano al nodo che esegue l'operazione figlia (--vps <node>); auto
e incident leggono MON anche quando --vps punta al primario. Senza --vps,
i comandi di failover risolvono la posizione del tenant dal registry.
| Comando | Descrizione | Opzioni principali |
|---|---|---|
flo failover setup [tenant] | Arruola una coppia tenant (primario + standby) | --standby <profile> (obbligatorio), --primary <profile>, --all, --dry-run, -y |
flo failover replication setup <tenant> | Backup base dello standby e avvio streaming | --mode async|sync, --dry-run, -y |
flo failover replication status <tenant> | Interroga e registra lo stato live della replica | |
flo failover status | Postura DR per tutte le istanze arruolate (primario/standby, stato replica) | |
flo failover monitor | Salute replica + deriva immagini/segreti; esce non-zero se degradato | --tenant, --all, --notify, --max-lag <s>, -y |
flo failover sync-image <tenant> | Scarica + fissa sullo standby l'immagine corrente del primario | -y |
flo failover sync-secrets <tenant> | Ri-mirror cert OIDC + segreti DB/audit dopo rotazione | -y |
flo failover sync-media <tenant> | Mirror upload su filesystem locale primario→standby (no-op per R2) | -y |
flo failover run | Failover verso uno standby (fencing → promozione → avvio → verifica → DNS) | --to <profile> (obbligatorio), --tenant o --all, --dry-run, --resume-dns, -y |
flo failover back <tenant> | Failback al primario originale ripristinato (re-clone → promozione) | --to <profile> (obbligatorio), -y |
flo failover watch | Osservatore di liveness solo osservativo; registra/invia email su sospette interruzioni | --tenant, --all, --interval, --threshold, --notify, -y |
flo failover drill <tenant> | Test game-day: disastro → failover → verifica RTO/RPO → failback | --chaos, --max-rpo, --keep, --autonomous, --dry-run, --check, -y |
flo failover auto register <tenant> | Registra la coppia nello store DR di MON | -y |
flo failover auto prepare <tenant> | Abilita i lease incumbent dopo la separazione dei ruoli; la promozione resta spenta | -y |
flo failover auto status <tenant> | Ciclo di vita durevole della promozione automatica | |
flo failover auto arm <tenant> | Consente l'autorità automatica di MON dopo preflight completo | -y |
flo failover auto disarm <tenant> | Disarmo sicuro in due passi; --finalize termina l'emissione lease e disarma entrambi gli agent | --finalize, -y |
flo failover auto restore <tenant> | Riarmo idempotente: coppia di nuovo armata con standby in streaming | -y |
flo failover incident list|show|explain|export|reconcile|resume|abort | Record durevole dell'incidente: elenca attivi, ispeziona transizioni, spiega la decisione, esporta JSON, riconcilia dopo riavvio, riprende/interrompe incidenti bloccati | show/explain/export/reconcile prendono un ID incidente; --out <path>; resume/abort richiedono -y |
flo failover agent install|upgrade | Installa/aggiorna l'agent root-only + unità systemd su un VPS | --config <path> (obbligatorio), -y |
flo failover agent enroll | Transizione prepared→enrolled per un tenant | --tenant, --config, -y |
flo failover agent status|daemon|uninstall | Stato agent, processo servizio, rimozione (solo tutti-prepared non armati) | uninstall richiede --config, -y |
flo failover teardown <tenant> | Rimuove solo la copia standby + replica (non tocca mai Cloudflare) | --dry-run, -y |
flo failover storm | Rompe più tenant di test insieme, ciascuno col suo chaos, poi riarma | --targets <tenant[:chaos],...> (obbligatorio), --sequential, -y |
flo failover soak <tenant> | Ripete il drill autonomo N volte con chaos rotante | --runs, --chaos, --max-duration, --continue-on-failure, --report <path>, -y |
Le forme di lettura (status, replication status, monitor, incident list/show, auto status) fanno parte del registry remoto autenticato; le forme di scrittura richiedono il
control plane e -y. La forma remota del drill valida --chaos, --max-rpo,
--dry-run, --check e rifiuta --force e --keep.
Drill
flo --vps production failover drill <test-id> --dry-run # stampa il piano
flo --vps production failover drill <test-id> --check # prerequisiti live, nessun chaos
flo --vps production failover drill <test-id> --chaos kill-db --max-rpo 0 -y
flo --vps production failover drill <test-id> --autonomous --chaos stop-hard -y
- Modalità chaos:
kill-db,kill-app,stop-hard,pause(oppurerandom). - Solo i tenant marcati esplicitamente
isTest: truepossono essere drillati distruttivamente. --dry-runstampa un piano (non prova il successo);--checkverifica i prerequisiti live (streaming, modalità, immagini, segreti, routing, autorità) senza canary, chaos, promozione o scritture DNS.--autonomousattende un incidente automatico MON corrispondente avviato dopo il guasto e non crea mai un incidente manuale come fallback. Un timeout o un incidente bloccato non è un successo.--max-rpo <writes>è una soglia di accettazione, non una garanzia di replica asincrona; il report misura le scritture consolidate effettivamente perse.stormesoakscalano i drill su tenant/esecuzioni; soak accoda una riga JSON per esecuzione in--reporte riarma tra le esecuzioni.
Cosa accade ai tenant durante un failover
- MON decide (oppure l'operatore esegue
failover run); una singola sonda fallita non può mai attivare una promozione, e MON tenta prima un riavvio locale limitato quando la policy lo consente. - La sorgente è isolata (fencing): le nuove scritture sono revocate e svuotate (percorso controllato) oppure il lease è lasciato scadere più lo skew (percorso irraggiungibile). Il backend e l'agent del vecchio primario rifiutano lavoro senza un lease valido.
- MON consolida una nuova epoca ed emette l'autorità al target; il PostgreSQL standby è promosso e l'applicazione parte sul target.
- La readiness è verificata (PostgreSQL, applicazione, origine diretta) prima che il DNS sia rispostato; il registry è solo una proiezione dello stato MON.
- Dopo un
run, le coppie coinvolte sono NON PROTETTE (nessuno standby caldo) finché il vecchio primario non si riprende e non si esegue failback / riprotezione.flo failover statuselenca le coppie non protette con l'esatto comando di riprotezione.
Checklist di incidente
flo control whoami
flo --vps <primary> vps stats
flo --vps <primary> failover status
flo --vps <primary> failover monitor --all -y
flo --vps <primary> failover incident list
- Leggi il record durevole prima di agire:
incident show <id>, poiexplain. incident resume <id> -ysolo dopo una nuova sonda di sicurezza live;incident abort <id> -ysolo dopo che ripristino di sorgente e standby sono provati.- Non eliminare mai incidenti, lease, epoche o file di fencing per far passare un drill; non modificare a mano il database MON.
- Completa un disarmo DR totale prima della manutenzione tenant o di un deploy MON; il gate di deploy rifiuta lease pendenti, incidenti attivi o autorità ignota.
- Il failback resta manuale:
flo --vps <standby> failover back <tenant> --to <primary> -y, poiflo --vps <primary> failover replication setup <tenant> -yper ricreare la protezione. - Mantieni
docs/DisasterRecoveryDrill.mdcome runbook passo-passo (autorizzazione, separazione ruoli, arruolamento agent, drill, disarmo, deploy/recupero MON).