Articles

Debugging war story :the mystery OF NXDOMAIN

følgende blogginnlegg beskriver et feilsøkingseventyr på Cloudflares Mesos-baserte klynge. Denne interne klyngen brukes primært til å behandle loggfilinformasjon slik At Cloudflare-kunder har analyser, og for våre systemer som oppdager og reagerer på angrep.

problemet oppstod ikke har noen effekt på våre kunder, men har ingeniører klør seg i hodet…

Problemet

på et tidspunkt i en av våre klynger begynte vi å se feil som dette (et NXDOMAIN for et eksisterende domene på vår interne DNS):

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

dette virket veldig rart, siden domenet faktisk eksisterte. Det var en av våre interne domener! Ingeniører hadde nevnt at de hadde sett denne oppførselen, så vi bestemte oss for å undersøke dypere. Spørringer som utløste denne feilen var varierte og varierte fra dynamiske SRV-poster administrert av mesos-dns til eksterne domener sett opp fra innsiden av klyngen.

Vårt første naive forsøk var å kjøre følgende i en løkke:

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

Kjører dette en stund på en server reproduserte ikke problemet: alle oppslag var vellykkede. Så tok vi våre servicelogger for en dag og grep for «ingen slik vert» og lignende meldinger. Feil skjedde sporadisk. Det var timer mellom feil og ingen åpenbar mønster som kan føre oss til noen konklusjon. Vår undersøkelse forkastet muligheten for at feilen lå I Go, som vi bruker for mange av våre tjenester, siden feil kom fra Java-tjenester også.

Inn i kaninhullet

vi pleide å kjøre Ubundet på en ENKELT IP over noen få maskiner for vår cluster DNS resolver. BGP er da ansvarlig for å annonsere interne ruter fra maskinene til ruteren. Vi bestemte oss for å prøve å finne et mønster ved å sende mange forespørsler fra forskjellige maskiner og opptaksfeil. Her er hva vårt lasttestprogram så ut først:

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 kjører net.LookupHost i en løkke med små pauser og loggfeil; det er det. Pakking dette inn I En Docker container og kjører På Maraton var et opplagt valg for oss, siden det er hvordan vi kjører andre tjenester uansett. Logger blir sendt Til Kafka og Deretter Til Kibana, hvor vi kan analysere dem. Kjører dette programmet på 65 maskiner som gjør oppslag hver 50ms viser følgende feilfordeling (fra høy til lav) over verter:

Vi så ingen sterk korrelasjon til stativer eller bestemte maskiner. Feil skjedde på mange verter, men ikke på alle av dem, og i forskjellige tider skjer windows-feil på forskjellige maskiner. Å sette Tid På X-aksen og antall feil På y-aksen viste følgende:

For å se om noen BESTEMT DNS-recursor hadde blitt gal, stoppet vi alle lastgeneratorer på vanlige maskiner og startet lastgenereringsverktøyet på recursorene selv. Det var ingen feil om noen timer, noe som antydet At Ubundet var helt sunt.

Vi begynte å mistenke at pakketap var problemet, men hvorfor skulle «ingen slik vert» oppstå? Det skal bare skje når EN nxdomain-feil er I ET DNS-svar, men vår teori var at svarene ikke kom tilbake i det hele tatt.

Den Manglende

for å teste hypotesen om at tap av pakker kan føre til en «no such host» – feil, prøvde vi først å blokkere utgående trafikk på port 53:

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

i dette tilfellet graver og lignende verktøy bare tidsavbrudd, men returnerer ikke «no such host»:

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

go Er litt smartere Og Forteller deg mer om hva som skjer, men returnerer ikke «ingen slik vert» heller:

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

Siden Linux-kjernen forteller avsenderen at den droppet pakker, måtte vi peke navnetjeneren til noe svart hull i nettverket som ikke gjør noe med pakker for å etterligne pakketap. Fortsatt ikke flaks:

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 å fortsette å skylde på nettverket måtte vi støtte våre antagelser på en eller annen måte, så vi la til timinginformasjon til våre oppslag:

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 å være ærlig, startet vi med timing feil og lagt til suksess timing senere. Feil skjedde etter 10s, relativt mange vellykkede svar kom etter 5s. Det ser ut som pakketap, men forteller oss fortsatt ikke hvorfor «ingen slik vert» skjer.

Siden nå var vi på et sted da vi visste hvilke verter som var mer sannsynlig å bli påvirket av dette, kjørte vi følgende to kommandoer parallelt i toscreen økter:

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

