La sera del 7 maggio il raffreddamento è venuto meno in una sala dati di una availability zone in us-east-1, i rack hanno perso alimentazione e il blast radius di AWS ha fatto quello che promette la documentazione: il danno è rimasto dentro use1-az4. Ogni altra zona ha continuato a servire. Le aziende che quella notte sono cadute, sono cadute dentro la loro stessa architettura, e lo sappiamo con una precisione insolita perché lunedì Coinbase ha pubblicato un postmortem che lo dice con parole sue. Il trading è stato, nella loro formulazione, "unavailable or degraded for roughly eight hours, with full recovery of all systems taking another twelve", durante un evento in cui il confine di zona della piattaforma ha funzionato.

Questa è la Parte 1 di Nothing Fails Alone, una serie sul divario tra la disponibilità che compri da un cloud provider e la disponibilità che effettivamente costruisci sopra di essa. Ogni parte prende un incidente reale, ne estrae l'affermazione architetturale che mette alla prova e rilascia un piccolo lab che puoi eseguire sul tuo account. I lab vivono in un repo companion, nothing-fails-alone su GitHub, e ognuno di essi è read-only.

Cosa è successo davvero in use1-az4

Il primo incident report sull'AWS Health Dashboard portava il timestamp delle 17:25 PDT del 7 maggio, e poiché l'evento risolto è nel frattempo scomparso dalla dashboard, la formulazione qui sotto è quella riportata da The Register all'epoca: problemi nell'availability zone use1-az4 di us-east-1. Un aggiornamento successivo ha spiegato il meccanismo senza giri di parole: "EC2 instances and EBS volumes hosted on impacted hardware are affected by the loss of power during the thermal event." Alle 18:47 PDT, AWS ha comunicato che stava lavorando per riportare le temperature a livelli normali e ha avvisato che anche altri servizi dipendenti dalle istanze EC2 e dai volumi EBS colpiti in quella zona potevano risultare compromessi.

Il postmortem di Coinbase completa il quadro con la causa fisica: più unità chiller si sono guastate contemporaneamente in un'unica sala dati, e la perdita di raffreddamento ha innescato uno spegnimento di sicurezza termica dei rack colpiti. È questo che significa in pratica "loss of power during the thermal event". I rack non sono stati distrutti; sono stati deliberatamente spenti, perché far girare la computazione senza raffreddamento la distrugge. Le istanze EC2 e i volumi EBS su quei rack sono andati offline insieme, in un edificio, in una zona.

Il ripristino è stato graduale, e onesto nel dirsi graduale. Alle 22:11 PDT di quella notte, AWS ha riferito che era tornata online capacità di raffreddamento aggiuntiva e che alcuni rack si erano ripristinati, con altri da recuperare "in a controlled and safe manner". La capacità di raffreddamento è tornata ai livelli pre-incidente alle 13:50 PDT dell'8 maggio, circa venti ore dopo il primo report, con il ripristino delle istanze in ritardo rispetto al ripristino del raffreddamento. Se il tuo modello mentale di un evento AZ è un blip di quindici minuti, ricalibralo: un evento termico si misura in ore perché la fisica lo fa, e i rack tornano rack per rack, non tutti insieme.

Cosa non è successo

Le altre availability zone in us-east-1 sono rimaste in salute. È la parte di questo incidente che dovrebbe ricevere più attenzione di quanta ne riceva, perché è la parte che AWS ha effettivamente promesso. Le availability zone sono strutture fisicamente separate con alimentazione e raffreddamento indipendenti, e il 7 maggio quell'indipendenza è stata messa alla prova da un guasto reale di una struttura e ha retto. Il dominio di guasto è stato una sala dati; il blast radius è rimasto dentro una zona.

C'è stato esattamente un caveat onesto, e vale la pena citarlo perché conta più avanti in questa serie. AWS ha spostato il traffico lontano dalla zona colpita, ha consigliato ai clienti di trasferire i workload verso le altre zone di us-east-1, e poi ha ammesso: "Customers may experience longer than usual provisioning times." Certo che possono. Quando una zona muore, ogni architettura multi-AZ costruita correttamente in quella regione inizia a rimpiazzare capacità nelle zone superstiti nello stesso momento. Il confine di zona ha retto, ma le zone superstiti sono un pool condiviso, e il failover di massa è un thundering herd contro quel pool. Tieni a mente quel caveat; la Parte 3 di questa serie parla di cosa il tuo piano di ripristino condivide con il piano di ripristino di tutti gli altri.

Le parole di Coinbase

