Debuggen stark geladener Golang-Anwendungen oder wie wir nach einem Problem in Kubernetes gesucht haben, das nicht vorhanden war

In der modernen Welt der Kubernetes-Clouds muss man auf die eine oder andere Weise mit Softwarefehlern konfrontiert werden, die nicht von Ihnen oder Ihrem Kollegen gemacht wurden, sondern die Sie lösen müssen. Dieser Artikel kann einem Neuling in der Welt von Golang und Kubernetes helfen, einige Möglichkeiten zum Debuggen seiner eigenen und fremden Software zu verstehen.

Bild

Mein Name ist Viktor Yagofarov, ich entwickle die Kubernetes-Cloud bei DomKlik und heute möchte ich darüber sprechen, wie wir das Problem mit einer der Schlüsselkomponenten unseres Produktions-K8s-Clusters (Kubernetes) gelöst haben.

In unserem Kampfcluster (zum Zeitpunkt des Schreibens):

  • 1890 wurden Pods und 577 Dienste gestartet (die Anzahl der echten Mikrodienste liegt ebenfalls im Bereich dieser Zahl).
  • Ingress- Controller liefern ungefähr 6k RPS und ungefähr die gleiche Menge geht von Ingress direkt an hostPort .


Problem


Vor einigen Monaten hatten unsere Pods ein Problem mit der Auflösung von DNS-Namen. Tatsache ist, dass DNS hauptsächlich über UDP funktioniert und im Linux-Kernel einige Probleme mit Conntrack und UDP auftreten. DNAT beim Zugriff auf die Dienstadressen von k8s Service verschärft das Problem nur bei Conntrack-Rennen . Es ist erwähnenswert, dass in unserem Cluster zum Zeitpunkt des Problems etwa 40.000 RPS für DNS-Server, CoreDNS, vorhanden waren.

Bild

Es wurde beschlossen, den lokalen Caching-DNS-Server NodeLocal DNS (nodelocaldns) zu verwenden, der speziell von der Community auf jedem Worker-Knoten des Clusters erstellt wurde. Dieser befindet sich noch in der Beta-Phase und soll alle Probleme lösen. Kurz gesagt: Entfernen Sie UDP, wenn Sie eine Verbindung zum Cluster-DNS herstellen, entfernen Sie NAT und fügen Sie eine zusätzliche Cache-Schicht hinzu.

In der ersten Iteration der Implementierung nodelocaldns haben wir Version 1.15.4 verwendet (nicht zu verwechseln mit der Version des Cubes ), die mit Kubespray «kubernetes-installer» geliefert wurde - wir sprechen von unserer Firma Fork fork aus Southbridge.

Fast unmittelbar nach der Einführung traten Probleme auf: Der Speicher floss und der Herd wurde gemäß den Speichergrenzen neu gestartet (OOM-Kill). Zum Zeitpunkt des Neustarts ging der gesamte Datenverkehr auf dem Host verloren, da /etc/resolv.conf in allen Pods genau auf die IP-Adresse von nodelocaldns zeigte.

Diese Situation war definitiv nicht jedermanns Sache, und unser OPS-Team hat eine Reihe von Maßnahmen ergriffen, um sie zu beseitigen.

Da ich selbst neu in Golang bin, war ich sehr daran interessiert, diesen ganzen Weg zu gehen und mich mit Debugging-Anwendungen in dieser wunderbaren Programmiersprache vertraut zu machen.

Wir suchen nach einer Lösung


So lass uns gehen!

Version 1.15.7 wurde in den Entwicklungscluster heruntergeladen, der bereits als Beta und nicht als 1.15.4 als Alpha gilt, aber die Jungfrau hat keinen solchen Datenverkehr in DNS (40.000 RPS). Es ist traurig.

In dem Prozess, wir ungebunden nodelocaldns von Kubespray und schrieben eine spezielle Helm - Chart für bequemere Ausrollen. Gleichzeitig haben sie ein Spielbuch für Kubespray geschrieben, mit dem Sie die Kubelet- Einstellungen ändern können , ohne den gesamten Cluster- Status stundenweise zu verarbeiten. Darüber hinaus kann dies punktweise erfolgen (zuerst eine kleine Anzahl von Knoten überprüfen).

Als nächstes haben wir die Version von nodelocaldns 1.15.7 für prod eingeführt. Die Situation wurde leider wiederholt. Die Erinnerung floss.

Das offizielle Nodelocaldns-Repository hatte eine Version mit dem Tag 1.15. 8, aber aus irgendeinem Grund konnte ich Docker nicht zum Ziehen dieser Version zwingen und dachte, dass ich das offizielle Docker-Image noch nicht gesammelt habe, daher sollte diese Version nicht verwendet werden. Dies ist ein wichtiger Punkt, auf den wir zurückkommen werden.

Debugging: Stufe 1


Lange Zeit konnte ich nicht herausfinden, wie ich meine Version von nodelocaldns im Prinzip zusammensetzen sollte, da das Makefile von der Rübe mit unverständlichen Fehlern aus dem Docker-Image abstürzte und ich nicht wirklich verstand, wie man mit govendor ein Go-Projekt schlau aufbaut , das auf seltsame Weise sofort in Verzeichnisse sortiert wurde für verschiedene DNS-Serveroptionen. Die Sache ist, dass ich angefangen habe, Go zu lernen, als bereits eine normale Out-of-Box-Versionierung von Abhängigkeiten aufgetreten war .

