Jak se dvouminutový výpadek protáhl na sedm hodin
Aneb proč čtyři síťovky přes dva switche nezachránily vůbec nic
Jak se dvouminutový výpadek protáhl na sedm hodin
Aneb proč čtyři síťovky přes dva switche nezachránily vůbec nic.
Minulý týden nám rutinní firmware update jednoho kusu síťového hardwaru vyřadil produkční Kubernetes cluster z provozu na sedm hodin. Samotný reboot po aktualizace trval asi dvě minuty. Výpadek clusteru trval minut skoro tři sta šedesát.
Ten poměr - dvě minuty triggeru, sedm hodin škod - je celé téma tohoto článku. Trigger byl malý a sám o sobě se dal přežít. Ten obrovský dopad ale způsobilo několik vrstev nenápadných rozhodnutí v návrhu. Každé z nich dávalo samo o sobě smysl, jenže dohromady se nasčítala do něčeho, co nikdo z nás nečekal.
Žádná data jsme neztratili a všechny služby naběhly zpátky. Úložiště bylo po celou dobu v pořádku, committed historie state store byla v pořádku, každý blocklistovaný klient se podařilo ručně namountovat zpět a všechny workloady se vrátily přesně tam, kde byly ve tři ráno. Škoda byla v čase - ne v datech.
Krátká verze: redundance je jen tak dobrá, jako vaše liveness probe. Dlouhá verze následuje.
Třetí ráno, ticho v krajině
Pohotovostní telefon nezazvonil. Žádné alerty, žádné poplachy. Cluster prostě nebyl, a první moment, kdy si toho někdo všiml, byl až ráno.
(To, proč nezazvonil pohotovostní telefon, by si zasloužilo samostatný článek: monitoring běžel ve stejném clusteru, který padl. K tomu se dostaneme.)
Začali jsme tedy tam, kde se začíná vždycky: kdo měl ve tři ráno přístup? Nikdo. Co říká change log? Nic. Co bylo v plánu? Také nic.
Tak jsme to začali rozmotávat zpětně podle toho, co bylo vidět.
Trigger: gateway, která si vyrobila vlastní výpadek
Naše perimeter gateway - jediný kus hardwaru, kterým teče veškerý site traffic - si v noci sama aktualizovala firmware a rebootovala, aby změny aplikovala. Asi o čtyři hodiny později, uprostřed incident response, ji jeden z nás rebootoval znovu - zvenku vypadala zamrzlá a reboot se zdál jako logická věc. (Zamrzlá nebyla. K tomu se ještě dostaneme.)
Každý reboot vyřadil síť z provozu na zhruba dvě minuty.
Samo o sobě to je drobný výpadek. Dvě minuty bez internetu ve tři ráno produkční cluster nepoloží - a záměrný dvouminutový reboot během incident response už vůbec ne. Máme čtyři NICy na node, bondované přes dva nezávislé access switche právě kvůli tomuto scénáři. Redundance byla reálná. Jen nepomohla.
A tohle byla ta část, jejíž pochopení nám trvalo nejdéle.
Amplifier: proč čtyři síťovky nezachránily nic
Každý node v clusteru má stejnou síťovou konfiguraci. Dva páry NICů, každý pár svázaný do link aggregation skupiny, každá skupina napojená na jiný switch. Nad těmito dvěma aggregation skupinami sedí active-backup bond. Na papíře toto přežije výpadek libovolného switche, libovolného NICu i libovolného linku. V praxi to nepřežilo nic z toho - ne v tomto incidentu, a pravděpodobně ani v mnoha dalších.
Důvod je brutálně jednoduchý: active-backup bond nekontroluje link carrier. Pinguje gateway. Když dva po sobě jdoucí ARP pingy na gateway selžou, bond prohlásí svůj slave za mrtvý a přepne. Záložní slave má ale jako ARP target stejnou gateway. Takže ve chvíli, kdy gateway přestane odpovídat na ARP - po dvou sekundách - jsou všichni slaves na hostu současně označeni za mrtvé a bond skončí ve stavu „no active interface".
Přečtěte si to ještě jednou. Každý node, každý NIC, každá aggregation skupina, oba switche - všechno směrovalo liveness check na jeden jediný cíl, a ten cíl bylo přesně to zařízení, jehož reboot celý incident spustil.
Redundance na L1 a L2 byla kompletní. Health probe byla single point of failure. A redundance prohrála.
Že to nebylo nic fyzického, víme z logů samotných switchů: v okamžiku, kdy bondy spadly, se žádný server-facing port nepřepnul. Kabely, switch porty, link agregace - všechno bylo up celou dobu. Bondy se vzdaly jen proto, že nedosáhly na jednu jedinou IP adresu.
Pokud si z tohoto článku máte odnést jednu věc, ať je to tato:
Redundantní datové cesty za liveliness probe se single-point-of-failure nejsou redundance.
Tři pasti, které proměnily krátký výpadek na sedm hodin
Takže oba dvouminutové výpadky sítě zasáhly všechny nody v clusteru. Se správně navrženým softwarem by se nody znovu připojily, cluster by se zkonvergoval, a my bychom o tom ani nedozvěděli.
Dozvěděli jsme se to. Postaraly se o to tři pasti v softwarovém stacku.
Past #1 - crash-loop, ze kterého se nedá restartovat
Naše Kubernetes distribuce používá leader election ve svém control daemonu. Když síť spadla, node držící leader nemohl obnovit lease a asi po 47 sekundách to vzdal a skončil s fatálním logem.
Tento jediný exit zabil child container runtime daemona. Což odstartovalo další past (viz dále). A když pak supervisor daemon restartoval, daemon zjistil, že se nemůže spojit se svým lokálním state store - protože ten byl zamrzlý - a skončil znovu. A znovu. A znovu. Stovky restartů, než se k tomu dostal člověk.
Vzorec byl jednoduchý:
Daemon ke svému startu potřeboval to, co sám právě rozbil.
Self-healing funguje jenom tehdy, když recovery path nezávisí na tom, co právě selhalo.
Past #2 - state store, který zamrzl místo aby spadl
Když spolu s control daemonem umřel container runtime, vzal s sebou i consuemr log pipe state storu. State store běžel dál a dál se snažil zapisovat logy. Pipe se zaplnila. Write se zablokoval. A protože state store loguje pod zámkem, každá další goroutine, která chtěla logovat, se zařadila do fronty za ten zablokovaný write.
Celý proces zamrzl - živý, ale paralyzovaný. Health checky nevracely nic. Log file state store se úplně zastavil. A - což je klíčové - zamrznutí přetrvalo i po obnovení sítě. Pipe byla pořád plná, její consumer pořád pryč. Dokud jsme to ručně neodblokovali, state store zůstal permanentně zaseklý.
Vzorec:
„Přechodný" selhání se může stát permanentním skrze nenápadné propojrní mezi procesy.
Vztah producer–consumer mezi dvěma procesy je v pořádku, dokud se jeden z nich nemá sám zotavit.
Past #3 - DNS žilo uvnitř vlastní failure domain
Tohle byla past, která nás držela na lopatkách ještě hodiny poté, co už bylo všechno ostatní vyřešené.
Naše perimeter gateway forwarduje LAN DNS dotazy na interní resolver. Tento interní resolver je load-balanced služba, která běží uvnitř Kubernetes clusteru, na virtuální IP adrese propagované - a zde ta past zaklapne - DaemonSetem, který si pro svůj restart musí stáhnout container image.
Cluster se z tohoto stavu sám nevyhrabe. Každá minuta dalších pokusů jen potvrzovala, že se mu to nikdy nepovede.
Vzorec:
Základní infrastruktura - DNS, image registry, čas, identita - nesmí žít uvnitř failure domain, kterou obsluhuje.
Když vám spadne DNS pokaždé, když spadne cluster, bez zásahu člověka se cluster sám nikdy nevrátí.
Co jsme vlastně udělali (a co jsme cestou udělali špatně)
Ten druhý reboot - ten ruční - byl ten, co nás definitivně dorazil. Asi čtyři hodiny po incidentu vypadala gateway zvenku zamrzle: ARP neodpovídalo, management UI se táhl, nic s ní zdánlivě nekomunikovalo. Logický krok byl reboot. Logický krok byl špatně. Gateway nebyla zaseklá - zaseklý byl cluster a gateway vypadala divně právě proto, že s ní cluster nedokázal komunikovat. Ten dvouminutový reboot, který měl gateway „probudit", dorazil dva control-plane nody, které do té doby ještě přežívaly na degradovaném clusteru. Lekce pro příště.
Jakmile jsme pochopili, co bylo to skutečné zamrznutí, zotavení už bylo čiště mechanické:
- Odmrazit state store ručně na každém control-plane nodu - vyprázdnit zablokované log pipe čtením z druhého konce. (Ano, opravdu. Odblokovali jsme Linux kernel pipe tím, že jsme četli file descriptor z child file table mrtvého container runtime procesu.)
- Snapshotnout state store předtím, než uděláme cokoliv dalšího. Měli jsme zamrznutí, ne crash, ale nemůžeme věřit ničemu.
- Zabít všechny clusterové procesy na každém control-plane nodu, pak restartovat daemon. Jakmile state store zase odpovídal, startup path daemonu doběhla.
- Rozbít DNS deadlock přesměrováním gatewaye dostala externí upstream DNS, aby něco dokázalo resolvnout hostname image registry. DaemonSet si stáhl image. DNS služba v clusteru ožila. Gateway nakonec dostala zpátky i svůj interní resolver - ale jenom proto, že jsme jí nejdřív dali nouzový externí.
- Vyřešit každého blocklistovaného storage klienta. Worker nody, které byly během výpadku příliš dlouho nedostupné, byly při reconnectu blocklistnuty metadata serverem storage clusteru a jejich mounty zůstaly natrvalo mrtvé bez auto-recovery. Museli jsme je ručně unmountovat a znovy přimountovat.
Cluster byl zpátky asi sedm hodin po prvním rebootu. Nulová ztráta dat. Každý workload, který běžel ve tři ráno, běžel znovu - se stejným stavem a stejnými daty.
Co si z toho odnést (aneb vzorce, co stojí za zapamatování)
Konkrétní nástroje přicházejí a odcházejí. Vzorce zůstávají. Tohle jsou ty, které jsme si z toho odnesli.
Zbytek sítě musí přežít reboot gatewaye - protože gateway rebootovat bude. Auto-updaty, plánovaná údržba, hardwarové chyby, krátký výpadek proudu nebo - jak jsme se přesvědčili na vlastní kůži - člen týmu, který během incident response zahájí reboot, protože zařízení vypadá zamrzle. Reboot přijde, a pravděpodobně častěji, než byste čekali. Správná otázka není jak gateway donutit, aby nerebootovala — ale co selže, když rebootuje, a jak zajistit, aby nic kritického nezáviselo na tom, že v daný okamžik odpovídá na ARP.
Během incidentu může být zjevná oprava špatná oprava. Gateway, která zvenku „vypadá zamrzle", může být ve skutečnosti zdravá gateway sedící vedle rozbitého clusteru. Reboot, který měl gateway probudit, prodloužil výpadek na zbývající uzly - protože gateway ve skutečnosti nebyla rozbitá, rozbitý byl cluster. Před každým rebootem během incidentu patří otázka: Není to, co vidíme, spíš symptom než příčina?
Redundance je jen tak dobrá, jako health check, který nad ní sedí. Čtyři NICy, dva switche, dvě aggregation skupiny - všechno poražené jedním ARP probe cílem. Když má vaše liveness check single point of failure, redundance v data plane pod ní je divadlo. Health probes mají být multi-target, ideálně na zařízení ve vaší broadcast doméně, a nikdy výhradně na to jediné zařízení, jehož pádu se nejvíc bojíte.
Self-healing vyžaduje, aby heal path nebyla závislá na tom, co se právě rozbilo. Control-plane daemony, které se restartují, ale neumí nastartovat bez zdravého state store. State store, co nefunguje bez zdravého loggeru. DNS služby, které potřebují DNS, aby nastartovaly. Každé „auto-recover" si zaslouží otázku: na čem to závisí, a co se stane, když právě tato závislost selže?
Základní služby musí žít mimo failure domain, kterou obsluhují. DNS, image registry, NTP, identita, monitoring, ze kterého se dozvíte, že je něco rozbité - nic z toho nesmí záviset na věci, kterou má udržovat naživu. Říkali jsme to roky. U jedné služby jsme to měli hotové. U zbytku jsme to nedotáhli.
Krátký trigger neznamená krátký výpadek. Dopad dvouminutové události je dán výhradně softwarovým stackem pod ní. Náš trigger byl malý. Dopad byl sedm hodin a recovery byla kompletně manuální. Pokud tato dvě čísla u vás mohou být v takovém poměru, váš stack má kumulující se pasti, které jste ještě neodhalili.
Závěrečná myšlenka
Žádné z designových rozhodnutí, která se nakonec zkombinovala do tohoto výpadku, nebylo v okamžiku, kdy padlo, evidentně špatné. Bond ARP probe mířil na gateway, protože gateway je standardní cíl ARP probe pro ověření, že síť opravdu funguje. DNS služba běžela na cluster VIP, protože cluster VIP jsou standardní způsob, jak získat HA service IP adresy. Leader election control daemonu chránila proti split-brain scénářům, což je přesně ta věc, která ničí clustery. Každé z těchto rozhodnutí dávalo samostatně smysl.
To hlavní ponaučení není toto nedělejte. Je to:
Když se tyhle věci navrství, ptejte se, co se stane, když budou mít všechny špatný den najednou.
Protože dřív nebo později ho nějaká mít bude - a ostatní se přidají.
Cluster je zpátky. Bond probes se přepisují na multi-target. Interní DNS se stěhuje mimo cluster na samostatný resolver. Auto-update na gateway záměrně nevypínáme - gateway rebootovat bude tak jako tak, a fix, který má smysl, je udělat všechno pod ní odolné vůči jejímu rebootu. Žádný z těch fixů není vzrušující. Ani nemusela být.
Tak to obvykle chodí.