Passa al contenuto principale

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, RFC1918 10.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_ENABLED vincola 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​

InterruttoreSuperficiePredefinitoEffetto
FAILOVER_ENABLEDBackend tenantspentoAbilita middleware lease, intercettore EF, health check lease
FLO_CONTROL_DR_ENABLEDMONfalseVincola store DR, chiave di firma, worker e rotte /internal/dr/* + /api/dr/*
FLO_CONTROL_DR_AUTOMATION_ENABLEDMONfalsePromozione 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.

ComandoDescrizioneOpzioni 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 statusPostura DR per tutte le istanze arruolate (primario/standby, stato replica)
flo failover monitorSalute 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 runFailover 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 watchOsservatore 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|abortRecord durevole dell'incidente: elenca attivi, ispeziona transizioni, spiega la decisione, esporta JSON, riconcilia dopo riavvio, riprende/interrompe incidenti bloccatishow/explain/export/reconcile prendono un ID incidente; --out <path>; resume/abort richiedono -y
flo failover agent install|upgradeInstalla/aggiorna l'agent root-only + unità systemd su un VPS--config <path> (obbligatorio), -y
flo failover agent enrollTransizione prepared→enrolled per un tenant--tenant, --config, -y
flo failover agent status|daemon|uninstallStato 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 stormRompe 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 (oppure random).
  • Solo i tenant marcati esplicitamente isTest: true possono essere drillati distruttivamente.
  • --dry-run stampa un piano (non prova il successo); --check verifica i prerequisiti live (streaming, modalità, immagini, segreti, routing, autorità) senza canary, chaos, promozione o scritture DNS.
  • --autonomous attende 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.
  • storm e soak scalano i drill su tenant/esecuzioni; soak accoda una riga JSON per esecuzione in --report e riarma tra le esecuzioni.

Cosa accade ai tenant durante un failover​

  1. 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.
  2. 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.
  3. MON consolida una nuova epoca ed emette l'autorità al target; il PostgreSQL standby è promosso e l'applicazione parte sul target.
  4. La readiness è verificata (PostgreSQL, applicazione, origine diretta) prima che il DNS sia rispostato; il registry è solo una proiezione dello stato MON.
  5. 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 status elenca 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>, poi explain.
  • incident resume <id> -y solo dopo una nuova sonda di sicurezza live; incident abort <id> -y solo 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, poi flo --vps <primary> failover replication setup <tenant> -y per ricreare la protezione.
  • Mantieni docs/DisasterRecoveryDrill.md come runbook passo-passo (autorizzazione, separazione ruoli, arruolamento agent, drill, disarmo, deploy/recupero MON).