poenget var å få et nettverk dump med mislykkede løser. Der så vi følgende spørsmål:

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

To spørsmål tidsavbrudd uten svar, men den tredje blir heldig og lykkes. Selvfølgelig har vi ikke cloudflare.com i vår interne domene, Så Ubundet rettmessig gir NXDOMAIN som svar, 10s etter oppslaget ble igangsatt.

Bingo

La oss se på / etc / resolv.conf for å forstå mer:

nameserver 10.1.0.9search in.our.internal.domain

ved hjelp av søkeordet kan vi bruke korte vertsnavn i stedet FOR FQDN, noe som gjør myhosttransparent tilsvarermyhost.in.our.internal.domain.

FOR DNS-resolveren betyr det følgende: for ALLE DNS-spørringer spør navneserveren 10.1.0.9, hvis dette mislykkes, tilføy .in.our.internal.domain til spørringen og prøv på nytt. Det spiller ingen rolle hvilken feil som oppstår for den opprinnelige DNS-spørringen. Vanligvis ER DET NXDOMAIN, men i vårt tilfelle er det en lesetid på grunn av pakketap.

følgende hendelser syntes å måtte skje for en» ingen slik vert » feil skal vises:

  1. den opprinnelige DNS-forespørselen må gå tapt
  2. forsøket som sendes etter 5 sekunder, må gå tapt
  3. den påfølgende spørringen for det interne domenet (forårsaket av search – alternativet) må lykkes og returnere NXDOMAIN

på den annen side, for å observere en tidsbestemt DNS-spørring i stedet for NXDOMAIN, må du miste fire pakker sendt 5 sekunder etter hverandre (2 for den opprinnelige spørringen og 2 for den interne versjonen av domenet ditt), noe som er en Mye mindre sannsynlighet. Faktisk så vi bare ET NXDOMAIN etter 15s en gang og så aldri en feil etter 20s.

For å validere denne antagelsen bygde vi EN PROOF-of-concept DNS-server som slipper alle forespørsler om cloudflare.com, men sender et NXDOMAIN for eksisterende domener:

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)}}

Til slutt fant Vi hva som foregikk og hadde en måte å pålidelig replikere den oppførselen på.

Løsninger

La oss tenke på hvordan vi kan forbedre vår klient for å bedre håndtere disse forbigående nettverksproblemer, noe som gjør det mer robust. Mannen siden for resolv.conf forteller deg at du har to knapper:timeout ogretries alternativene. Standardverdiene er henholdsvis 5 og 2.

Med Mindre DU holder DNS-serveren veldig opptatt, er det svært lite sannsynlig at det vil ta det mer enn 1 sekund å svare. Faktisk, hvis du tilfeldigvis har en nettverksenhet på Månen, kan du forvente at den svarer om 3 sekunder. Hvis navneserveren din bor i neste rack og kan nås over et høyhastighetsnettverk, kan du trygt anta at HVIS DET ikke er svar etter 1 sekund, FIKK DNS-serveren din ikke søket ditt. Hvis du vil ha mindre rare» no such domain » – feil som gjør at du klør på hodet, kan du like godt øke forsøk. Jo flere ganger du prøver på nytt med forbigående pakketap, jo mindre sjanse for feil. Jo oftere du prøver på nytt, jo høyere sjanser til å fullføre raskere.

Tenk deg at du har virkelig tilfeldig 1% pakketap.

  • 2 forsøk, 5s timeout: maks 10s vent før feil, 0.001% sjanse for feil
  • 5 forsøk, 1s timeout: maks 5s vent før feil, 0.000001% sjanse for feil

i virkeligheten vil distribusjonen være annerledes på grunn av at pakketap ikke er tilfeldig, men DU kan forvente å vente mye mindre FOR DNS å svare med denne typen endring.som du vet, er mange systembiblioteker som gir DNS-oppløsning som glibc, nscd, systemd-resolved, ikke herdet for å håndtere å være på internett eller i et miljø med pakketap. Vi har møtt utfordringen med å skape et pålitelig OG raskt DNS-oppløsningsmiljø flere ganger etter hvert som vi har vokst, bare for senere å oppdage at løsningen ikke er perfekt.

Over til deg

Gitt hva du har lest i denne artikkelen om pakketap og split-DNS / private-navnerom, hvordan vil du designe et raskt og pålitelig oppløsningsoppsett? Hvilken programvare vil du bruke og hvorfor? Hva tuning endringer fra standard konfigurasjon vil du bruke?