Right data. Smart use. More money - BOOM!
Data Analyst|Python|SQL|Transforming Raw Data into Business Value

Der Fall der alle 7 Sekunden ausrastenden CPU

Eine systematische Fehlersuche unter Linux

Wie ich zwei Tage lang einem Geist auf meinem Server nachgejagt bin – und was jeder daraus lernen kann, der ein ähnliches Rätsel lösen will.

Das Symptom

Alles fing harmlos an: Alle 10 Sekunden wurde meine Internetleitung für etwa 3 Sekunden spürbar beansprucht. Kein Drama auf den ersten Blick – aber es passierte zu regelmäßig, um Zufall zu sein. Downloads, Browser-Tabs, Cloud-Sync – das alles verursacht unregelmäßigen Traffic. Ein Takt wie eine Uhr dagegen ist fast immer ein Zeichen für einen Prozess, der in einer Schleife hängt.

Später kam ein zweites Symptom dazu: Die CPU sprang alle 5–7 Sekunden auf 20 % Last. Und am Ende musste ich sogar feststellen, dass ein hartes kill -9 auf einen völlig unbeteiligten Prozess meinen Rechner zu einem Totalabsturz brachte (dazu mehr in der Randnotiz am Ende).

Mein System: CachyOS (Arch-basiert), Docker mit gut zwei Dutzend Containern, mehrere selbst gehostete KI-Tools (Ollama, Open WebUI, LM Studio), diverse Node/Python-Projekte – ein typisches Homelab-Setup, in dem sich ein Übeltäter hervorragend verstecken kann.

[yop_poll id="4"]

Warum das schwer zu finden ist

Ein Prozess, der alle paar Sekunden kurz aktiv wird, hinterlässt keine dauerhafte Spur. top oder der Systemmonitor zeigen einen Schnappschuss – trifft man nicht genau den Moment der Aktivität, sieht alles normal aus. Genau das macht solche Fehler tückisch: Sie sind nicht selten, sondern kurz. Man braucht Werkzeuge, die über die Zeit mitschreiben, nicht nur den aktuellen Moment zeigen.

Die Werkzeugkiste

Hier die Tools, die sich in der Reihenfolge ihrer Aussagekraft als nützlich erwiesen haben – vom Groben zum Feinen:

1. ss – Momentaufnahme der Netzwerkverbindungen

bash

sudo ss -tunp | sort

Zeigt alle offenen Verbindungen mit Prozessname und PID. Gut für den ersten Überblick, aber eine Einzelmessung sagt nichts über Wiederholung aus.

2. Wiederholte Messung in eine Datei loggen

Da man den richtigen Moment nur schwer live erwischt, sollte man über Zeit mitschreiben statt live zuzuschauen:

bash

for i in (seq 1 20); date +%T; ss -tp | grep -E "verdaechtig"; sleep 2; end > netlog.txt

(Hinweis für fish-Shell-Nutzer: Die Syntax unterscheidet sich von bash. Es gibt keine \|-Escapes und keine do...done-Blöcke, sondern for ... ; end.)

3. /proc/net/dev – Bandbreite ganz ohne Zusatzsoftware

bash

for i in (seq 1 30); date +%T; grep enp10s0 /proc/net/dev; sleep 1; end

Das zeigt die kumulierten Byte-Zähler des Netzwerk-Interfaces. Steigt der Zähler alle paar Sekunden um mehrere Megabyte, ist das ein objektiver Beweis für periodischen Traffic – unabhängig davon, welcher Prozess dahintersteckt.

4. nethogs – Bandbreite pro Prozess, live

bash

sudo nethogs -t enp10s0 > nethogs.txt

Der -t-Schalter (Trace-Modus) ist der Schlüssel: Er schreibt fortlaufend in die Konsole statt das Terminal live neu zu zeichnen – dadurch lässt sich die Ausgabe in eine Datei umleiten und in Ruhe analysieren, statt sie live wegklicken zu müssen.

5. tcpdump – der Blick auf die Pakete selbst

bash

sudo tcpdump -i enp10s0 -nn -q > tcpdump.txt

Zeigt jedes einzelne Paket mit Quelle, Ziel und Größe. Gut, um zu bestätigen, dass und wohin Daten fließen – aber ohne Prozessnamen.

6. execsnoop (aus bcc-tools) – der eigentliche Durchbruch

bash

sudo pacman -S bcc bcc-tools
sudo execsnoop > execsnoop.txt

Das ist ein eBPF-basiertes Tool, das jeden einzelnen neu gestarteten Prozess in Echtzeit protokolliert, inklusive PID, Elternprozess und vollständigem Kommandozeilenaufruf. Bei einem Prozess, der sich ständig neu startet (statt dauerhaft zu laufen), ist das die mit Abstand direkteste Methode: Man sieht sofort, wer sich alle paar Sekunden wiederholt.

