Zum Inhalt springen

Blog

Ankündigung: Neuer Rust-basierter Cluster Agent


tl;dr Wir haben unseren Cluster Agent von Go auf Rust migriert – er ist jetzt kleiner und verbraucht weniger Speicher. Um den neuen Cluster Agent zu nutzen, aktualisieren Sie einfach auf das aktuelle Release (cli/v0.8.2, helm/v0.15.2). Sie können ihn auch live hier ausprobieren.


Vor Kurzem haben wir beschlossen, unseren Cluster Agent von Go auf Rust zu migrieren. Ich freue mich, berichten zu können, dass die Neuentwicklung abgeschlossen ist. Das Ergebnis ist ein Cluster-Agent-Image, das 57 % kleiner (10 MB) ist und 70 % weniger Speicher verbraucht (~3 MB), während die CPU-Auslastung weiterhin minimal bleibt (~0,1 %).

Die erste Version von Kubetail war dafür konzipiert, innerhalb des Clusters zu laufen und Logs über einen Webbrowser zugänglich zu machen. Die primäre Aufgabe des Backends bestand darin, Anfragen an die Kubernetes API zu stellen und Antworten in Echtzeit an das Frontend weiterzuleiten. Nach Abwägung verschiedener Optionen, darunter Python und JavaScript, entschied ich mich für Go, da es von der Kubernetes API gut unterstützt wird, hervorragendes Multithreading bietet und schnelle Executables sowie kleine Docker-Images erzeugt.

Die nächste Version von Kubetail fügte das kubetail CLI-Tool hinzu, das das Web-Dashboard auch lokal ausführen konnte. Auch für das CLI-Tool wählte ich wieder Go, weil die Sprache gute Bibliotheken für CLI-Interaktionen bietet (danke spf13!), plattformübergreifend gut unterstützt wird und – besonders wichtig – die Wiederverwendung der Go-basierten Web-App ermöglichte, die bereits für das In-Cluster-Dashboard verwendet wurde.

Bis dahin rief Kubetail Logs ausschließlich über die Kubernetes API ab. Als ich jedoch neue Funktionen wie Log-Dateigrößen und Zeitstempel des letzten Ereignisses hinzufügen wollte – Daten, die die Kubernetes API nicht bereitstellt – erkannte ich, dass wir einen Agent mit direktem Zugriff auf die rohen Log-Dateien auf jedem Knoten benötigen. Obwohl ich eine andere Sprache hätte wählen können, entschied ich mich erneut für Go, da es die Sprache war, die ich am besten kannte und die uns bisher gute Dienste geleistet hatte. Glücklicherweise hatte Go auch hervorragende Unterstützung für gRPC, das sich als natürliche Wahl für die Schnittstelle des Agents anbot.

Angesichts des damaligen Funktionsumfangs war ich mit meiner ursprünglichen Entscheidung für Go sehr zufrieden, da es uns sowohl auf dem Desktop als auch im Cluster gut gedient hatte. Dann begann ich zu untersuchen, wie unsere meistgewünschte Funktion implementiert werden könnte: die Protokollsuche.

Als ich über die Protokollsuche nachdachte, war mir von Anfang an klar, dass ich grep statt eines Volltextindex einsetzen wollte – grep reicht für die meisten Anwendungsfälle aus, und ich wollte unseren Nutzern nicht den Ressourcenaufwand einer Volltextindizierung zumuten. Gleichzeitig nutzte ich rg schon eine Weile persönlich, um Logs zu durchsuchen, und war von seiner Geschwindigkeit beeindruckt. Als ich nach einer Grepping-Lösung suchte, fragte ich mich, ob ich es irgendwie einsetzen könnte. Da stellte ich fest, dass es als Bibliothek verfügbar war – mit einem Haken: Es war in Rust geschrieben.

