DNS-Resolving mit persistentem Cache

Wir fahren in Bezug auf DNS-Caching eine einfache, aber sehr effektive Strategie: Alle von uns verwalteten Hosts bekommen einen lokalen DNS-Resolver. Zwar haben wir in unseren Netzen jeweils ebenfalls DNS-Resolver, die direkt mit einem Eintrag in der /etc/resolv.conf netzintern genutzt werden können, aber das brauchen wir eigentlich nur während der Installation von Systemen, die wir typischerweise via PXE-Boot und dann über das Netz machen, wofür ein fertig bereitstehender Resolver vonnöten ist. Sobald eine Maschine läuft, bekommt sie einen eigenen Resolver.

Dieser Ansatz mag erstmal ungewöhnlich wirken, gehört doch das Eintragen von klassischerweise zwei DNS-Resolvern in die eigene Netzwerkkonfiguration zum üblichen Standard. Ein Aspekt ist sicherlich eine gewisse Performancesteigerung – was der gängige Grund sein dürfte, warum oft empfohlen wird, einen lokalen DNS-Cache auf dem heimischen Rechner zu installieren, denn bei gar nicht mal so wenigen Zugangsprovidern sind die von jenen bereitgestellten Resolver oftmals in bedauernswertem Zustand, was die Performance angeht, und gerade die günstigeren Plaste-DSL-Router für Daheim bekleckern sich hier auch oft nicht mit Ruhm – gelegentlich mag auch mein Linksys-Router einfach keine DNS-Anfragen mehr auflösen oder an den Upstream-Resolver durchreichen, obwohl er den Internetzugang an sich noch bereitstellt – nur ein Router-Reboot hilft dann noch. „Stabil“ ist anders.

Im Rechenzentrum zählen diese Argumente weniger. Die dort bereitgestellten DNS-Resolver liegen ja im gleichen Netz wie die anfragenden Hosts, und sie sind zudem mit üppiger Hardware ausgestattet. Was die Performance angeht, liegen wir hier also eher von Optimierungen im Millisekundenbereich, die es wirklich nicht wert wären.

Worum’s uns viel mehr geht, ist, mögliche Störungsquellen zu minimieren. Das DNS an sich ist voller Redundanz und Lastverteilung: Müssen DNS-Anfragen aufgelöst werden, stehen satte 13 IPv4-Adressen von Root-Nameservern bereit; einige sind auch via IPv6 erreichbar. Hinter den meisten stehen nicht etwa einzelne Hosts, sondern gleich etliche, die via Anycast geografisch verteilt sind. Fragt man jene beispielsweise nach der .de-Delegation, erhält man derzeit immerhin gleich fünf mögliche Nameserver, auch wiederum geografisch verteilt und zum Teil über IPv6 erreichbar. Für jede einzelne .de-Domain sind dann auch wieder mindestens zwei Nameserver zuständig, die gemäß der Vorgaben der DENIC in unterschiedlichen Class-C-Netzen stehen müssen (auch wenn das nicht zwingend eine geografische Trennung bedeutet). Es gibt also Redundanzen, wohin das Auge sieht. In den seltenen Fällen, wo tatsächlich einer dieser Hosts gestört ist, hat ein DNS-Resolver also praktisch immer genug Alternativen, um ans Ziel zu kommen – und das auch performant, weil er natürlich auch die Information, dass ein bestimmter Host aktuell nicht ansprechbar ist, eine gewisse Zeit lang cachen kann.

Und diese ganze Redundanz soll man nun aufgeben, in dem man alle Hosts in seinem Netzwerk über einen einzigen DNS-Resolver führt? Natürlich, natürlich: Wenigstens zwei sollten es schon sein; mehr als drei sind aber normalerweise schon gar nicht möglich. Wenn nun einer ausfällt, gönnt sich die C-Library aber defaultmäßig stolze fünf Sekunden, bis sie den nächsten Nameserver versucht – und versucht es ebenso defaultmäßig immer wieder beim ersten. Zwar lässt sich das in gewissen Grenzen anpassen (hint, hint: options timeout:1 rotate), aber so weit, dass dann z.B. ein Resolver, der ein paar Mal nicht antwortet, für eine gewisse Zeit lang gar nicht mehr angesprochen wird, geht die Logik dann eben doch nicht. Es gibt also zwar auch ein bisschen Redundanz, aber wirklich elegant ist sie nicht. Man braucht also wenn dann schon wirklich hoch verfügbare DNS-Resolver im Netz, denn jeder Ausfall zieht empfindliche Probleme nach sich: Logins via SSH brauchen sekundenlang, weil das System versucht, ein Reverse Lookup für die IP durchzuführen und danach auch noch ein Forward Lookup, um zu schauen, ob’s passt, und wehe dem, der kein HostnameLookups Off in seiner Apache-Konfiguration hat.

