Van egy bizonyos fajta némaság, amit minden üzemeltető ismer. Az a pár másodperc, amikor a monitoring riaszt, megnyitod a terminált, lefuttatod az első kubectl get pods parancsot, és a kimenet nem azt mutatja, amire számítottál. Nem egyetlen piros sor. Hanem tíz. Húsz. Több namespace-ben, több node-on. És a gyomrod összeszorul, mert tudod: ez nem egyetlen szolgáltatás hibája.
Ez a cikk egy ilyen estéről szól. Egy anonimizált, valós production incidensről, ahol egy három node-os MariaDB-Galera klaszter teljesen szétesett egy látszólag ártalmatlan karbantartási lépés után. Leírjuk a teljes diagnózist, a konkrét parancsokat, a valós hibaüzeneteket, és ami a legfontosabb: a helyreállítás helyes sorrendjét — azt a részt, amit a legtöbb tutorial kihagy. A galera cluster helyreállítás ugyanis önmagában nem nehéz. A nehéz az, hogy felismerd, mikor nem a Galera a probléma, hanem csak a tünet.
A környezet, amiben dolgoztunk
Mielőtt belevágnánk, érdemes tisztázni a terepet, mert a hibakeresés sorrendje innen következik.
A klaszter egy K3S alapú Kubernetes környezet volt, ARM-alapú worker node-okkal, hat workerrel és egy control-plane node-dal. A perzisztens tárolást egy felhőszolgáltató CSI drivere adta, a hálózati réteget pedig Flannel CNI. Az adatbázis-réteg egy Bitnami chartból telepített, három node-os MariaDB-Galera klaszter volt, dedikált namespace-ben, háromszoros, virtuálisan szinkron replikációval. Erre épült több ügyfél alkalmazása, és — ami később kritikus lett — egy levelezőrendszer is, amely közvetlenül az adatbázistól függött.
A Galera lényege épp az erőssége: minden node írható, és a klaszter kvórum (quorum) alapján dönti el, mikor működőképes. Három node-os klaszternél a kvórum kettő. Ha egyszerre csak egy node esik ki, a maradék kettő tovább működik, és a visszatérő node automatikusan újra csatlakozik. De ha egyszerre több node-ot veszítesz el — vagy ha a hálózat szakad szét alattuk —, a klaszter elveszíti a kvórumot, és a túlélő node-ok is leállnak. Ez nem hiba, hanem védelem: a Galera inkább leáll, mintsem hogy szétfutó, inkonzisztens adatot engedjen.
Hogyan kezdődött: az ártalmatlannak tűnő lépés
A kiváltó ok egy ütemezett node-karbantartás volt. Az egyik worker node-ot ideiglenesen eltávolítottuk a klaszterből egy frissítés miatt. Rutinfeladat — elvileg.
A gond ott kezdődött, hogy amikor a node visszatért, a Flannel CNI hálózati állapota nem állt helyre tisztán. A node felépült, a kubelet elindult, de a pod-hálózat hiányos maradt. Ez pedig elindította a dominót:
- A visszatérő node-okon a pod-ok nem érték el az API servert a klaszteren belüli hálózaton keresztül.
- A felhő CSI controller crashloop-ba került, mert a node, amin futott, nem érte el a felhőszolgáltató API-ját DNS-en keresztül — így új volume-okat sem lehetett csatolni.
- A CoreDNS épp egy olyan node-ra ütemeződött át, ahol a CNI hibás volt, így a klaszter szintű DNS-feloldás is leállt.
- A Galera node-ok kommunikációja megszakadt, elveszett a kvórum, és a teljes adatbázis-klaszter leállt.
- A node újraregisztrációja során elvesztek a node-ok címkéi (label-jei), amiket az ütemezés használt.
Amikor megnyitottam a terminált, ez fogadott:
prod-mariadb mariadb-galera-1 0/1 CrashLoopBackOff 10 (2m1s ago) 15h
prod-mariadb mariadb-galera-2 0/1 CrashLoopBackOff 7 (41s ago) 15h
És a levelezőrendszer pod-jai ContainerCreating és Error állapotban toporogtak. A naiv reakció ilyenkor az, hogy az ember rárohan a Galerára. De ez a legnagyobb hiba, amit elkövethetsz.
Az első tanulság: egy problémát egyszerre
A mariadb replikáció hiba itt nem ok volt, hanem következmény. A Galera azért nem indult el, mert alatta a hálózat és a tároló nem működött. Ha ebben a pillanatban erőből bootstrapolod a Galerát egy hibás hálózaton, két dolog történhet: vagy nem indul el úgysem, vagy — ami sokkal rosszabb — szétváló klasztert (split-brain) hozol létre, ahol két node külön-külön „primary”-nek hiszi magát, és az adat menthetetlenül szétfut.
Ezért a helyreállítás sorrendje a függőségi láncot követte, nem a riasztások sorrendjét:
Működik a CNI / hálózat?
↓ igen
Fut a CSI driver (csatolhatók a volume-ok)?
↓ igen
Elérhető a DNS (CoreDNS)?
↓ igen
MOST jöhet a Galera bootstrap
↓
És csak ezután az adatbázistól függő szolgáltatások
Az első ellenőrzés tehát nem az adatbázis volt, hanem a hálózat a visszatért node-on:
ssh worker-node-2 ip link show flannel.1
ssh worker-node-2 curl -sk --connect-timeout 3 https://10.43.0.1:443 | head -c 50
A válasz megnyugtató volt:
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 ...
{ "kind": "Status", "apiVersion": "v1", ...
A flannel.1 interfész élt, és az API server 401 Unauthorized választ adott — ami ebben a kontextusban jó hír: a hálózat működik, csak az autentikáció hiányzik a nyers curl-ből. A hálózat helyreállt. A CSI és a CoreDNS rendbe jött. Csak ezután fordultunk az adatbázishoz.
A diagnózis: mit mond a Galera
Az első lépés mindig a logok elolvasása. Ne nyúlj a pod-okhoz, amíg nem érted, mit mond a klaszter:
kubectl get pods -n prod-mariadb -o wide
kubectl logs -n prod-mariadb mariadb-galera-0 --previous --tail=30
A galera-0 logja kristálytiszta volt — és ez a sor minden Galera-üzemeltető rémálma:
[ERROR] WSREP: It may not be safe to bootstrap the cluster from this node.
It was not the last one to leave the cluster and may not contain all the updates.
To force cluster bootstrap with this node, edit the grastate.dat file manually
and set safe_to_bootstrap to 1.
[ERROR] WSREP: wsrep::connect(gcomm://) failed: 7
[ERROR] Aborting
Ez a hibaüzenet pontosan azt jelenti, amit ki is mond. A Galera 3.19 óta van egy „Safe-to-Bootstrap” nevű védelmi mechanizmusa: a klaszter feljegyzi, melyik node hagyta el utoljára a klasztert, és csak azt engedi biztonságosan elsőként elindulni. Ha egy másik node-ot próbálsz bootstrapolni, a Galera leáll, mert az a node esetleg nem tartalmazza a legfrissebb tranzakciókat.
A kulcskérdés tehát: melyik node bootstrapolható biztonságosan? A választ a grastate.dat fájl adja meg, ami minden node adatkönyvtárában ott van. Lefuttattam mindhárom node-on:
for i in 0 1 2; do
echo "=== galera-$i ==="
kubectl exec -n prod-mariadb mariadb-galera-$i -- \
cat /bitnami/mariadb/data/grastate.dat 2>/dev/null || echo "nem elérhető"
done
Az eredmény ez volt:
=== galera-0 ===
nem elérhető
=== galera-1 ===
# GALERA saved state
version: 2.1
uuid: 7200262c-15e6-11f0-84bf-020271b61c84
seqno: -1
safe_to_bootstrap: 1
=== galera-2 ===
nem elérhető
Ez a kimenet aranyat ér. A galera-1 node-on safe_to_bootstrap: 1 áll — ez azt jelenti, hogy a klaszter ő rajta keresztül indítható biztonságosan. Ő volt az utolsó, aki tisztán elhagyta a klasztert, így nála van a legfrissebb állapot.
Egy gyakori félreértés a seqno: -1. Sokan megijednek tőle, pedig ez itt teljesen normális: leállított, futó mysqld nélküli állapotban a Galera a szekvenciaszámot a memóriában tartja, és a grastate.dat-ba -1-et ír. A seqno akkor lesz érdekes, ha hard crash történt, és mindhárom node-on safe_to_bootstrap: 0 áll — ilyenkor a --wsrep-recover paraméterrel kell kideríteni node-onként az utolsó tranzakció pozícióját, és a legnagyobb értékű node-ot kell bootstrapolni. Nekünk szerencsénk volt: a Safe-to-Bootstrap flag egyértelmű választ adott, és nem kellett a --wsrep-recover ágra lépnünk.
A helyreállítás: lépésről lépésre
Innen következett a tényleges galera cluster helyreállítás. Itt a teljes sorrend, ahogy végrehajtottuk.
1. lépés — A hiányzó pod újragenerálása. Egy bosszantó részlet: a galera-1 pod maga nem is létezett, a StatefulSet valamiért nem hozta létre. Bootstrapolni pedig nem tudsz olyan pod-ot, ami nincs. Először tehát a StatefulSet-et kellett rávenni, hogy újraépítse:
kubectl describe statefulset mariadb-galera -n prod-mariadb | grep -A5 "Events"
kubectl rollout restart statefulset mariadb-galera -n prod-mariadb
2. lépés — A bootstrap flag biztosítása. Megvártam, amíg a pod létrejön (akár CrashLoopBackOff állapotban is), majd ellenőriztem és szükség esetén beállítottam a flaget. Mivel a galera-1-en már safe_to_bootstrap: 1 állt, ezt a lépést csak biztonságból futtattam le:
kubectl get pod mariadb-galera-1 -n prod-mariadb -w
kubectl exec -n prod-mariadb mariadb-galera-1 -- \
bash -c "sed -i 's/safe_to_bootstrap: 0/safe_to_bootstrap: 1/' /bitnami/mariadb/data/grastate.dat"
3. lépés — A klaszter bootstrapolása a helyes node-ról. A Bitnami chart esetében nem érdemes kézzel babrálni az init containerekkel; a chartnak van dedikált bootstrap paramétere. Ezzel a galera-1 indult el elsőként, primary komponensként:
helm upgrade prod-mariadb-galera bitnami/mariadb-galera \
-n prod-mariadb \
--reuse-values \
--set galera.bootstrap.bootstrapFromNode=1 \
--set galera.bootstrap.forceSafeToBootstrap=true
4. lépés — A bootstrap figyelése. A logokat valós időben követtem, kiszűrve a lényeges sorokat:
kubectl logs -n prod-mariadb mariadb-galera-1 -f | grep -E "WSREP|ERROR|Synchronized"
Itt láttam viszont a Galera gcache-helyreállítását is, ami önmagában is megnyugtató jel volt — a node a meglévő write-set cache-ből próbált talpra állni, nem nulláról:
WSREP: GCache::RingBuffer initial scan... 100.0% complete.
WSREP: Recovering GCache ring buffer: found gapless sequence 131324998-131366714
WSREP: Start replication
WSREP: Connecting with bootstrap option: 1
5. lépés — A többi node csatlakoztatása. Amint a galera-1 elérte a primary állapotot, a galera-0 és a galera-2 normál indítással automatikusan megkereste a primary komponenst, és State Snapshot Transferrel (SST) szinkronizálta magát. Ez a Galera szépsége: a többi node-ot nem kell külön bootstrapolni, csak elindítani.
Néhány perccel később ez fogadott:
mariadb-galera-0 1/1 Running 6 (5m17s ago) 10m
mariadb-galera-1 1/1 Running 0 4m57s
mariadb-galera-2 1/1 Running 0 4m57s
A klaszter 3/3-on állt. A wsrep_cluster_size újra 3 volt, a wsrep_cluster_status pedig Primary. A galera-0 hat újraindítása nem ijesztő — ezek a bootstrap előtti crashloop maradványai voltak, mielőtt a primary felállt.
A mellékhatás, amire senki sem számít: a Multi-Attach hiba
Amikor a Galera helyreállt, az tőle függő levelezőrendszer pod-jai elkezdtek volna elindulni — de nem tudtak. Az esemény ez volt:
Warning FailedAttachVolume Multi-Attach error for volume "pvc-a2cb3c33-..."
Volume is already used by pod(s) mailu-dovecot-..., mailu-rspamd-..., debug-pod-1
Ez egy klasszikus következménye a kaszkád-hibáknak. A volume ReadWriteOnce (RWO) módú volt, vagyis egyszerre csak egy pod használhatja. A régi, „zombi” pod-ok azonban — amelyek a node kiesésekor sosem terminálódtak tisztán — még mindig fogták a volume-ot. Az új pod nem tudta megkaparintani.
A megoldás a beragadt pod-ok erőltetett törlése volt:
kubectl delete pod mailu-dovecot-6ffbc66dc6-b56l9 -n prod-mailu --force --grace-period=0
kubectl delete pod mailu-rspamd-ff6fcfcf5-zl7l4 -n prod-mailu --force --grace-period=0
kubectl delete pod debug-pod-1 -n prod-mailu --force --grace-period=0
A --force --grace-period=0 itt indokolt: a pod-ok logikailag már halottak voltak, csak a Kubernetes objektum élt. Amint elengedték a volume-ot, az új pod-ok automatikusan elindultak. Ez is a sorrend fontosságát igazolja: az adatbázistól függő rétegekhez csak azután lehetett hozzányúlni, hogy az adatbázis maga állt.
Amit a tutorialok nem írnak le
Ha végigolvasol tíz Galera-helyreállítási útmutatót, mind ugyanazt mondja: nézd meg a grastate.dat-ot, állítsd be a safe_to_bootstrap-ot, bootstrapolj. Ez igaz, de hiányos. A valóságban a mariadb replikáció hiba ritkán önálló esemény. Íme, amit ez az incidens megtanított, és amit ritkán találsz meg a dokumentációban:
A sorrend fontosabb, mint a parancsok. A Galera bootstrap egy rossz hálózati állapotban kárt okozhat. Mindig állítsd helyre alatta a hálózatot, a tárolót és a DNS-t, mielőtt az adatbázishoz nyúlsz.
A seqno: -1 nem mindig vészjel. Tiszta leállásnál normális. A pánikgomb a safe_to_bootstrap: 0 minden node-on egyszerre — ez jelenti, hogy a --wsrep-recover útra kell lépned.
A kvórumvesztés gyakran nem adatbázishiba. Sokszor a hálózati réteg vagy egy node-eltávolítás a valódi ok. Ha csak a Galerát javítod, de a kiváltó okot nem, néhány óra múlva újra elszáll.
A zombi pod-ok és RWO volume-ok a kaszkád természetes velejárói. Számíts rá, hogy a helyreállított adatbázis után a tőle függő szolgáltatások beragadt volume-okkal fognak küzdeni.
Hogyan előzhető meg legközelebb
Egy incidens akkor ér valamit, ha tanulsz belőle. A mi tennivalóink listája az eset után így nézett ki:
- A node-címkéket kódban tároljuk. A label-ek elvesztése az újraregisztrációnál teljesen elkerülhető, ha a címkék infrastruktúra-as-code formában (például deklaratív konfigban) léteznek, és egy paranccsal visszaállíthatók.
- A felhő CSI controllert stabil node-hoz rögzítjük
nodeSelector-ral, hogy egy hibás node ne tudja crashloop-ba vinni a teljes tárolóréteget. - A
grastate.dattartalmát monitoringgal figyeljük. Ha egy nodesafe_to_bootstrapállapota vagy awsrep_cluster_sizeeltér a várttól, riasztás megy ki, mielőtt a felhasználó észlelné a hibát. A megfelelő szerver monitoring eszközök kiválasztása itt nem luxus, hanem üzletmenet-folytonossági kérdés. - A leállás előtt mindig állítsuk le az írásokat. Ha a klasztert szándékosan állítod le, előbb állítsd meg az írásokat, hogy minden node ugyanazon a ponton álljon meg — így a visszaindítás gyors IST lesz, nem lassú, teljes SST.
- Gyakoroljuk a teljes klaszter-újraindítást. A Galera teljes újraépítése pont olyan, mint a backupból való visszaállítás: nem akkor akarod először kipróbálni, amikor baj van. Egy begyakorolt lépéssor csökkenti az állásidőt és — ami legalább ennyire fontos — az üzemeltető stresszét.
A nagyobb kép
Ennek az estének a tanulsága túlmutat a Galerán. A magas rendelkezésre állás nem egyetlen technológia, hanem rétegek összjátéka: hálózat, tároló, DNS, adatbázis és a rájuk épülő alkalmazások. Egyetlen réteg hibája végighullámzik a többin, és a hibakeresés művészete épp annak felismerése, hogy melyik réteg az ok, és melyik csak a tünet. Ha mélyebben érdekel a téma, érdemes elolvasni, hogyan építünk fel magas rendelkezésre állású weboldalakat és webáruházakat, és mire figyelünk a szerverek biztonsági konfigurációjánál.
A valóság az, hogy ezek az incidensek mindig a legrosszabbkor jönnek, és a különbség egy húszperces és egy hatórás állásidő között szinte mindig a felkészültség és a helyes sorrend ismerete. Egy production adatbázis-klaszter helyreállítása nem hősködés — fegyelmezett, módszeres munka, ahol a türelem többet ér a gyorsaságnál.
Ha a céged kritikus rendszerei Kubernetesen, konténerizált adatbázisokon vagy felügyelt felhőinfrastruktúrán futnak, és nem szeretnéd, hogy egy ilyen este a te terhed legyen, abban tudunk segíteni. A felhő üzemeltetés és a szerverek távfelügyelete szolgáltatásunk pontosan erről szól: hogy amikor a monitoring hajnali háromkor riaszt, ne a te telefonod csörögjön. És ha érdekel, hogyan lehet mindezt költséghatékonyan megtenni, nézd meg, hogyan spórolható meg 30-40% felhő környezetben.
A megbízhatóság nem szerencse kérdése. Sorrend, gyakorlás és a megfelelő ember a terminál másik végén — ennyi az egész.