Bevor ich eigenen Code schrieb, erkundete ich die Idee, rg als externes Executable über exec.Command via stdin/stdout anzusprechen. Das funktionierte für einfache Anwendungsfälle, wurde aber zunehmend unhandlich, als ich benutzerdefinierte Funktionen wie Zeitfilter, ANSI-Escape-Sequenzbehandlung und Unterstützung für JSON-formatierte Zeilen hinzufügte. Deshalb entschied ich mich, einen maßgeschneiderten Log-Datei-Grepper zu schreiben. Ich habe kurz erwogen, Go zu verwenden, kam aber letztlich aus Performance- und Robustheitsgründen zu dem Schluss, dass ich die Bibliothek hinter rg, nämlich ripgrep, nutzen wollte – was bedeutete, dass der Code in Rust geschrieben werden musste.

Da ich den gesamten Cluster Agent zu diesem Zeitpunkt nicht in Rust neu schreiben wollte, untersuchte ich Möglichkeiten, Rust aus Go heraus aufzurufen (z. B. rustgo), und entschied mich schließlich, den benutzerdefinierten Rust-Code als separates Executable zu belassen und es aus Go über exec.Command aufzurufen. Um den Code so einfach wie möglich zu halten, verwendete ich ein gemeinsames Protocol-Buffers-Schema mit Serialisierung/Deserialisierung an der stdin/stdout-Schnittstelle.

Nach dem Launch der Suchfunktion wuchs unsere Community, und ich traf zwei Entwickler mit deutlich mehr Rust-Erfahrung als ich: Christopher Valerio (freexploit) und Giannis Karagiannis (gikaragia). Zunächst begannen sie, Verbesserungen am Rust-Code vorzunehmen. Als sie sich mit der Codebasis vertraut gemacht hatten, begannen wir, darüber zu sprechen, wie die Impedanzunterschiede zwischen Go und Rust im Cluster Agent beseitigt werden könnten. Unabhängig von der Suchfunktion läuft der Cluster Agent auf jedem Knoten im Cluster, weshalb es wichtig ist, dass er so performant und ressourcenschonend wie möglich ist – genau der Anwendungsfall, für den Rust gemacht ist. Mit diesen Ideen in der Luft hielten wir ein Community-Meeting ab, bei dem wir die Migration des gesamten Agents nach Rust diskutierten. Sie erklärten sich begeistert, daran mitzuwirken – also sagten wir: Machen wir es!

Nachdem die Entscheidung gefallen war, legten Christopher und Giannis los. Christopher definierte die initiale Hochebenenarchitektur des Projekts und erstellte erste Issues in GitHub. Dann stieg Giannis ein und begann, den Funktionsumfang zu implementieren, Tests zu schreiben und weitere Issues anzulegen, um Beiträge anderer Mitwirkender zu ermöglichen. Giannis erreichte in nur wenigen Wochen Funktionsparität mit dem Go-basierten Cluster Agent, und nach etwa einer weiteren Woche Tests entschieden wir, dass der Code bereit war, in den main-Branch zu mergen.

Ich habe selbst erst vor Kurzem begonnen, Rust zu lernen, weshalb Claude Code und Codex CLI unverzichtbar waren, um mir bei der Überprüfung von Giannis’ Pull Requests zu helfen. Auch er nutzte die Chatbots auf seiner Seite – es war eine echte Mensch-Bot-Partnerschaft, vermittelt durch GitHub Pull Requests. Ein entscheidender Vorteil war, dass der Agent eine klar definierte gRPC-Schnittstelle verwendet: Wir konnten das Protocol-Buffers-Schema wiederverwenden und einfach umschalten, sobald der Rust-basierte Agent Funktionsparität mit der Go-Version erreichte. Für den Aufbau des Rust-basierten gRPC-Servers verwendeten wir tonic, was unkompliziert war und nur geringfügige Unterschiede gegenüber dem Go-basierten gRPC-Server aufwies.

