Articles

Debugging krigshistorie: mysteriet om domænet

følgende blogindlæg beskriver et debugging eventyr på Cloudflare ‘ s Mesos-baserede klynge. Denne interne klynge bruges primært til at behandle logfiloplysninger, så Cloudflare-kunder har analyser, og til vores systemer, der registrerer og reagerer på angreb.

det opståede problem havde ingen effekt på vores kunder, men fik ingeniører til at ridse deres hoveder…

problemet

på et tidspunkt i en af vores klynger begyndte vi at se fejl som dette (et domæne for et eksisterende domæne på vores interne DNS):

lookup some.existing.internal.host on 10.1.0.9:53: no such host

dette virkede meget underligt, da domænet faktisk eksisterede. Det var et af vores interne domæner! Ingeniører havde nævnt, at de havde set denne adfærd, så vi besluttede at undersøge dybere. Forespørgsler, der udløste denne fejl, blev varieret og varierede fra dynamiske SRV-poster administreret af mesos-dns til eksterne domæner, der blev set op inde fra klyngen.

vores første naive forsøg var at køre følgende i en løkke:

while true; do dig some.existing.internal.host > /tmp/dig.txt || break; done

at køre dette et stykke tid på en server gengav ikke problemet: alle opslag var vellykkede. Så tog vi vores service logs for en dag og gjorde en grep for “ingen sådan vært” og lignende meddelelser. Fejl skete sporadisk. Der var timer mellem fejl og intet indlysende mønster, der kunne føre os til nogen konklusion. Vores undersøgelse kasserede muligheden for, at fejlen lå i Go, som vi bruger til mange af vores tjenester, da der også kom fejl fra Java-tjenester.

ind i kaninhullet

vi plejede at køre ubundet på en enkelt IP på tværs af et par maskiner til vores cluster DNS resolver. BGP er derefter ansvarlig for at annoncere interne ruter fra maskinerne til routeren. Vi besluttede at prøve at finde et mønster ved at sende masser af anmodninger fra forskellige maskiner og optagelsesfejl. Sådan så vores belastningstestprogram først ud:

package mainimport ("flag""fmt""net""os""time")func main() {n := flag.String("n", "", "domain to look up")p := flag.Duration("p", time.Millisecond*10, "pause between lookups")flag.Parse()if *n == "" {flag.PrintDefaults()os.Exit(1)}for {_, err := net.LookupHost(*n)if err != nil {fmt.Println("error:", err)}time.Sleep(*p)}}

Vi kørernet.LookupHost i en løkke med små pauser og logfejl; det er det. Emballage dette i en Docker container og kører på Marathon var et oplagt valg for os, da det er sådan, vi kører andre tjenester alligevel. Logs bliver sendt til Kafka og derefter til Kibana, hvor vi kan analysere dem. Kørsel af dette program på 65 maskiner, der laver opslag hver 50 ms, viser følgende fejlfordeling (fra høj til lav) på tværs af værter:

vi så ingen stærk sammenhæng med stativer eller specifikke maskiner. Fejl skete på mange værter, men ikke på dem alle, og i forskellige tidsvinduer sker der fejl på forskellige maskiner.

for at se, om en bestemt DNS-recursor var blevet skør, stoppede vi alle belastningsgeneratorer på almindelige maskiner og startede belastningsgenereringsværktøjet på recursorerne selv. Der var ingen fejl på få timer, hvilket antydede, at ubundet var helt sundt.

Vi begyndte at mistanke om, at pakketab var problemet, men hvorfor skulle “ingen sådan vært” forekomme? Det skulle kun ske, når en DOMÆNEFEJL er i et DNS-svar, men vores teori var, at svarene slet ikke kom tilbage.

den manglende

for at teste hypotesen om, at tab af pakker kan føre til en “ingen sådan vært” – fejl, forsøgte vi først at blokere udgående trafik på port 53:

sudo iptables -A OUTPUT -p udp --dport 53 -j DROP

i dette tilfælde grave og lignende værktøjer bare time out, men returner ikke “ingen sådan vært”:

; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> cloudflare.com;; global options: +cmd;; connection timed out; no servers could be reached

Go er lidt smartere og fortæller dig mere om, hvad der foregår, men vender heller ikke tilbage “ingen sådan vært” :

error: lookup cloudflare.com on 10.1.0.9:53: write udp 10.1.14.20:47442->10.1.0.9:53: write: operation not permitted

da linuk-kernen fortæller afsenderen, at den faldt pakker, måtte vi pege navneserveren på et sort hul i netværket, der ikke gør noget med pakker for at efterligne pakketab. Stadig ikke held:

error: lookup cloudflare.com on 10.1.2.9:53: read udp 10.1.14.20:39046->10.1.2.9:53: i/o timeout

for at fortsætte med at bebrejde netværket måtte vi på en eller anden måde støtte vores antagelser, så vi tilføjede timingoplysninger til vores opslag:

s := time.Now()_, err := net.LookupHost(*n)e := time.Now().Sub(s).Seconds()if err != nil { log.Printf("error after %.4fs: %s", e, err)} else if e > 1 { log.Printf("success after %.4fs", e)}

for at være ærlig startede vi med timingfejl og tilføjede succestiming senere. Fejl skete efter 10 ‘erne, forholdsvis mange vellykkede svar kom efter 5’ erne. Det ligner pakketab, men fortæller os stadig ikke, hvorfor “ingen sådan vært” sker.