Ein lokaler DNS-Resolver hingegen fällt eigentlich nur in einer Situation aus: Nämlich dann, wenn die komplette Maschine ausfällt – und dann ist’s ohnehin egal, bzw. dann hat man ohnehin dringendere Probleme. Vor allem aber beträfe ein reiner Resolver-Ausfall dann nur diesen einen Host – und nicht gleich sämtliche Hosts im Netz, die jenen Resolver benutzen. Insofern fahren wir hier gewissermaßen eine Insel-Strategie: Wenn was kaputtgeht, dann jedenfalls nur sehr begrenzt. Und die Realität zeigt nun im Lauf von Jahren auf Dutzenden von Hosts: Es geht nichts kaputt. Das Setup ist rock-solid.

Als lokalen DNS-Cache benutzen wir hierbei dnscache aus dem djbdns-Paket. Das ist, ohne Frage, für jemanden, der sonst nicht mit Software aus dem DJB-Universum, mit einer gewöhnungsbedürften Installation verbunden, die aber schnell ihre Vorzüge ausspielt – und letztlich punktet dnscache genau wie tinydns eben besonders auch mit hoher Stabilität und Sicherheit. Einige Dinge sind aber zu beachten:

Erstens, errno.

Damit djbdns mit einer aktuellen glibc kompiliert werden kann, muss in conf-cc am Ende des Compileraufrufs -include /usr/include/errno.h angehängt werden; ein Umstand, den Dan Bernstein als Bug der glibc ansieht, auch wenn das durchaus diskutiert werden kann (runterscrollen zu „references“).

Zweitens, Root-Nameserver.

Die mit djbdns mitgelieferte Liste von Root-Nameservern ist nicht mehr ganz aktuell. Das lässt sich aber schnell fixen, weil natürlich jeder der (noch verfügbaren) Root-Nameserver eine aktuelle Liste liefern kann. Mit

dnsip `dnsqr ns . | awk '/^answer: \./ { print $5 }'` >
/service/dnscache/root/servers/@

lässt sie sich fix aktualisieren; ein

svc -h /service/dnscache

informiert dnscache zur Laufzeit über die neue Serverliste.

Drittens, Cache-Größe.

Die Standardkonfiguration gibt dnscache eine Cache-Größe von gerade mal 1 MB. Das hat zur Folge, dass insbesondere auf Systemen, die viele DNS-Anfragen machen, der Cache möglicherweise eine stärkere Fluktuation aufweist als nötig wäre, mit der Folge, dass DNS-Anfragen, die er eigentlich aus dem Cache hätte beantworten können, wenn jener nicht zu früh wieder hätte aufgeräumt werden müssen, erneut stellen muss. Angesichts heutiger RAM-Dimensionen stellt es mit Sicherheit kein Problem dar, dem Cache 10 MB zu verpassen:

echo 10000000 > /service/dnscache/env/CACHESIZE

vergrößert den Cache, und

echo 12000000 > /service/dnscache/env/DATALIMIT

teilt dem dnscache begrenzenden softlimit mit, dass dnscache nun mehr RAM belegen darf. Mit

svc -t /service/dnscache

wird die laufende dnscache-Instanz beendet, woraufhin svscan den Dienst automatisch neu startet. Welche Cache-Größe wirklich angemessen ist, führt hier zu weit; entsprechende Strategien sind aber bereits an verschiedenen Stellen dokumentiert.

Viertens sind wir kürzlich auf einen Patch gestoßen, der dnscache etwas äußerst Nützliches beibringen, nämlich die Möglichkeit, seinen aktuellen Cacheinhalt in eine Datei zu dumpen – und bei einem Start seinen Cache auch erstmal direkt aus jener Datei wieder zu befüllen. Auf diese Weise ist der Cache von dnscache auch nach einem Neustart sofort „warm“, ohne dass es erstmal wieder damit beginnen muss, von den Root-Nameservern her die TLD-spezifischen-Nameserver aufzulösen, und so weiter.