Das Endergebnis ist ein Cluster-Agent-Image, das 57 % kleiner (10 MB) ist und 70 % weniger Speicher verbraucht (~3 MB), bei weiterhin minimaler CPU-Auslastung (~0,1 %). Außerdem ist der Code jetzt viel einfacher zu handhaben, da er vollständig in einer einzigen Sprache vorliegt.

Unsere Mission ist es, Nutzern leistungsstarke Logging-Werkzeuge in einem einfachen und schlanken Paket zur Verfügung zu stellen. Da die Kubernetes API nur begrenzte Logging-Möglichkeiten bietet, erfordert die Freischaltung fortgeschrittener Funktionen direkten Zugriff auf die rohen Log-Dateien auf jedem Knoten. Genau hier kommt der Cluster Agent ins Spiel – er ist das Fundament für alles, was wir als Nächstes entwickeln möchten.

Natürlich sind Nutzer verständlicherweise vorsichtig, wenn es darum geht, Agents in ihren Clustern zu installieren. Agents müssen nicht nur nützlich sein, sondern auch klein, schnell und sicher. Die Rust-Migration ist unsere Antwort auf diese Anforderungen. Indem wir die Image-Größe um mehr als die Hälfte reduziert und den Speicherbedarf um 70 % gesenkt haben, ist der Kubetail Agent jetzt klein genug, um selbst in den ressourcenknappsten Umgebungen eingesetzt werden zu können.

Aber das ist erst der Anfang. Rust wird es uns ermöglichen, die Grenzen dessen zu verschieben, was innerhalb des Clusters in Echtzeit, direkt mit Dateien auf der Festplatte, bei minimalem CPU- und Speicherverbrauch möglich ist. Derzeit liegt unser Fokus auf Logs, aber derselbe Ansatz lässt sich auf Metriken, Benachrichtigungen und andere Arten von Observability-Daten anwenden.

Wir sind begeistert von dem, was als Nächstes kommt, und würden uns freuen, wenn Sie dabei sind. Wenn Ihnen gefällt, was wir tun, und Sie Code beitragen oder Feedback als Nutzer teilen möchten, treten Sie uns auf Discord bei.

Danke, OCV

Wenn es uns gelingt, unsere Mission zu erfüllen und eine neue Logging-Schicht für Kubernetes zu bauen, die in jedem Cluster läuft, dann wird das zu einem erheblichen Teil Open Core Ventures (OCV) und ihrem von Alex Smith geleiteten Catalyst-Programm zu verdanken sein.

OCV ist eine Venture-Gesellschaft, die von Sid Sijbandij (Mitgründer von GitLab) gegründet wurde und frühphasige Open-Source-Unternehmen finanziert, die auf Open-Core-Prinzipien basieren. Im Rahmen ihrer Open-Source-Förderaktivitäten haben sie ein Programm namens Catalyst ins Leben gerufen, das Maintainern von Open-Source-Projekten, die ihr Projekt ausbauen möchten, ein kleines Stipendium und umfangreiches Mentoring bietet. Im Verlauf von 12 Wochen lernen Sie, wie man eine Open-Source-Community aufbaut und das eigene Produkt effektiv vermarktet, damit es wachsen und an Zugkraft gewinnen kann. Kubetail hat kürzlich an dem Programm teilgenommen, und für uns war es ein Wendepunkt.

Bevor ich an Kubetail arbeitete, war ich Mitgründer eines Startups namens Octopart, das Teil des W07-Batches von Y Combinator war. Als Startup verlief es für uns nicht schlecht, und so verfolgte ich beim Start von Kubetail einen ähnlichen Ansatz: Ich konzentrierte mich auf die Entwicklung eines MVP und postete diesen, sobald er fertig war, auf Hacker News (HN). Glücklicherweise erreichte der Beitrag für einige Stunden die Frontpage, und wir erhielten einige hundert GitHub-Sterne sowie eine kleine Anzahl echter Nutzer (~10).