Pavel Selivanov hat mir bei dem Problem sehr geholfen.Pauljamm, wofür vielen Dank an ihn. Ich habe es geschafft, meine Version zusammenzustellen.

Als nächstes schraubten wir den pprof- Profiler , testeten die Baugruppe an der Jungfrau und rollten sie in den Stoß aus.

Ein Kollege aus dem Chat-Team hat wirklich geholfen, die Profilerstellung zu verstehen, sodass Sie bequem über die CLI-URL am Dienstprogramm pprof festhalten und den Speicher und die Verarbeitungsthreads mithilfe der interaktiven Menüs im Browser untersuchen können. Vielen Dank auch an ihn.

Auf den ersten Blick lief der Prozess basierend auf der Ausgabe des Profilers gut - der größte Teil des Speichers wurde auf dem Stapel zugewiesen und anscheinend ständig von Go-Routinen verwendet .

Aber irgendwann wurde klar, dass in den „schlechten“ Herden von Nodelocaldns im Vergleich zu den „gesunden“ zu viele Fäden aktiv waren. Und die Fäden verschwanden nirgendwo, sondern hingen weiter im Gedächtnis. In diesem Moment wurde Pavel Selivanovs Vermutung, dass "Fäden fließen", bestätigt.

Bild

Debugging: Stufe 2


Es wurde interessant, warum dies geschieht (Fäden fließen), und die nächste Stufe in der Untersuchung des Nodelocaldns-Prozesses hat begonnen.

Der statische Check des Static Analyzer- Codes hat gezeigt, dass es bereits beim Erstellen eines Threads in der Bibliothek , der in nodelocaldns verwendet wird, einige Probleme gibt (inkluda CoreDNS, inklod nododlocaldns'om). Nach meinem Verständnis wird an einigen Stellen kein Zeiger auf die Struktur übertragen , sondern eine Kopie ihrer Werte .

Es wurde beschlossen, mit dem Dienstprogramm gcore einen Coredump des „schlechten“ Prozesses zu erstellen und zu sehen, was sich darin befindet.

Mit einem GDB-ähnlichen DLV- Tool in Coredump steckenIch erkannte seine Kraft, aber ich erkannte, dass ich auf diese Weise sehr lange nach einem Grund suchen würde. Daher habe ich Coredump in die Goland-IDE geladen und den Status des Prozessspeichers analysiert.

Debugging: Stufe 3


Es war sehr interessant, die Struktur des Programms zu studieren und den Code zu sehen, der sie erstellt. In ungefähr 10 Minuten wurde klar, dass viele Go-Routinen eine Art Struktur für TCP-Verbindungen erstellen, diese als falsch markieren und niemals löschen (erinnern Sie sich an ungefähr 40.000 RPS?).

Bild

Bild

In den Screenshots sehen Sie den problematischen Teil des Codes und die Struktur, die beim Schließen der UDP-Sitzung nicht gelöscht wurde.

Durch Coredump wurde der Schuldige für eine solche Anzahl von RPS auch durch IP-Adressen in diesen Strukturen bekannt (danke, dass Sie dabei geholfen haben, einen Engpass in unserem Cluster zu finden :).

Entscheidung


Während des Kampfes gegen dieses Problem stellte ich mit Hilfe von Kollegen aus der Kubernetes-Community fest, dass das offizielle Docker-Image von nodelocaldns 1.15.8 immer noch existiert (und ich habe tatsächlich krumme Hände und irgendwie falsches Docker gezogen, oder WIFI war ungezogen Moment ziehen).

In dieser Version sind die Versionen der Bibliotheken, die er verwendet, sehr "verärgert": Insbesondere der "Täter" "apnalisierte" etwa 20 Versionen!

Darüber hinaus unterstützt die neue Version bereits die Profilerstellung über pprof und wird über Configmap aktiviert. Sie müssen nichts neu zusammensetzen.

Eine neue Version wurde zuerst in dev und dann in prod heruntergeladen.
III ... Sieg !
Der Prozess begann, seinen Speicher an das System zurückzugeben, und die Probleme wurden gestoppt.

In der Grafik unten sehen Sie das Bild: "Smoker's DNS vs. DNS einer gesunden Person. "

Bild

Ergebnisse


Die Schlussfolgerung ist einfach: Überprüfen Sie mehrmals, was Sie tun, und verachten Sie die Hilfe der Community nicht. Infolgedessen haben wir mehrere Tage mehr Zeit mit dem Problem verbracht als wir konnten, aber wir haben einen ausfallsicheren DNS-Betrieb in Containern erhalten. Vielen Dank, dass Sie bis zu diesem Punkt gelesen haben :)

Nützliche Links:

1. www.freecodecamp.org/news/how-i-investigated-memory-leaks-in-go-using-pprof-on-a-large-codebase-4bec4325e192
2 . habr.com/en/company/roistat/blog/413175
3. rakyll.org

All Articles