Debugging war story: The mystery of nxdomain
A következő blogbejegyzés egy hibakeresési kalandot ír le a Cloudflare Mezos-alapú klaszterén. Ezt a belső fürtöt elsősorban a naplófájl-információk feldolgozására használják, hogy a Cloudflare ügyfelei analitikával rendelkezzenek, valamint a támadásokra reagáló rendszereink számára.
a felmerült probléma nem volt hatással ügyfeleinkre, de a mérnökök vakargatták a fejüket…
A probléma
az egyik Klaszterünk egy pontján ilyen hibákat kezdtünk látni (egy nxdomain egy meglévő domainhez a belső DNS-en):
lookup some.existing.internal.host on 10.1.0.9:53: no such host
Ez nagyon furcsának tűnt, mivel a domain valóban létezett. Ez volt az egyik belső domainünk! A mérnökök megemlítették, hogy látták ezt a viselkedést, ezért úgy döntöttünk, hogy mélyebben megvizsgáljuk. A hibát kiváltó lekérdezések változatosak voltak, a Mezos-dns által kezelt dinamikus SRV rekordoktól a fürt belsejéből felnézett külső tartományokig terjedtek.
első naiv kísérletünk a következő ciklus futtatása volt:
while true; do dig some.existing.internal.host > /tmp/dig.txt || break; done
ennek egy ideig egy szerveren történő futtatása nem reprodukálta a problémát: az összes keresés sikeres volt. Aztán fogtuk a szolgáltatás naplók egy napra, és nem egy grep a” nincs ilyen host ” és hasonló üzeneteket. A hibák szórványosan történtek. Órák teltek el a hibák között, és nem volt nyilvánvaló minta, amely bármilyen következtetésre vezetne minket. Vizsgálatunk elvetette annak lehetőségét, hogy a hiba a Go-ban rejlik, amelyet sok szolgáltatásunkhoz használunk, mivel a hibák a Java szolgáltatásokból is származnak.
A nyúl lyukba
A klaszter DNS-feloldónk számára egyetlen IP-n keresztül futtattunk kötetlen futtatást. A BGP ezután felelős a belső útvonalak bejelentéséért a gépektől az útválasztóig. Úgy döntöttünk, hogy megpróbálunk mintát találni, sok kérést küldve különböző gépekről és rögzítési hibákat. Itt van, amit a terhelés Tesztelési program nézett ki, mint az első:
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)}}
futtatjuk net.LookupHost
egy hurok kis szünetek és naplózási hibák; ennyi. Ezt egy dokkoló konténerbe csomagolni és Maratonon futni nyilvánvaló választás volt számunkra, mivel egyébként is így működtetünk más szolgáltatásokat. A naplókat Kafkába, majd Kibanába szállítják, ahol elemezhetjük őket. Ha ezt a programot 65 gépen futtatja, 50 ms-enként kereséseket végez, a következő hibaeloszlást mutatja (magasról alacsonyra) a gazdagépek között:
nem láttunk szoros összefüggést a rackekkel vagy bizonyos gépekkel. A hibák sok gépen történtek, de nem mindegyiken, és különböző időpontokban a windows hibák különböző gépeken fordulnak elő. Az X tengelyen töltött idő és az Y tengelyen lévő hibák száma a következőket mutatta:
hogy lássuk, hogy egy adott DNS-rekurzor megőrült-e, leállítottuk az összes terhelésgenerátort a szokásos gépeken, és elindítottuk a terhelésgeneráló eszközt magukon a rekurzorokon. Néhány órán belül nem volt hiba, ami azt sugallta, hogy a kötetlen teljesen egészséges.
gyanítani kezdtük, hogy a csomagvesztés volt a probléma, de miért fordul elő “ilyen gazdagép”? Ez csak akkor történhet meg, ha EGY NXDOMAIN hiba van egy DNS-válaszban, de elméletünk szerint a válaszok egyáltalán nem jöttek vissza.
A hiányzó
annak a hipotézisnek a teszteléséhez, hogy a csomagok elvesztése “nincs ilyen gazdagép” hibához vezethet, először megpróbáltuk blokkolni a kimenő forgalmat az 53-as porton:
sudo iptables -A OUTPUT -p udp --dport 53 -j DROP
ebben az esetben a dig és hasonló eszközök csak időtúllépést jelentenek, de nem adnak vissza “nincs ilyen gazdagép”:
; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> cloudflare.com;; global options: +cmd;; connection timed out; no servers could be reached
Go egy kicsit okosabb, és többet mond arról, hogy mi folyik itt, de nem tér vissza “nincs ilyen host” sem:
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
mivel a Linux kernel azt mondja a feladónak, hogy eldobta a csomagokat, a névszerverre kellett mutatnunk egy fekete lyukat a hálózatban, amely semmit sem tesz a csomagokkal, hogy utánozza a csomagvesztést. Még mindig nincs szerencsénk:
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
a hálózat hibáztatásához valahogy támogatnunk kellett feltételezéseinket, ezért időzítési információkat adtunk hozzá a kereséseinkhez:
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)}
hogy őszinte legyek, időzítési hibákkal kezdtük, majd később hozzáadtuk a siker időzítését. Hibák történtek 10-es évek után, viszonylag sok sikeres válasz érkezett 5-ös után. Úgy néz ki, mint a csomagvesztés, de még mindig nem mondja el nekünk, hogy miért “nincs ilyen gazdagép”.
mivel most olyan helyen voltunk, amikor tudtuk, hogy mely gazdagépeket érinti ez nagyobb valószínűséggel, a következő két parancsot futtattuk párhuzamosan két screen
munkamenetben:
while true; do dig cloudflare.com > /tmp/dig.log || break; done; date; sudo killall tcpdumpsudo tcpdump -w /state/wtf.cap port 53
a lényeg az volt, hogy egy hálózati dump-ot kapjunk sikertelen megoldásokkal. Ott a következő lekérdezéseket láttuk:
00.00s A cloudflare.com05.00s A cloudflare.com10.00s A cloudflare.com.in.our.internal.domain
két lekérdezés válasz nélkül időtúllépés, de a harmadik szerencsés és sikeres lesz. Természetesen nincs cloudflare.com a belső domainünkben, így a kötetlen jogosan ad választ az NXDOMAIN-nek, 10s a Keresés megkezdése után.
Bingo
nézzük meg az/etc / resolv fájlt.conf, hogy jobban megértsük:
nameserver 10.1.0.9search in.our.internal.domain
a keresési kulcsszó használatával rövid hostneveket használhatunk az FQDN helyett, így a myhost
átláthatóan egyenértékű a myhost.in.our.internal.domain
.
a DNS-feloldó esetében ez a következőket jelenti: bármely DNS-lekérdezés esetén kérdezze meg a 10.1.0.9 névkiszolgálót, ha ez nem sikerül, fűzze hozzá a.in.our.internal.domain
lekérdezést, majd próbálja meg újra. Nem számít, milyen hiba történik az eredeti DNS-lekérdezésnél. Általában NXDOMAIN, de a mi esetünkben ez egy olvasási időtúllépés a csomagvesztés miatt.
úgy tűnt, hogy a következő eseményeknek kell bekövetkezniük a” no such host ” hiba megjelenéséhez:
- az eredeti DNS-kérésnek el kell vesznie
- az 5 másodperc után elküldött újrapróbálkozásnak el kell vesznie
- a belső tartomány következő lekérdezésének (amelyet a
search
opció okoz) sikerülnie kell, és vissza kell adnia az NXDOMAIN-t
másrészt az nxdomain helyett egy időzített DNS-lekérdezés megfigyeléséhez el kell veszítenie az 5 másodperc után küldött négy csomagot másodpercek egymás után (2 az eredeti lekérdezéshez és 2 a tartomány belső verziójához), ami sokkal kisebb valószínűség. Valójában csak egyszer láttunk egy NXDOMAIN-t 15 év után, és soha nem láttunk hibát 20 év után.
annak igazolására, hogy a feltételezés, építettünk egy proof-of-concept DNS szerver, amely csepp minden kérést cloudflare.com, de küld egy nxdomain-t a meglévő domainekhez:
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)}}
végül megtaláltuk, mi folyik itt, és sikerült megbízhatóan lemásolni ezt a viselkedést.
megoldások
gondoljunk arra, hogyan javíthatjuk ügyfelünket ezen átmeneti hálózati problémák jobb kezelésében, rugalmasabbá téve azt. A resolv man oldala.a conf azt mondja, hogy két gombod van: a timeout
és a retries
opciók. Az alapértelmezett értékek 5, illetve 2.
hacsak nem tartja nagyon elfoglalva a DNS-kiszolgálót, nagyon valószínűtlen, hogy 1 másodpercnél tovább tartana a válaszadás. Valójában, ha van egy hálózati eszköz a Holdon, akkor számíthat arra, hogy 3 másodpercen belül válaszol. Ha a névszerver a következő rackben él, és nagy sebességű hálózaton keresztül érhető el, akkor nyugodtan feltételezhetjük, hogy ha 1 másodperc után nem érkezik válasz, a DNS-kiszolgáló nem kapta meg a lekérdezést. Ha azt szeretné, hogy kevésbé furcsa” nincs ilyen domain ” hibák, amelyek megkarcolják a fejét, akkor is növelheti az újrapróbálkozásokat. Minél többször próbálkozik újra átmeneti csomagvesztéssel, annál kisebb a kudarc esélye. Minél gyakrabban próbálkozik újra, annál nagyobb az esélye a gyorsabb befejezésnek.
képzelje el, hogy valóban véletlenszerű 1% – os csomagvesztesége van.
- 2 újrapróbálkozás, 5S timeout: max 10S várakozás a hiba előtt, 0.001% hiba esélye
- 5 újrapróbálkozás, 1s timeout: max 5s várakozás a hiba előtt, 0.000001% hiba esélye
a való életben az eloszlás más lenne, mivel a csomagvesztés nem véletlenszerű, de számíthat arra, hogy sokkal kevesebbet vár, amíg a DNS válaszol az ilyen típusú változásokra.
mint tudod, sok olyan rendszerkönyvtár, amely DNS-felbontást biztosít, mint például a glibc, az nscd, a systemd-resolved, nem edzett az interneten vagy a csomagveszteséggel rendelkező környezetben. Számos alkalommal szembesültünk azzal a kihívással, hogy megbízható és gyors DNS-felbontási környezetet hozzunk létre, ahogy nőttünk, csak később rájöttünk, hogy a megoldás nem tökéletes.
át neked
tekintettel arra, amit ebben a cikkben olvastál a csomagvesztésről és a split-DNS/private-namespace-ről, hogyan terveznél gyors és megbízható felbontást? Milyen szoftvert használnál és miért? Milyen hangolás változik a szokásos konfigurációhoz képest?