Dann geriet Kubetail in das Trough of Sorrow. Das ist der Teil der Startup-Kurve nach dem ersten Launch, wenn der anfängliche Hype abflaut und man mit einer Handvoll Nutzern, keinerlei externer Bestätigung und nur dem eigenen internen Optimismus dasteht. Ich kannte das Tal der Tränen bereits und tat das, was ich zuvor getan hatte: Ich steckte den Kopf in die Arbeit und programmierte weiter.

In dieser Phase konzentrierte ich mich darauf, unseren MVP (das Kubetail Dashboard) so benutzerfreundlich wie möglich zu gestalten. Als Reaktion auf das Feedback einiger früher Nutzer änderte ich die Architektur so, dass das Dashboard neben dem Betrieb innerhalb des Clusters auch auf dem Desktop des Nutzers ausgeführt werden konnte. Außerdem arbeitete ich daran, es für Nutzer einfacher zu machen, die App über Homebrew und andere Paketrepositories zu finden und herunterzuladen. Im Hintergrund konzentrierte ich mich auf die Implementierung unserer meistgewünschten Funktion: die Suche.

Über mehr als ein Jahr arbeitete ich alleine, während das Wachstum des Projekts stagnierte. Dann erhielt ich eine unerwartete E-Mail von OCV, die zu unserer Aufnahme in das Catalyst-Sponsoringprogramm führte – und alles veränderte.

Als Teil von Catalyst erhielt ich praxisnahes Mentoring von Alex und dem OCV-Team, das für mich als jemanden mit technischen Fähigkeiten, aber ohne Erfahrung im Community-Aufbau oder in der Verwaltung eines Open-Source-Projekts, unschätzbar wertvoll war. Mit der Unterstützung von Catalyst verschob ich meinen Alltag von reinem Programmieren hin zu einer Balance zwischen Entwicklung, Community-Engagement und Unterstützung von Mitwirkenden.

Vor Catalyst hatte Kubetail keinerlei Community. Wir hatten einen Discord-Server, aber ich war der Einzige darin und arbeitete täglich allein. Dann begleitete mich Alex Woche für Woche und schlug vor, worauf ich mich konzentrieren und was ich ausprobieren sollte. Mit seiner Hilfe wuchs Kubetail von rund 300 Sternen auf über 1.300 innerhalb von 12 Wochen. Und noch bedeutsamer: Die Community nahm Fahrt auf. Vor Catalyst hatten wir 3 Mitwirkende und keine Nutzer in Discord. Jetzt haben wir 35 Mitwirkende und eine lebhafte Discord-Community mit 61 Mitgliedern.

Während Catalyst kam alles zusammen, und wir waren endlich bereit, unsere Protokollsuchfunktion zu launchen – diesmal mit einer Community im Rücken und dem Mentoring von OCV, das uns half, das Feature neuen Nutzern vorzustellen. Als wir das Feature ankündigten, landete Kubetail mehr als einen Tag lang auf der Frontpage von HN und wurde von Zehntausenden Nutzern auf Reddit und Twitter gesehen. Dies führte zu einem Anstieg der monatlichen Downloads von weniger als 100 auf über 400 und verwandelte Kubetail von einem kleinen Leidenschaftsprojekt in ein ambitioniertes, gemeinschaftlich getragenes Vorhaben. Der persönliche Höhepunkt von Catalyst für mich ereignete sich rund um diese Zeit, als ich unseren 1.000-Sterne-Meilenstein auf GitHub mit einem neuen Kubetail-Maintainer (rxinui) und dem Rest unserer Community teilen konnte.

Discord celebration