I postmortem delle società quotate di solito sono levigati fino a non dire nulla. Questo no, ed è per questo che vale la pena leggerlo per intero. Due frasi reggono l'intera storia architetturale. Prima: "Our matching engine was pinned to a single building." Seconda, la spiegazione di come: il matching engine di Coinbase Exchange "runs as a Raft-based replicated cluster inside an AWS Cluster Placement Group". E poi la frase che trasforma un evento di zona in un'interruzione di otto ore: "There was no automated cross-zone failover."

Un cluster placement group è single-AZ per costruzione. Non è un limite che AWS nasconde; è il prodotto. Il senso della strategia cluster è impacchettare le istanze su hardware fisicamente vicino, sullo stesso segmento di rete ad alta banda, in modo che la latenza da nodo a nodo sia la più bassa che EC2 possa offrire. Non puoi avere questo e la separazione di zona allo stesso tempo, perché la separazione di zona è distanza e la distanza è latenza. Per un matching engine, dove i microsecondi di latenza da nodo a nodo sono il prodotto, un cluster placement group è la scelta corretta, e Coinbase lo dice: "We make this choice deliberately."

Il problema è cosa ci viveva dentro. Raft esiste per sopravvivere alla perdita di una minoranza di nodi. Metti cinque nodi Raft in un cluster placement group e hai costruito un protocollo di consenso i cui membri condividono un edificio, il che significa che condividono un dominio di guasto, il che significa che il protocollo non può più fare l'unica cosa per cui esiste. Quando AWS ha terminato le istanze EC2 dentro il placement group di Coinbase alle 21:29 ET, tre dei cinque nodi del matching engine sono caduti insieme e il quorum era sparito. Un quorum che condivide una sala dati non è un quorum; è un solo guasto con cinque nomi.

Senza alcun failover automatico cross-zone alle spalle, il ripristino è diventato un processo di persone. Ha richiesto una modifica di codice d'emergenza, rilasciata durante l'incidente, per rimuovere un'assunzione di avvio secondo cui tutti e cinque i nodi del cluster erano risolvibili. Il matching engine è tornato in modalità cancel-only alle 02:25 ET dell'8 maggio, il trading completo è ripreso alle 03:49 ET, il sito retail e l'app mobile erano completamente ripristinati alle 09:53 ET, e l'arretrato dei topic di event-streaming si è smaltito entro le 14:00 ET. Circa otto ore di down, altre dodici fino al ripristino completo, durante un evento che AWS ha contenuto in una zona.

Il multi-AZ è qualcosa che compri e qualcosa che costruisci

La forma generale di questo incidente non è una storia di Coinbase. È il divario tra acquistare infrastruttura multi-AZ e costruire un sistema multi-AZ, e quel divario ha un piccolo numero di forme ricorrenti. Ognuna di queste è una configurazione che sul diagramma di architettura sembra ridondante e non lo è:

  • L'Auto Scaling group a subnet singola. L'ASG rimpiazzerà ben volentieri le istanze guaste per sempre, nella stessa zona, perché la lista di subnet che gli hai dato risolve a una sola AZ. Quando quella zona si degrada, la capacità desiderata non può essere soddisfatta finché un umano non modifica il gruppo. Il self-healing che guarisce dentro il guasto non è self-healing.
  • RDS senza Multi-AZ. Nessuno standby sincrono significa che la perdita di zona non è un failover, è un restore: da uno snapshot, misurato in ore, con perdita di dati fino all'ultimo punto di backup. Il piano di DR accanto a esso di solito presume un failover sotto il minuto, perché qualcuno ha letto la documentazione Multi-AZ e nessuno ha controllato il flag nella console.
  • Il quorum in un cluster placement group. Il pattern di Coinbase: tre, cinque o sette nodi di Raft, etcd o ZooKeeper impacchettati in un gruppo che è single-AZ per definizione. Il layer di consenso si dichiara in salute e fault-tolerant fino al momento in cui l'edificio non è d'accordo.
  • La dipendenza NAT cross-AZ. Una subnet privata nella zona B la cui rotta di default punta a un NAT gateway nella zona A. Il guasto della zona A elimina il traffico in uscita di istanze la cui zona è perfettamente in salute. Questa è invisibile su ogni diagramma perché la freccia punta a "NAT", non a una zona.

Nessuna di queste è esotica. Tutte superano un load test, un game day che uccide solo istanze e la maggior parte delle architecture review, perché ciascuna fallisce solo quando fallisce un'intera zona, e le zone intere falliscono abbastanza di rado da lasciare l'assunzione non verificata per anni.

Il lab: un detector di concentrazione single-AZ

