ProHoster > Blog > podávání > Příběh o chybějících paketech DNS z technické podpory Google Cloud
Příběh o chybějících paketech DNS z technické podpory Google Cloud
Z editoru blogu Google: Přemýšleli jste někdy o tom, jak inženýři Google Cloud Technical Solutions (TSE) zpracovávají vaše požadavky na podporu? Technici technické podpory TSE jsou odpovědní za identifikaci a nápravu zdrojů problémů hlášených uživateli. Některé z těchto problémů jsou docela jednoduché, ale někdy narazíte na lístek, který vyžaduje pozornost několika inženýrů najednou. V tomto článku nám jeden ze zaměstnanců TSE řekne o jednom velmi ošemetném problému ze své nedávné praxe - případ chybějících DNS paketů. V tomto příběhu uvidíme, jak se inženýrům podařilo situaci vyřešit a co nového se při opravě chyby naučili. Doufáme, že vás tento příběh nejen poučí o hluboce zakořeněné chybě, ale také vám poskytne přehled o procesech, které se týkají podání žádosti o podporu ve službě Google Cloud.
Odstraňování problémů je věda i umění. Vše začíná vytvořením hypotézy o důvodu nestandardního chování systému, načež je testován na pevnost. Než však zformulujeme hypotézu, musíme jasně definovat a přesně formulovat problém. Pokud otázka zní příliš vágně, budete muset vše pečlivě analyzovat; Toto je „umění“ odstraňování problémů.
V rámci Google Cloud se tyto procesy stávají exponenciálně složitějšími, protože Google Cloud se snaží ze všech sil zaručit soukromí svých uživatelů. Z tohoto důvodu nemají inženýři TSE přístup k úpravám vašich systémů ani možnost prohlížet konfigurace tak široce jako uživatelé. Proto, abychom otestovali jakoukoli z našich hypotéz, nemůžeme my (inženýři) rychle upravit systém.
Někteří uživatelé se domnívají, že vše opravíme jako mechanici v autoservisu a jednoduše nám pošlete ID virtuálního stroje, zatímco ve skutečnosti proces probíhá v konverzačním formátu: shromažďování informací, vytváření a potvrzování (nebo vyvracení) hypotéz, a nakonec, rozhodovací problémy jsou založeny na komunikaci s klientem.
Dotyčný problém
Dnes tu máme příběh s dobrým koncem. Jedním z důvodů úspěšného vyřešení navrhovaného případu je velmi podrobný a přesný popis problému. Níže můžete vidět kopii prvního lístku (upravený tak, aby skryl důvěrné informace):
Tato zpráva obsahuje pro nás mnoho užitečných informací:
Zadán konkrétní virtuální počítač
Je indikován samotný problém - DNS nefunguje
Je uvedeno, kde se problém projevuje - VM a kontejner
Jsou uvedeny kroky, které uživatel provedl k identifikaci problému.
Požadavek byl zaregistrován jako „P1: Critical Impact - Service Unusable in production“, což znamená neustálé sledování situace 24/7 podle schématu „Follow the Sun“ (více o priority uživatelských požadavků), s jeho přesunem z jednoho týmu technické podpory do druhého s každým posunem časového pásma. Ve skutečnosti, když se problém dostal k našemu týmu v Curychu, už obletěl zeměkouli. Do této doby uživatel přijal zmírňující opatření, ale obával se opakování situace ve výrobě, protože hlavní příčina dosud nebyla objevena.
V době, kdy letenka dorazila do Curychu, jsme již měli po ruce následující informace:
Obsah /etc/hosts
Obsah /etc/resolv.conf
Výkon iptables-save
Sestaveno týmem ngrep pcap soubor
S těmito údaji jsme byli připraveni zahájit fázi „vyšetřování“ a odstraňování problémů.
Naše první kroky
Nejprve jsme zkontrolovali protokoly a stav serveru metadat a ujistili se, že funguje správně. Server metadat odpovídá na IP adresu 169.254.169.254 a mimo jiné je zodpovědný za kontrolu doménových jmen. Také jsme dvakrát zkontrolovali, že firewall funguje správně s virtuálním počítačem a neblokuje pakety.
Byl to nějaký zvláštní problém: kontrola nmap vyvrátila naši hlavní hypotézu o ztrátě paketů UDP, takže jsme v duchu přišli s několika dalšími možnostmi a způsoby, jak je zkontrolovat:
Jsou pakety zahazovány selektivně? => Zkontrolujte pravidla iptables
Není to moc malé? MTU? => Zkontrolujte výstup ip a show
Ovlivňuje problém pouze pakety UDP nebo také TCP? => Odjet dig +tcp
Jsou vráceny pakety generované dig? => Odjet tcpdump
Funguje libdns správně? => Odjet strace pro kontrolu přenosu paketů v obou směrech
Zde se rozhodneme zavolat uživateli k řešení problémů živě.
Během hovoru jsme schopni zkontrolovat několik věcí:
Po několika kontrolách vyřadíme pravidla iptables ze seznamu důvodů
Zkontrolujeme síťová rozhraní a směrovací tabulky a dvakrát zkontrolujeme správnost MTU
Zjišťujeme to dig +tcp google.com (TCP) funguje jak má, ale dig google.com (UDP) nefunguje
Po odjetí tcpdump během práce dig, zjistíme, že se vracejí UDP pakety
Jedeme pryč strace dig google.com a vidíme, jak správně volá dig sendmsg() и recvms(), druhý je však přerušen časovým limitem
Bohužel přichází konec směny a my jsme nuceni problém eskalovat do dalšího časového pásma. Požadavek však v našem týmu vzbudil zájem a kolega navrhuje vytvořit počáteční DNS balíček pomocí scrapy modulu Python.
from scapy.all import *
answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())
Tento fragment vytvoří paket DNS a odešle požadavek na server metadat.
Uživatel spustí kód, vrátí se odpověď DNS a aplikace ji přijme, čímž potvrdí, že na úrovni sítě není žádný problém.
Po další „cestě kolem světa“ se požadavek vrací našemu týmu a já ho zcela přenáším na sebe v domnění, že pro uživatele bude pohodlnější, když požadavek přestane kroužit z místa na místo.
Mezitím uživatel laskavě souhlasí s poskytnutím snímku obrazu systému. To je velmi dobrá zpráva: možnost testovat systém sám výrazně urychluje odstraňování problémů, protože již nemusím žádat uživatele, aby spouštěl příkazy, posílal mi výsledky a analyzoval je, vše mohu udělat sám!
Kolegové mi začínají trochu závidět. U oběda diskutujeme o konverzi, ale nikdo netuší, co se děje. Naštěstí sám uživatel již přijal opatření ke zmírnění následků a nikam nespěchá, takže máme čas problém rozpitvat. A protože máme obrázek, můžeme spustit jakékoli testy, které nás zajímají. Skvělý!
Krok zpět
Jedna z nejoblíbenějších otázek na pohovorech pro pozice systémových inženýrů zní: „Co se stane, když pingnete www.google.com? Otázka je skvělá, protože kandidát potřebuje popsat vše od shellu po uživatelský prostor, jádro systému a pak síť. Usmívám se: někdy se otázky na pohovoru ukáží jako užitečné v reálném životě...
Rozhodl jsem se aplikovat tuto otázku HR na aktuální problém. Zhruba řečeno, když se pokusíte určit název DNS, stane se následující:
Aplikace volá systémovou knihovnu, jako je libdns
libdns zkontroluje konfiguraci systému, na který server DNS by se měl obrátit (v diagramu je to 169.254.169.254, server metadat)
libdns používá systémová volání k vytvoření soketu UDP (SOKET_DGRAM) a odesílání paketů UDP s dotazem DNS v obou směrech
Prostřednictvím rozhraní sysctl můžete nakonfigurovat zásobník UDP na úrovni jádra
Jádro spolupracuje s hardwarem a přenáší pakety po síti přes síťové rozhraní
Hypervizor zachytí a odešle paket na server metadat při kontaktu s ním
Server metadat prostřednictvím své magie určí název DNS a vrátí odpověď pomocí stejné metody
Dovolte mi připomenout, jaké hypotézy jsme již zvažovali:
Hypotéza: Rozbité knihovny
Test 1: spusťte strace v systému, zkontrolujte, zda dig volá správná systémová volání
Výsledek: Jsou volána správná systémová volání
Test 2: pomocí srapy zkontrolujte, zda dokážeme určit jména, která obcházejí systémové knihovny
Výsledek: můžeme
Test 3: Spusťte rpm –V na balíčku libdns a souborech knihovny md5sum
Výsledek: kód knihovny je zcela identický s kódem v pracovním operačním systému
Test 4: připojte obraz kořenového systému uživatele na virtuální počítač bez tohoto chování, spusťte chroot a zjistěte, zda DNS funguje
Výsledek: DNS funguje správně
Závěr na základě testů: problém není v knihovnách
Hypotéza: V nastavení DNS je chyba
Test 1: po spuštění dig zkontrolujte tcpdump a zjistěte, zda se pakety DNS odesílají a vracejí správně
Výsledek: pakety jsou přenášeny správně
Test 2: dvakrát zkontrolujte na serveru /etc/nsswitch.conf и /etc/resolv.conf
Výsledek: vše je správně
Závěr na základě testů: problém není v konfiguraci DNS
Hypotéza: jádro poškozené
Test: nainstalujte nové jádro, zkontrolujte podpis, restartujte
Výsledek: podobné chování
Závěr na základě testů: jádro není poškozeno
Hypotéza: nesprávné chování uživatelské sítě (nebo síťového rozhraní hypervizoru)
Test 1: Zkontrolujte nastavení brány firewall
Výsledek: firewall předává pakety DNS na hostiteli i GCP
Test 2: zachyťte provoz a sledujte správnost přenosu a vrácení DNS požadavků
Výsledek: tcpdump potvrzuje, že hostitel přijal zpětné pakety
Závěr na základě testů: problém není v síti
Hypotéza: server metadat nefunguje
Test 1: Zkontrolujte, zda protokoly serveru metadat neobsahují anomálie
Výsledek: v protokolech nejsou žádné anomálie
Test 2: Vynechejte server metadat přes dig @8.8.8.8
Výsledek: Rozlišení je narušeno i bez použití serveru metadat
Závěr na základě testů: problém není v serveru metadat
Sečteno a podtrženo: testovali jsme všechny subsystémy kromě nastavení runtime!
Ponoření se do nastavení jádra
Pro konfiguraci spouštěcího prostředí jádra můžete použít volby příkazového řádku (grub) nebo rozhraní sysctl. Podíval jsem se dovnitř /etc/sysctl.conf a jen si představte, objevil jsem několik vlastních nastavení. S pocitem, jako bych se něčeho chytil, jsem zahodil všechna nastavení, která nejsou síťová nebo neTCP, a zůstal jsem u nastavení hor. net.core. Pak jsem šel tam, kde byla oprávnění hostitele ve virtuálním počítači, a začal jsem používat nastavení jedno po druhém, jedno po druhém, s poškozeným virtuálním počítačem, dokud jsem nenašel viníka:
net.core.rmem_default = 2147483647
Tady to je, konfigurace prolomení DNS! Našel jsem vražednou zbraň. Ale proč se to děje? Ještě jsem potřeboval motiv.
Základní velikost vyrovnávací paměti paketů DNS se konfiguruje pomocí net.core.rmem_default. Typická hodnota je někde kolem 200 kB, ale pokud váš server přijímá velké množství paketů DNS, možná budete chtít zvětšit velikost vyrovnávací paměti. Pokud je vyrovnávací paměť plná, když přijde nový paket, například proto, že jej aplikace nezpracovává dostatečně rychle, pak začnete ztrácet pakety. Náš klient správně zvětšil velikost vyrovnávací paměti, protože se obával ztráty dat, protože používal aplikaci pro sběr metrik prostřednictvím paketů DNS. Hodnota, kterou nastavil, byla maximální možná: 231-1 (pokud je nastaven na 231, jádro vrátí „NEPLATNÝ ARGUMENT“).
Najednou jsem si uvědomil, proč nmap a scapy fungovaly správně: používaly raw sockety! Raw sockety se liší od běžných socketů: obcházejí iptables a nejsou ukládány do vyrovnávací paměti!
Proč ale „příliš velký buffer“ způsobuje problémy? Evidentně to nefunguje tak, jak bylo zamýšleno.
V tomto okamžiku bych mohl problém reprodukovat na více jádrech a více distribucích. Problém se objevil již na jádře 3.x a nyní se objevil i na jádře 5.x.
Ostatně při startu
sysctl -w net.core.rmem_default=$((2**31-1))
DNS přestal fungovat.
Začal jsem hledat pracovní hodnoty pomocí jednoduchého binárního vyhledávacího algoritmu a zjistil jsem, že systém fungoval s 2147481343, ale toto číslo pro mě byla nesmyslná sada čísel. Navrhl jsem klientovi, aby zkusil toto číslo, a on odpověděl, že systém funguje s google.com, ale stále zobrazuje chybu u jiných domén, takže jsem pokračoval ve vyšetřování.
nainstaloval jsem dropwatch, nástroj, který měl být použit dříve: přesně ukazuje, kde v jádře paket končí. Na vině byla funkce udp_queue_rcv_skb. Stáhl jsem si zdrojové kódy jádra a pár jich přidal funkceprintk sledovat, kde přesně paket končí. Rychle jsem našel správný stav if, a prostě na to nějakou dobu zíral, protože tehdy se všechno konečně sešlo do celku: 231-1, nic neříkající číslo, nefunkční doména... Byl to kus kódu v __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;
Vezměte prosím na vědomí:
rmem je typu int
size je typu u16 (unsigned šestnáctibitový int) a ukládá velikost paketu
sk->sk_rcybuf je typu int a ukládá velikost vyrovnávací paměti, která se podle definice rovná hodnotě in net.core.rmem_default
Kdy sk_rcvbuf se blíží 231, výsledkem může být sečtení velikosti paketu přetečení celého čísla. A protože je to int, jeho hodnota se stane zápornou, takže podmínka se stane pravdivou, když by měla být nepravda (více o tom můžete přečíst na odkaz).
Chybu lze opravit triviálním způsobem: castingem unsigned int. Použil jsem opravu a restartoval systém a DNS znovu fungoval.
Chuť vítězství
Své poznatky jsem předal klientovi a odeslal LKML kernel patch. Těší mě: každý dílek skládačky do sebe zapadá, dokážu přesně vysvětlit, proč jsme pozorovali to, co jsme pozorovali, a co je nejdůležitější, díky týmové práci jsme dokázali najít řešení problému!
Stojí za to uznat, že se případ ukázal jako vzácný a naštěstí tak složité požadavky od uživatelů dostáváme jen zřídka.