Ich mache mir keine Illusionen darüber, wie schwierig der Weg vor uns ist. Wir arbeiten an einem anspruchsvollen technischen Problem und bewegen uns in einem Bereich mit vielen gut finanzierten Unternehmen wie Datadog, Grafana, New Relic und ClickHouse, die bereits die Aufmerksamkeit der meisten unserer potenziellen Nutzer haben. Darüber hinaus erwarten Nutzer von Observability-Tools bereits ein umfangreiches Funktionsangebot, sodass wir viele talentierte Ingenieure benötigen werden, um das Ziel zu erreichen – und dafür brauchen wir Ressourcen, deren Beschaffung wir noch nicht vollständig gelöst haben.

Dennoch war ich noch nie so optimistisch bezüglich unserer Erfolgschancen. Jedes Mal, wenn ich etwas Neues von einem unserer erfahrenen Mitwirkenden lerne oder sehe, wie begeistert unsere jüngeren Mitwirkenden sind, wenn einer ihrer Pull Requests gemergt wird, gibt mir das neue Energie. Jedes Mal, wenn ich einen Pull Request eines Nutzers prüfe, der sein eigenes Problem löst, oder mit jemandem über eine neue Funktion diskutiere, bestärkt mich das in der Überzeugung, dass ich den besten Weg gewählt habe, ein Produkt zu entwickeln – gemeinsam als Teil einer Open-Source-Community.

Für mich ist ein Open-Source-Projekt wie ein Kochtopf, der hochwertige Produkte hervorbringen kann, die Nutzer gerne verwenden und die ihnen gut tun. Aber die magische Zutat hinter jedem Produkt ist natürlich die Community – und wenn es um die Community von Kubetail geht, muss ich an dieser Stelle Alex und dem Rest des OCV-Teams herzlich danken.

Ankündigung: Echtzeit-Protokollsuche für Kubernetes

Seit dem Launch von Kubetail letztes Jahr war die mit Abstand am häufigsten gewünschte Funktion die Protokollsuche. Ich freue mich, mitteilen zu können, dass wir diese nun endlich in unserem aktuellen offiziellen Release (cli/v0.4.3, helm/v0.10.1) verfügbar haben. Hier können Sie sie in Aktion sehen:

https://www.kubetail.com/demo

Die Implementierung der Suche hat Zeit in Anspruch genommen, weil die Kubernetes API diese Funktion nicht nativ unterstützt – wir mussten sie von Grund auf neu entwickeln. Wir haben erwogen, sie schnell mittels clientseitigem Grepping umzusetzen, aber das wäre keine gute Benutzererfahrung gewesen: Jede Suche hätte potenziell einen vollständigen Download vieler Log-Dateien ausgelöst, was langsam und bandbreitenintensiv gewesen wäre. Es gibt Umgehungsmöglichkeiten, aber diese hätten zusätzliche Benutzereingaben erfordert, was ebenfalls keine gute Erfahrung darstellt.

Stattdessen haben wir die Suche durch Entwicklung eines maßgeschneiderten, in Rust geschriebenen Executables implementiert, das ripgrep einbindet. Warum Rust? Weil es verdammt schnell ist. Der Großteil des Kubetail-Backends ist in Go geschrieben, aber für diese systemnahe Komponente, die Log-Dateien auf der Festplatte liest, wollten wir maximale Geschwindigkeit. Das Ergebnis: Ein vollständiger Scan einer 1-GB-Datei dauert etwa 250 ms. Bei jeder Anfrage scannt das Executable nur die relevanten Container-Log-Dateien auf jedem Knoten und streamt ausschließlich die übereinstimmenden Zeilen zurück in Ihren Browser. Die meisten Anfragen können früh abgebrochen werden, sodass sie sogar vor einem vollständigen Scan ein Ergebnis liefern können. Kubetail-Suche lässt sich als „Remote-Grep” für Ihre Kubernetes-Logs verstehen. Sie müssen keine gesamte Log-Datei mehr herunterladen, nur um sie lokal zu durchsuchen.