Weitere nützliche Tools aus demselben Paket, falls execsnoop nicht reicht: opensnoop (welche Datei öffnet wer), tcpconnect (wer baut TCP-Verbindungen auf, mit Prozessname), filetop/biotop (Festplatten-I/O pro Prozess).

7. journalctl – die Systemlogs

bash

journalctl -u <servicename> -f

Sobald ein Verdächtiger als systemd-Service identifiziert ist, zeigt das Live-Log oft sofort die Fehlermeldung, die den Neustart-Loop auslöst.

Praxistipp: Journal-Logs überleben nicht automatisch einen Neustart, wenn die Persistenz nicht aktiviert ist. Das lohnt sich präventiv einzurichten:

bash

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

Der Weg zur Lösung – Schritt für Schritt

Runde 1: Der erste falsche Verdächtige

ss -tunp zeigte diverse Dauerverbindungen (LM Studio Link-Feature, Browser-Tabs). Diese blieben aber über mehrere Messungen mit demselben lokalen Port bestehen – ein klares Zeichen, dass es sich um normale Keep-Alive-Verbindungen handelte, nicht um den gesuchten Wiederholungstäter.

Runde 2: Bandbreite bestätigt das Muster objektiv

Die /proc/net/dev-Messung zeigte eindeutige Sprünge von 8–10 MB innerhalb einzelner Sekunden, in einem wiederkehrenden Rhythmus. Damit war klar: Es handelt sich nicht um Einbildung, sondern um echten, spürbaren Traffic.

Runde 3: nethogs zeigt wechselnde PIDs desselben Musters

Der Trace-Modus von nethogs zeigte eine Kette aus npm startnpm run buildnpx vite buildesbuild, die sich alle paar Sekunden mit neuer PID wiederholte. Neue PID bei gleichem Befehl bedeutet: Der Prozess stirbt und wird neu gestartet – ein Restart-Loop.

Runde 4: Den Elternprozess finden

bash

ps -eo pid,ppid,cmd | grep -E "verdaechtiger_prozessname"

Damit ließ sich zurückverfolgen, wer diese Kette startet. Zunächst wurde ein Konfigurationsproblem bei uvx open-webui als Mitverursacher identifiziert (ein systemd --user-Service, der wegen eines fehlgeschlagenen Python-Paket-Builds ständig neu startete) und behoben. Die Netzwerklast sank spürbar, verschwand aber nicht vollständig.

Runde 5: Der zweite, hartnäckigere Verdächtige

Nach dem ersten Fix blieb ein Rest-Muster: CPU-Last alle 5–7 Sekunden, die sogar bei gekapptem Internet kurzzeitig bestehen blieb. Das war ein wichtiger diagnostischer Hinweis: Ein Prozess, der bei fehlendem Netz trotzdem CPU verbraucht, versucht wahrscheinlich, eine Verbindung aufzubauen und scheitert dabei wiederholt (DNS-Auflösung, Verbindungsversuch, Timeout) – reines Netzwerk-Monitoring reicht dann nicht mehr aus, weil das eigentliche Problem tiefer sitzt.

Runde 6: execsnoop bringt die endgültige Klarheit

Der Einsatz von execsnoop zeigte unmissverständlich denselben Kreislauf, diesmal einem konkreten Verzeichnis zugeordnet:

npm start → npm run build → npx vite build → vite build → esbuild

mit Pfad /opt/openhamclock. Ein systemd-System-Service (openhamclock.service) war der Übeltäter.

Die Auflösung

bash

journalctl -u openhamclock.service -f

zeigte die Ursache glasklar:

[FATAL] Uncaught exception: Cannot find module 'rss-parser'
...
systemd[1]: openhamclock.service: Scheduled restart job, restart counter is at 771.

Ein Node.js-Projekt (eine Amateurfunk-„Weltuhr“-Anwendung) hatte ein fehlendes npm-Paket. Die package.json enthielt ein prestart-Skript, das bei jedem Start automatisch neu baute (npm run build). Der Server crashte danach sofort wegen des fehlenden Moduls. Die systemd-Unit war mit Restart=on-failure konfiguriert – ohne Begrenzung, wie oft das passieren darf. Ergebnis: Ein kompletter Build-Zyklus (inklusive Vite-Bundling und esbuild-Kompilierung) alle 4–5 Sekunden, seit vermutlich Tagen, unbemerkt, mit Restart-Zähler 771 und weiter steigend.

Die eigentliche Lösung

Kurzfristig:

bash