da vi nu var på et sted, da vi vidste, hvilke værter der var mere tilbøjelige til at blive påvirket af dette, kørte vi følgende to kommandoer parallelt i toscreen sessioner:

while true; do dig cloudflare.com > /tmp/dig.log || break; done; date; sudo killall tcpdumpsudo tcpdump -w /state/wtf.cap port 53

pointen var at få et netværksdump med mislykkede løsninger. Derinde så vi følgende forespørgsler:

00.00s A cloudflare.com05.00s A cloudflare.com10.00s A cloudflare.com.in.our.internal.domain

to forespørgsler timeout uden noget svar, men den tredje bliver heldig og lykkes. Naturligvis har vi ikke cloudflare.com i vores interne domæne, så ubundet giver med rette domæne som svar, 10s efter opslag blev indledt.

Bingo

lad os se på/etc / resolv.conf for at forstå mere:

nameserver 10.1.0.9search in.our.internal.domain

brug af søgeordet giver os mulighed for at bruge korte værtsnavne i stedet for FKDN, hvilket gørmyhostgennemsigtigt svarende tilmyhost.in.our.internal.domain.

for DNS-resolveren betyder det følgende: for enhver DNS-forespørgsel spørg navneserveren 10.1.0.9, hvis dette mislykkes, tilføj .in.our.internal.domain til forespørgslen og prøv igen. Det betyder ikke noget, hvilken fejl der opstår for den originale DNS-forespørgsel. Normalt er det et domæne, men i vores tilfælde er det en læse timeout på grund af pakketab.

følgende begivenheder syntes at skulle forekomme for at en” ingen sådan vært ” – fejl skulle vises:

  1. den oprindelige DNS-anmodning skal gå tabt
  2. det forsøg, der sendes efter 5 sekunder, skal gå tabt
  3. den efterfølgende forespørgsel til det interne domæne (forårsaget af search) skal lykkes og returnere domænenavn

på den anden side skal du miste fire pakker sendt 5 sekunder efter hinanden (2 for den oprindelige forespørgsel og 2 for den interne version af dit domæne), hvilket er en meget mindre sandsynlighed. Faktisk så vi kun et domæne efter 15 ‘erne en gang og så aldrig en fejl efter 20’ erne.

for at validere denne antagelse byggede vi en proof-of-concept DNS-server, der dropper alle anmodninger om cloudflare.com

package mainimport ("github.com/miekg/dns""log")func main() {server := &dns.Server{Addr: ":53", Net: "udp"}dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {m := &dns.Msg{}m.SetReply(r)for _, q := range r.Question {log.Printf("checking %s", q.Name)if q.Name == "cloudflare.com." {log.Printf("ignoring %s", q.Name)// just ignorereturn}}w.WriteMsg(m)})log.Printf("listening..")if err := server.ListenAndServe(); err != nil {log.Fatalf("error listening: %s", err)}}

endelig fandt vi, hvad der foregik og havde en måde at pålideligt replikere denne adfærd på.

løsninger

lad os tænke over, hvordan vi kan forbedre vores klient til bedre at håndtere disse forbigående netværksproblemer, hvilket gør det mere modstandsdygtigt. Manden side for resolv.conf fortæller dig, at du har to knapper: timeout og retries indstillinger. Standardværdierne er henholdsvis 5 og 2.

medmindre du holder din DNS-server meget travlt, er det meget usandsynligt, at det vil tage det mere end 1 sekund at svare. Faktisk, hvis du tilfældigvis har en netværksenhed på månen, kan du forvente, at den svarer på 3 sekunder. Hvis din navneserver bor i det næste rack og kan nås via et højhastighedsnetværk, kan du med sikkerhed antage, at hvis der ikke er noget svar efter 1 sekund, fik din DNS-server ikke din forespørgsel. Hvis du vil have mindre underlige “ingen sådanne domæne” – fejl, der får dig til at ridse dit hoved, kan du lige så godt øge forsøgene. Jo flere gange du prøver igen med forbigående pakketab, jo mindre chance for fiasko. Jo oftere du prøver igen, jo større er chancerne for at afslutte hurtigere.

Forestil dig, at du virkelig har tilfældigt 1% pakketab.

  • 2 Forsøg, 5S timeout: maks 10s vent før fejl, 0,001% chance for fiasko
  • 5 forsøg, 1s timeout: maks 5S vent før fejl, 0,000001% chance for fiasko

i det virkelige liv ville distributionen være anderledes på grund af det faktum, at pakketab ikke er tilfældigt, men du kan forvente at vente meget mindre på, at DNS svarer med denne type ændring.

som du kender mange systembiblioteker, der leverer DNS-opløsning som glibc, nscd, systemd-løst, er ikke hærdet til at håndtere at være på internettet eller i et miljø med pakketab. Vi har stået over for udfordringen med at skabe et pålideligt og hurtigt DNS-opløsningsmiljø flere gange, efterhånden som vi er vokset, kun for senere at opdage, at løsningen ikke er perfekt.

Over til dig

i betragtning af hvad du har læst i denne artikel om pakketab og split-DNS/private-namespace, hvordan ville du designe en hurtig og pålidelig opløsning setup? Hvilke programmer vil du bruge og hvorfor? Hvilke tuning ændringer fra standard konfiguration ville du bruge?