Der ursprüngliche Patch stammt von Efgé und bringt die Funktionalität ein, durch Senden eines SIGALRM dnscache dazu zu veranlassen, seinen Cacheinhalt in eine Datei zu dumpen, deren Name durch eine zuvor gesetzte Umgebungsvariable definiert wird, sowie die Funktionalität, jene Datei beim Starten wieder einzulesen. Nikola Vladov hat auf dieser Basis noch einen erweiterten Patch entwickelt, der zum einen dafür sorgt, dass dnscache beim Beenden automatisch einmal seinen Cache speichert, und der selbiges zudem in einem festlegbaren Rhythmus auch von sich aus tut, was insbesondere für Fälle praktisch ist, in denen ein Host crasht und von daher keine Gelegenheit mehr hat, den Cache zu schreiben wie bei einem sauberen Herunterfahren (nicht, dass sowas ständig vorkäme, aber der kluge Mann baut vor).

Lokal ordentlich zu cachen ist in jedem Fall eine gute Praxis, nicht nur der eigenen Performance wegen, sondern auch, um die Last, die man durch DNS-Anfragen auf anderen Hosts erzeugt, gering zu halten – es gewinnen also beide Seiten. Um so wichtiger ist das, wenn man nicht nur „gewöhnliche“ DNS-Daten abfragt, sondern auch Datenbanken, die via DNS publiziert werden, wie beispielsweise das IP to ASN Mapping der Team Cymru Community Services (mit herzlichem Dank an Daniel für den Tipp) – was für uns der Hauptgrund war, uns eingehender mit der Cache-Dumping-Thematik zu befassen. Der konkrete Anlass dafür reicht aber noch problemlos für einen weiteren Blogpost – stay tuned.

2 Antworten auf „DNS-Resolving mit persistentem Cache“

  1. Für DNS-Anfragen die über die Betriebssystem-API laufen, werden doch auch gecached (vgl, Windows: ipconfig /displaydns). Wenn das ein Dienst nicht tut, hat er sicher gute Gründe 😉

    Habt ihr Anwendungen, bei denen die Laufzeit im Rechenzentrum zum DNS-Resolver eine messbare Rolle spielen? Wie schon geschrieben, macht kein Apache oder Datenbankserver einen Hostname-Lookup (zumindest haben wir es so konfiguriert). Dazu kommt, dass bei uns in Clustern die privaten IPs verwendet werden und/oder Namen aus der /etc/hosts.

    Daher halte ich einen lokalen DNS-Server eher für eine potentielle Fehlerquelle als Performance-Steigerungsmaßnahme

    1. DNS-Anfragen, die über die Betriebssystem-API laufen, werden nicht gecached, wenn wir hier von Linux reden – um das zu realisieren, müsste erstmal ein nscd laufen, was zwar insbesondere bei vielen Desktop-Distros der Fall sein mag, aber auf unseren Servern durch die Bank weg nicht. Ob ich nun einen nscd laufen habe, der (unter anderem) meine DNS-Queries cacht, oder eben einen lokalen DNS-Cache, würde ich von daher erstmal als unerheblich sehen.

      Anwendungen, bei denen ein host-lokaler Resolver Vorteile gegenüber einem rechenzentrums-lokalen Resolver hat, haben wir nicht, bzw. ich würde den Unterschied für praktisch unmessbar halten. Ich hatte aber ja auch schon im Artikel geschrieben, dass Performance für uns nicht das entscheidende Argument ist, sondern eben eher, in Bezug auf DNS-Resolving unabhängig von anderen Hosts zu sein, selbst wenn ebenfalls wir jene anderen Hosts verwalten würden.

      Apache sollte man natürlich immer mit HostnameLookups Off laufen lassen, keine Frage. Wenn aber beispielsweise .htaccess-Dateien zugelassen sind, in denen User Regeln wie „Allow from *.somedomain.tld“ reinschreiben, dann zwingt das den Apache zu DNS-Lookups – auch wenn das global abgeschaltet ist. (Es hat übrigens auch den Nebeneffekt, dass ein %h im LogFormat dann auch plötzlich einen Hostnamen ins Log schreibt und nicht die IP – das für unser No-IP-Retention-Script, das bei den IPv4-IPs die letzte Stelle nullt, auch eine lustige Erfahrung, als da plötzlich keine IP stand, sondern ein Hostname, der dann eben nach anderer Regel anonymisiert werden wollte.)

      Ob ein lokaler DNS-Server nun eher eine potentielle Fehlerquelle ist, lasse ich mal offen. Ein lokaler nscd wäre aus meiner Sicht nicht weniger eine solche Fehlerquelle. 🙂

Kommentare sind geschlossen.