Um die Suche zu aktivieren, müssen Sie die „Cluster Resources” von Kubetail in Ihrem Cluster installieren. Dies gelingt einfach über einen Klick auf „Install” in der GUI oder durch den Aufruf von kubetail cluster install in der CLI. Diese Aktion stellt einen Kubetail Cluster Agent auf jedem Knoten sowie eine Instanz der Kubetail Cluster API bereit. Sobald die Cluster API verfügbar ist, nutzt das Dashboard diese, um auf benutzerdefinierte Kubetail-Funktionen wie die knotenbasierte Suche zuzugreifen. Andernfalls deaktiviert das Dashboard diese Funktionen in der GUI und fällt auf die Kubernetes API zurück.

Wir stehen erst am Anfang unserer Arbeit an der Protokollsuche und es gibt noch viel Raum für Verbesserungen. Wenn Sie ein Rust-, Go- oder React-Entwickler sind – oder ein UI-Designer mit Begeisterung für Logs – helfen Sie uns, die benutzerfreundlichste Open-Source-Logging-Plattform für Kubernetes aufzubauen. Treten Sie unserer Community auf Discord bei!

Andres

Neuer Kubetail CLI-Befehl „logs"

Um die Überwachung und Fehlersuche bei Multi-Container-Workloads auf Kubernetes zu vereinfachen, haben wir dem Kubetail CLI-Tool einen neuen logs-Befehl hinzugefügt. Mit dem neuen logs-Befehl können Sie Ihre Kubernetes-Workload-Logs jetzt in Echtzeit direkt im Terminal durchsuchen. Außerdem lassen sich Logs nach Zeitraum und anderen Quelleigenschaften wie Knoten und Zone filtern.

Um das Kubetail CLI-Tool zu installieren, können Sie es von der Release-Seite herunterladen oder Homebrew verwenden:

Terminal-Fenster
brew install kubetail

Hier sind einige Beispiele, was Sie mit dem neuen logs-Befehl tun können:

Terminal-Fenster
# Tail 'web' deployment in the 'default' namespace
kubetail logs deployments/web
# Tail 'web' deployment in the 'frontend' namespace
kubetail logs frontend:deployments/web
# Return last 100 records
kubetail logs deployments/web --tail=100
# Return first 100 records
kubetail logs deployments/web --head=100
# Stream new records
kubetail logs deployments/web --follow
# Return all records
kubetail logs deployments/web --all
# Return first 10 records starting from 30 minutes ago
kubetail logs deployments/web --since PT30M
# Return last 10 records leading up to 30 minutes ago
kubetail logs deployments/web --until PT30M
# Return first 10 records between two exact timestamps
kubetail logs deployments/web --since 2006-01-02T15:04:05Z07:00 --until 2007-01-02T15:04:05Z07:00
# Return last 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --force
# Return first 10 records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --head --force
# Return last 10 records that match "GET /about" or "GET /contact"
kubetail logs deployments/web --grep "GET /(about|contact)" --force
# Stream new records that match "GET /about"
kubetail logs deployments/web --grep "GET /about" --follow --force

Der logs-Befehl nutzt Ihre lokale kube-config-Datei zur Authentifizierung mit Ihrem Cluster. Um den Cluster zu wechseln, ändern Sie einfach den kube-config-Kontext. Alternativ können Sie das Flag --kube-context verwenden:

Terminal-Fenster
kubetail logs --kube-context minikube deployments/web

Sie werden feststellen, dass bei der Verwendung von --grep auch --force angegeben werden muss. Das liegt daran, dass die Filterung clientseitig erfolgt – das Tool lädt kontinuierlich Logs aus Ihrem Cluster herunter, bis die gewünschte Anzahl an Treffern gefunden wurde. Dies kann zu unerwartet großen Downloads führen, weshalb wir eine zusätzliche Flag-Prüfung eingebaut haben. Wir arbeiten bereits an einer neuen Funktion, um dieses Problem zu umgehen.

Probieren Sie den neuen logs-Befehl aus und lassen Sie uns wissen, was Sie davon halten!