Case study
Oporavak RabbitMQ klastera nakon ispada noda: kako je nedovoljno pracenje dovelo do split-brain klastera
Klijent nas je kontaktirao nakon sto je aplikacija pocela povremeno da omasuje pri upisu u RabbitMQ Streams.
Neobicno je bilo to sto RabbitMQ nije bio potpuno nedostupan.
Neki zahtevi su prolazili. Drugi su padali sa greskama slicnim sledecim:
| |
RabbitMQ logovi su takodje sadrzali ponovljene timeout-e za coordinator-a:
Sa strane aplikacije, otkazi su delovali gotovo nasumicno.
Sva tri RabbitMQ poda su takodje bila u stanju Running, pa nije bilo ociglednog ispada na nivou Kubernetes-a.
Pravi problem je bio dublji: RabbitMQ nodovi vise nisu bili saglasni oko stanja klastera, a Stream coordinator je imao vrlo razlicite Raft istorije na razlicitim nodovima.
To je promenilo pitanje oporavka sa:
Koji RabbitMQ nod je ziv?
na:
Koji nodovi sadrze najnovije konzistentno stanje koje mora da se sacuva?
Incident
Klaster se sastojao od tri RabbitMQ noda:
koji su pokretali RabbitMQ 4.1.3 sa Erlang 27.3.4.
Na Kubernetes nivou, sve je u pocetku izgledalo prihvatljivo:
Ali running pod samo dokazuje da je kontejner ziv. Ne dokazuje da broker i dalje ispravno ucestvuje u RabbitMQ klasteru.
Kada smo proverili sam RabbitMQ:
| |
rabbitmq-slough-0 je prijavio samo sebe kao clana klastera.
Sa njegove tacke gledista nije bilo prijavljenih particija niti alarma.
To je vec bilo sumnjivo: nod koji veruje da je zdrav jednocvorni klaster veoma je razlicit od ocekivanog trocvorog klastera.
flowchart LR
APP[Application] --> S[RabbitMQ Service]
S --> R0[rabbitmq-slough-0]
S --> R1[rabbitmq-slough-1]
S --> R2[rabbitmq-slough-2]
R0 -. different cluster view .-> R1
R1 <--> R2
APP --> ERR[Intermittent<br/>Stream errors]
Samo clanstvo u klasteru nije bilo dovoljno
Cinjenica da se klaster podelio i dalje nije govorila koje stanje treba da prezivi oporavak.
Za RabbitMQ Streams, vazan dokaz bio je unutar Stream coordinator-a.
Pregledali smo ga na svakom nodu:
| |
Kada su nodovi mogli dovoljno da komuniciraju za poredjenje, razlika je bila jasna.
| Node | State | Term | Log / commit | Snapshot |
|---|---|---|---|---|
rabbitmq-slough-0 | pre_vote | 23 | 3,794 | none |
rabbitmq-slough-1 | leader | 24 | 1,537,397 | 1,531,904 |
rabbitmq-slough-2 | follower | 24 | 1,537,397 | 1,531,904 |
Ovo je bio kljucni dokaz.
rabbitmq-slough-0 je imao commit poziciju od samo:
| |
dok su se druga dva noda slagala oko:
| |
Takodje su se slagali oko novijeg Raft term-a:
| |
i oko istog snapshot-a:
| |
rabbitmq-slough-1 je bio leader, a rabbitmq-slough-2 njegov follower.
flowchart TB
R0["rabbitmq-slough-0<br/>pre_vote<br/>term 23<br/>commit 3,794"]
subgraph AUTH["Newest consistent state"]
R1["rabbitmq-slough-1<br/>LEADER<br/>term 24<br/>commit 1,537,397"]
R2["rabbitmq-slough-2<br/>FOLLOWER<br/>term 24<br/>commit 1,537,397"]
R1 <--> R2
end
R0 -. divergent history .-> AUTH
Koje stanje je bilo autoritativno?
Odgovor nije bio odredjen staroscu poda, Kubernetes zdravljem ili time koji je broker slucajno prihvatio klijentsku konekciju.
Najjaci dokaz bilo je slaganje vise Raft glasaca.
Dva noda su nezavisno prijavila:
- isti trenutni term;
- istu commit poziciju;
- isti snapshot;
- ispravan leader/follower odnos.
Izolovani nod je prijavio dramatično stariju istoriju i ostao u pre_vote.
To je odluku uciniло jednostavnom:
Ova razlika je vazna tokom oporavka.
Komande kao sto su reset, force_reset, force_boot, offline uklanjanje noda ili ponovo kreiranje persistent storage-a mogu da odbace stanje ili redefinisu koji klaster metadata prezivljava.
Zato je bezbedan redosled sledeci:
- prikupiti stanje sa svakog noda;
- uporediti Raft term, commit index, ulogu i snapshot;
- utvrditi koji nodovi se slazu oko najnovije istorije;
- zastititi to stanje;
- tek onda uskladiti zastareli nod i obnoviti normalno clanstvo u klasteru.
Klaster koji se uspesno formira oko pogresnog stanja i dalje je neuspesan oporavak.
Zasto su padali samo neki upisi?
Pocetni simptom aplikacije sada je imao smisla.
Procesi brokera su bili zivi, ali RabbitMQ vise nije radio sa jednim konzistentnim stanjem Stream coordinator-a.
Neke operacije su i dalje mogle da se zavrse kroz dostupne puteve.
Druge su zahtevale aktivnost coordinator-a i padale sa:
| |
Zato je incident izgledao povremeno, a ne kao potpuni RabbitMQ ispado.
To takodje objasnjava zasto su obicne infrastrukturne provere to propustile.
Monitoring:
nije bio dovoljan.
Kvar je bio u distribuiranom stanju RabbitMQ klastera.
Korenski uzrok: degradacija klastera nije bila otkrivena
Oporavak ispravnog RabbitMQ stanja resio je trenutni incident.
Ali to nije bio kompletan fix.
Produkcioni RabbitMQ klaster sa tri noda ne bi trebalo da neprimetno degradira u razdvojene prikaze klastera i da tako ostane dok aplikacije ne pocnu da vracaju greske.
Ovaj incident je trebalo mnogo ranije da bude otkriven.
flowchart LR
A[Inter-node connectivity fails] --> B[RabbitMQ cluster degrades]
B --> C{Cluster alerting?}
C -->|Yes| D[Alert]
D --> E[Investigate before<br/>application impact]
C -->|No| F[Failure remains unnoticed]
F --> G[Coordinator problems]
G --> H[Application write errors]
Nedostajala je kontrola pracenja RabbitMQ cluster connectivity, a ne samo dostupnosti Kubernetes poda.
Upozori kada RabbitMQ nod izgubi svoje peerove
RabbitMQ izloži stanje Erlang distribution konekcija izmedju nodova kroz Prometheus metrike.
Za fiksni trocvorni klaster, svaki broker bi normalno trebalo da vidi dve peer konekcije u up stanju.
Alarm zato moze da proveri ovu invarijantu.
Na primer:
| |
Tacne labele zavise od Prometheus konfiguracije, ali princip je vazniji od samog upita.
Alarm bi trebalo da odgovori na pitanje:
Da li svaki RabbitMQ broker i dalje moze da komunicira sa svim ocekivanim cluster peers?
umesto samo:
Da li tri RabbitMQ poda rade?
Za ovo okruzenje, pracena invarijanta postaje:
Kratak for interval sprecava da prolazni restarti izazovu nepotrebne alarme, a i dalje otkriva stvarni split klastera mnogo pre nego sto stigne do aplikacionog saobracaja.
Prakticna lekcija
Prilikom oporavka distribuiranog sistema, nod koji je ziv nije nuzno onaj cije stanje treba da prezivi.
Za RabbitMQ Streams, uporedite samo distribuirano stanje.
U ovom incidentu:
Slaganje izmedju nodova 1 i 2 dalo nam je dokaz potreban da sacuvamo ispravno stanje umesto da slepo rebuild-ujemo klaster oko bilo kog brokera koji je slucajno bio dostupan.
Ali oporavak je bio samo pola resenja.
Dublji operativni problem bio je to sto je RabbitMQ klaster vec degradirao bez generisanja alarma.
Oporavak je resio incident. Pracenje zdravlja klastera je resilo korenski uzrok.
Za produkcione incidente gde Kubernetes resursi izgledaju zdravo, ali aplikacije i dalje dozivljavaju povremene infrastrukturne otkaze, pogledajte nasu DevOps troubleshooting uslugu.