sudo systemctl disable --now openhamclock.service

Langfristig (den Service reparieren):

bash

cd /opt/openhamclock
sudo -u <service-user> npm install rss-parser
sudo systemctl enable --now openhamclock.service

Und ganz wichtig für die Zukunft – jede eigene systemd-Unit mit Restart-Logik sollte ein Limit bekommen, damit ein kaputter Service nicht endlos in eine Schleife läuft:

ini

[Unit]
StartLimitIntervalSec=60
StartLimitBurst=5

Damit gibt systemd nach 5 Fehlschlägen innerhalb einer Minute auf, statt das über Tage unbemerkt zu wiederholen.

Checkliste: So gehst du bei periodischer CPU-/Netzwerklast vor

  1. Bestätige das Muster objektiv, bevor du tief gräbst. /proc/net/dev in einer Schleife mitschreiben reicht schon, um Einbildung von echtem Problem zu unterscheiden.
  2. Logge über Zeit, schau nicht live zu. Einzelmessungen verpassen kurze Ereignisse fast immer. Jedes Tool, das kann, sollte in eine Datei umgeleitet werden (nethogs -t, tcpdump > file, for-Schleifen mit date und sleep).
  3. Trenne Netzwerk- von Prozessanalyse. ss/tcpdump zeigen Verbindungen, aber keine Prozessnamen zuverlässig über Zeit. nethogs/execsnoop verbinden beides.
  4. Achte auf wechselnde PIDs bei gleichem Befehl. Das ist das verlässlichste Signal für einen Restart-Loop, nicht für einen normal laufenden Dauerprozess.
  5. CPU-Last, die auch ohne Netzwerk bestehen bleibt, deutet auf einen fehlschlagenden Verbindungsversuch hin (DNS, TLS-Handshake, Timeout-Handling), nicht auf reinen Datentransfer.
  6. execsnoop (eBPF) ist der schnellste Weg zur Wahrheit, wenn der Verdacht auf einen sich wiederholt neu startenden Prozess fällt. Ein einziger 60-Sekunden-Mitschnitt reicht meist aus.
  7. Sobald ein systemd-Service identifiziert ist, sofort ins Journal schauen. Die Fehlermeldung, die den Crash-Loop auslöst, steht dort fast immer im Klartext.
  8. Aktiviere Journal-Persistenz präventiv. Ohne sie sind Logs nach einem Neustart oder Absturz unwiederbringlich weg – genau dann, wenn man sie am dringendsten bräuchte.
  9. Gib jedem selbst geschriebenen systemd-Service ein Restart-Limit. Restart=on-failure ohne StartLimitBurst/StartLimitIntervalSec kann jahrelang unbemerkt in einer Schleife laufen und dabei CPU, Strom und im schlimmsten Fall Bandbreite verschwenden.

Randnotiz: Der Systemabsturz, der keiner war

Während der Fehlersuche führte ein einfaches kill -9 auf einen npm-Prozess zu einem sofortigen Totalabsturz des gesamten Systems. Ein normaler kill -9 auf einen einzelnen User-Prozess sollte niemals einen kompletten Absturz auslösen – das deutet eher auf ein unabhängiges, zufällig gleichzeitig auftretendes Problem hin (etwa an der Schnittstelle von GPU-Treiber und Wayland-Compositor). Da zu diesem Zeitpunkt keine persistente Journal-Speicherung aktiv war, ließ sich die Ursache im Nachhinein nicht mehr rekonstruieren – ein weiteres Argument dafür, Journal-Persistenz von Anfang an einzurichten.

Fazit

Werbung

Zwei Tage Fehlersuche für ein einziges vergessenes npm install – das klingt nach viel Aufwand für eine kleine Ursache. Aber der Weg dorthin zeigt ein universelles Muster: Periodische, kurze Lastspitzen lassen sich nicht durch Zuschauen finden, sondern nur durch systematisches Mitschreiben über die Zeit. Wer sich von grob (Netzwerk-Byte-Zähler) zu fein (eBPF-Prozessverfolgung) vorarbeitet, findet praktisch jeden Übeltäter dieser Art – ob es nun ein kaputtes npm-Paket, ein hängender Modell-Ladevorgang oder ein fehlkonfigurierter systemd-Service ist.


Verwendete Tools: ss, nethogs, tcpdump, bcc-tools (execsnoop), journalctl, systemctl. Getestet auf CachyOS (Arch Linux) mit der fish-Shell – die gezeigten Befehle sind teils fish-spezifisch (for i in (seq ...); ...; end statt for i in $(seq ...); do ...; done).

Der Fall der alle 7 Sekunden ausrastenden CPU

Schreibe einen Kommentar

Zum Inhalt springen