Debugging war story: The mystery of nxdomain
seuraavassa blogikirjoituksessa kuvataan virheenkorjausseikkailua CloudFlaren Mesos-pohjaisessa klusterissa. Tätä sisäistä klusteria käytetään ensisijaisesti lokitiedostojen tietojen käsittelyyn, jotta CloudFlaren asiakkailla on analytiikka, sekä järjestelmiimme, jotka havaitsevat hyökkäykset ja vastaavat niihin.
kohdattu ongelma ei vaikuttanut mitenkään asiakkaisiimme, vaan sai insinöörit raapimaan päätään…
ongelma
jossain vaiheessa yhdessä klusterissamme aloimme nähdä tällaisia virheitä (nxdomain olemassa olevalle verkkotunnukselle sisäisessä DNS: ssämme):
lookup some.existing.internal.host on 10.1.0.9:53: no such host
tämä tuntui hyvin oudolta, koska verkkotunnus oli todellakin olemassa. Se oli yksi sisäisistä verkkotunnuksistamme! Insinöörit olivat maininneet nähneensä tällaista käytöstä, joten päätimme tutkia asiaa tarkemmin. Tämän virheen laukaisevat kyselyt vaihtelivat mesos-dns: n hallinnoimista dynaamisista SRV-tietueista klusterin sisältä etsittyihin ulkoisiin verkkotunnuksiin.
ensimmäinen naiivi yrityksemme oli suorittaa seuraava silmukka:
while true; do dig some.existing.internal.host > /tmp/dig.txt || break; done
tämän suorittaminen jonkin aikaa yhdellä palvelimella ei toistanut ongelmaa: kaikki haut onnistuivat. Sitten otimme päivän palvelulokeja ja teimme grep ”ei tällaista isäntää” ja vastaavia viestejä. Virheitä tapahtui satunnaisesti. Virheiden välillä oli tunteja, eikä mitään selvää kaavaa, joka voisi johtaa mihinkään johtopäätökseen. Meidän tutkimus hylätään mahdollisuus, että virhe makasi Go, jota käytämme paljon palveluitamme, koska virheet olivat tulossa Java palvelut liian.
kaninkoloon
meillä oli tapana ajaa sitoutumattomana yhdellä IP: llä muutaman cluster DNS-resolverimme koneen läpi. Tämän jälkeen BGP vastaa sisäisten reittien ilmoittamisesta koneilta reitittimeen. Päätimme yrittää löytää kuvion lähettämällä paljon pyyntöjä eri koneilta ja kirjaamalla virheitä. Tältä kuormitustestausohjelmamme näytti aluksi:
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)}}
ajamme net.LookupHost
Loopissa, jossa on pieniä taukoja ja lokivirheitä; se siitä. Tämän pakkaaminen Telakkasäiliöön ja maratonjuoksu oli meille itsestäänselvä valinta, sillä niin me muut palvelut muutenkin hoidamme. Lokit lähetetään Kafkaan ja Kibanaan, jossa voimme analysoida ne. Tämän ohjelman suorittaminen 65 koneella, jotka tekevät hakuja 50 sekunnin välein, näyttää seuraavan virhejakauman (korkealta matalalle) isäntien välillä:
emme nähneet vahvaa korrelaatiota telineiden tai tiettyjen koneiden kanssa. Virheitä tapahtui monissa isännissä, mutta ei kaikissa ja eri aikoina Windowsin virheitä tapahtuu eri koneilla. Ajan asettaminen X-akselille ja virheiden määrä Y-akselille osoitti seuraavaa:
nähdäksemme, oliko jokin tietty DNS-rekursori seonnut, pysäytimme kaikki kuormageneraattorit tavallisilla koneilla ja käynnistimme kuormantuotantotyökalun itse rekursoreilla. Muutamassa tunnissa ei tullut virheitä, mikä viittasi siihen, että Unbound oli täysin terve.
aloimme epäillä pakettien katoamista, mutta miksi ”tällaista isäntää” ei tapahtuisi? Sen pitäisi tapahtua vain, kun DNS-vastauksessa on nxdomain-virhe, mutta teoriamme oli, että vastaukset eivät palanneet lainkaan.
puuttuva
testataksemme hypoteesia, jonka mukaan pakettien häviäminen voi johtaa ”no such host” – virheeseen, yritimme ensin estää lähtevän liikenteen portilla 53:
sudo iptables -A OUTPUT -p udp --dport 53 -j DROP
tässä tapauksessa kaivaa ja vastaavat työkalut vain aikalisälle, mutta älä palaa ”no such host”:
; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> cloudflare.com;; global options: +cmd;; connection timed out; no servers could be reached
go on vähän fiksumpi ja kertoo enemmän, mitä on tekeillä, mutta ei myöskään vastaa ”ei sellaista isäntää” :
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
koska Linux-ydin kertoo lähettäjälle pudottaneensa paketteja, jouduimme osoittamaan nimipalvelimen johonkin verkon mustaan aukkoon, joka ei tee paketeilla mitään jäljitelläkseen pakettihäviötä. Edelleenkään ei onnistanut:
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
jatkaaksemme verkon syyttämistä meidän piti jotenkin tukea oletuksiamme, joten lisäsimme hakuihin ajoitustietoa:
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)}
ollaksemme rehellisiä, aloitimme ajoitusvirheillä ja lisäsimme onnistumisen ajoituksen myöhemmin. Virheitä tapahtui 10S: n jälkeen, verrattain monta onnistunutta vastausta oli tulossa 5s: n jälkeen. Se kyllä näyttää pakettihäviöltä, mutta ei silti kerro, miksi ”ei tällaista isäntää” tapahdu.
koska nyt olimme paikassa, jossa tiesimme, kumpaan isäntään tämä vaikuttaa todennäköisemmin, ajoimme seuraavat kaksi komentoa rinnakkain kahdessa screen
sessions:
while true; do dig cloudflare.com > /tmp/dig.log || break; done; date; sudo killall tcpdumpsudo tcpdump -w /state/wtf.cap port 53
lähtökohtana oli saada verkkodumppaus epäonnistuneilla ratkaisuilla. Siellä nähtiin seuraavat kyselyt:
00.00s A cloudflare.com05.00s A cloudflare.com10.00s A cloudflare.com.in.our.internal.domain
kaksi kyselyä aikalisä ilman vastausta, mutta kolmas saa tuuria ja onnistuu. Meillä ei tietenkään ole cloudflarea.com meidän sisäinen verkkotunnus, niin sitoutumaton oikeutetusti antaa nxdomain vastauksena, 10s jälkeen lookup aloitettiin.
Bingo
katsotaan/etc / resolv.voit ymmärtää enemmän:
nameserver 10.1.0.9search in.our.internal.domain
hakusanan avulla voidaan käyttää lyhyitä isäntänimiä FQDN: n sijaan, jolloin myhost
vastaa läpinäkyvästi myhost.in.our.internal.domain
.
DNS-resolverille se tarkoittaa seuraavaa: jos DNS-kysely epäonnistuu, Kysy nimipalvelimelta 10.1.0.9, jos tämä ei onnistu, lisää .in.our.internal.domain
kyselyyn ja yritä uudelleen. Sillä ei ole väliä, mitä vika tapahtuu alkuperäisen DNS kysely. Yleensä se on NXDOMAIN, mutta meidän tapauksessamme se on lukuaika johtuen paketin menetys.
seuraavat tapahtumat näyttivät siltä, että ”ei tällaista isäntää” – virhettä ilmaantuisi:
– vaihtoehto) on onnistuttava ja palautettava nxdomain
toisaalta, jotta voi tarkkailla ajastettua DNS-kyselyä NXDOMAININ sijaan, on hukattava neljä lähetettyä pakettia 5 sekuntia yksi toisensa jälkeen (2 Alkuperäisen kyselyn ja 2 sisäisen version verkkotunnuksen), joka on paljon pienempi todennäköisyys. Itse asiassa näimme vain NXDOMAIN jälkeen 15s kerran ja koskaan nähnyt virheen jälkeen 20s.
vahvistaaksemme tämän oletuksen rakensimme proof-of-concept-DNS-palvelimen, joka pudottaa kaikki pyynnöt cloudflare.com, mutta lähettää nxdomainin olemassa oleville verkkotunnuksille:
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)}}
lopulta löysimme mitä oli tekeillä ja meillä oli tapa jäljitellä luotettavasti tuota käyttäytymistä.
ratkaisut
mietitään, miten voimme parantaa asiakastamme käsittelemään paremmin näitä ohimeneviä verkkoongelmia, jolloin se on joustavampi. Resolv: n miessivu.conf kertoo, että käytössä on Kaksi nuppia: timeout
ja retries
. Oletusarvot ovat 5 ja 2.
ellet pidä DNS-palvelintasi hyvin kiireisenä, on hyvin epätodennäköistä, että vastaamiseen menisi yli 1 sekunti. Itse asiassa, jos sinulla sattuu olemaan verkkolaite kuussa, voit odottaa sen vastaavan 3 sekunnissa. Jos nimipalvelimen asuu seuraavassa telineessä ja on saavutettavissa yli nopean verkon, voit turvallisesti olettaa, että jos ei ole vastausta jälkeen 1 sekunnin, DNS-palvelin ei saanut kyselyn. Jos haluat vähemmän outoja” ei tällaista verkkotunnusta ” virheitä, jotka saavat sinut raapimaan päätäsi, voit yhtä hyvin lisätä retries. Mitä useammin yrität uudelleen ohimenevän pakettihäviön kanssa, sitä pienempi epäonnistumisen mahdollisuus. Mitä useammin yrität uudelleen, sitä paremmat mahdollisuudet on viimeistellä nopeammin.
Kuvittele, että sinulla on todella satunnainen 1% pakettihäviö.
- 2 retries, 5s timeout: max 10s wait before error, 0.001% export of failure
- 5 retries, 1s timeout: max 5s wait before error, 0.000001% export of failure
tosielämässä jakauma olisi erilainen johtuen siitä, että pakettihäviö ei ole satunnainen, mutta voit odottaa paljon vähemmän DNS: n vastausta tämän tyyppisellä muutoksella.
kuten tiedätte monet DNS-resoluutiota tarjoavat järjestelmäkirjastot, kuten glibc, nscd, systemd-resolved, eivät ole kovettuneet käsittelemään internetissä olemista tai ympäristössä, jossa pakettihäviöitä on. Olemme kohdanneet haasteen luoda luotettava ja nopea DNS-resoluutio ympäristön useita kertoja kuin olemme kasvaneet, vain myöhemmin huomata, että ratkaisu ei ole täydellinen.
Over to you
ottaen huomioon, mitä olet lukenut tästä artikkelista pakettihäviöstä ja split-DNS / private-nimiavaruudesta, miten suunnittelisit nopean ja luotettavan resoluutioasetuksen? Mitä ohjelmistoa käyttäisit ja miksi? Mitä viritys muutoksia vakiokokoonpano käyttäisit?