Le affermazioni dovrebbero essere verificabili, quindi questa parte arriva con un lab: 01-single-az-detector, un singolo file Python che scansiona l'account e la regione correnti alla ricerca dei pattern sopra più altri tre. È read-only in senso stretto: ogni chiamata API che effettua è una Describe*. Esegue sette controlli: Auto Scaling group single-AZ, cluster placement group, cluster di dimensione da quorum dentro placement group, istanze RDS single-AZ, cluster RDS i cui membri stanno tutti in una zona, replication group ElastiCache senza Multi-AZ, e subnet private la cui rotta di default attraversa le zone per raggiungere un NAT gateway.

$ python3 detect.py
SEVERITY | RESOURCE                      | AZ                    | WHY
--------------------------------------------------------------------------...
CRITICAL | placement-group/matching-prod | us-east-1a (use1-az4) | 3 instances in cluster placement group 'matching-prod': a quorum-sized cluster in a group that is single-AZ by construction. ...
HIGH     | asg/api-workers               | us-east-1c (use1-az2) | Auto Scaling group spans exactly one AZ. Every replacement instance lands in the same zone ...
HIGH     | rds-cluster/orders            | us-east-1a (use1-az4) | All members of DB cluster 'orders' sit in one AZ. Aurora failover needs a reader in a different zone ...
MEDIUM   | subnet-0a1b2c3d -> nat-0e4f5a6b | us-east-1b (use1-az6) -> us-east-1a (use1-az4) | Private subnet in us-east-1b (use1-az6) routes 0.0.0.0/0 through a NAT gateway in us-east-1a (use1-az4) ...

4 finding(s).

Il controllo che si guadagna la severity CRITICAL è QUORUM_IN_ONE_AZ: un cluster placement group che contiene esattamente 3, 5 o 7 istanze in esecuzione. Quei numeri non sono arbitrari; sono la forma di un quorum Raft, etcd o ZooKeeper, e un workload a forma di quorum concentrato in una zona è esattamente la configurazione che ha messo giù Coinbase. Un placement group con un altro numero di istanze viene comunque segnalato, ma come HIGH: è un'ottimizzazione di latenza che qualcuno dovrebbe confermare essere stata scelta di proposito.

Due comportamenti sono deliberati. Primo, l'exit code: la scansione esce con 0 ogni volta che si completa, con o senza finding, perché questo è un report, non un gate. Se IAM nega una delle API, quel controllo viene saltato con un warning di una riga su stderr, i controlli restanti girano comunque e l'exit code resta 0; ottieni exit code 2 solo quando la scansione non può girare affatto, nessuna credenziale, nessuna regione, nessun boto3. Controlla stderr prima di fidarti di una tabella pulita. Secondo, ogni finding riporta la zona sia come nome sia come ID, per esempio us-east-1a (use1-az4). Il nome è un alias che AWS randomizza per account; l'ID è la zona fisica. La Health Dashboard diceva use1-az4, e senza l'ID non puoi sapere se quella fosse la tua us-east-1a. Oggi quella distinzione sembra un dettaglio. La Parte 2 parla dell'incidente in cui smette di esserlo.

Modalità di guasto e cosa tenere d'occhio

Il trade-off sottostante a tutto questo è legittimo, quindi nominalo invece di far finta che non esista. La collocazione single-AZ compra latenza, il trasferimento dati cross-AZ costa denaro, e la replica sincrona tra zone costa latenza in scrittura. Coinbase non è inciampata in un placement group; l'ha progettato e ha accettato un rischio. Il guasto non è stata la scelta, è stato che la conseguenza della scelta, nessun percorso automatico fuori dalla zona, è stata scoperta alle 21:29 durante l'incidente invece di essere decisa in una design review prima di esso.

Cosa tenere d'occhio nel tuo account, oltre a far girare il detector: l'aritmetica del quorum, non solo la sua collocazione, perché due nodi etcd su tre in una zona sono lo stesso difetto con una riga di tabella più amichevole. ASG che elencano diverse subnet che risolvono tutte alla stessa zona, motivo per cui il detector risolve le subnet di VPCZoneIdentifier invece di fidarsi della lista di AZ. Cache senza repliche: una perdita di zona prende ElastiCache a freddo, e la conseguente tempesta di miss atterra sul tuo database nel momento peggiore possibile. E il provisioning di capacità durante un evento di zona reale, che è l'unica cosa che il detector non può vedere, perché è una proprietà dell'architettura di tutti gli altri, non della tua. AWS te l'ha detto essa stessa, nel mezzo dell'incidente: longer than usual provisioning times. Il confine di zona è reale, e ha retto. Ciò che ci costruisci sopra è tuo.

Leggi questo dopo

Per consulenza su AWS, architettura cloud, resilience review e lavoro di piattaforma, parti da ercanermis.com.

Riferimenti