Zurück zum Blog

Ein CPU-Alarm, der in Wahrheit ein Firewall-Problem war

Ein CPU-Alarm, der in Wahrheit ein Firewall-Problem war

Datum 15. Juli 2026
Kategorie Infrastruktur
Lesezeit 8 Minuten

Das Symptom: 280 % CPU

Eine Warnung meldete einen Container mit dauerhaft hoher CPU-Last – rund 280 Prozent, also fast drei ausgelastete Kerne. Betroffen war ausgerechnet die Zeitreihen-Datenbank des Monitorings selbst. Der naheliegende Verdacht: teure Abfragen, zu viele Kennzahlen, ein Speicherleck.

Ein Blick auf die Auslastung über die Zeit zeigte ein Sägezahnmuster: Die CPU stieg über etwa eine Stunde langsam an, kollabierte schlagartig und begann von vorn. Solche Muster deuten selten auf die App selbst hin, sondern auf etwas, das im Takt mit ihr wächst und periodisch aufgeräumt wird. Die Abfragen waren tatsächlich vernachlässigbar günstig. Der wahre Treiber lag woanders – und zwar überraschend tief.

Die Ursachenkette rückwärts

Aufgerollt vom Symptom zur Wurzel ergab sich eine Kette über vier Ebenen. Es lohnt sich, sie einmal ganz zu sehen, weil jedes Glied für sich harmlos aussah:

Ebene Beobachtung
1. Symptom Monitoring-Datenbank bei 280 % CPU, im Stundentakt sägezahnförmig
2. Speicherdruck Der Arbeitsspeicher wuchs im selben Takt gegen ein weiches Limit
3. Datenflut Explosionsartig viele neue Zeitreihen – mehr pro Stunde als der gesamte Bestand
4. Wurzel Ein Container startete sich alle zwölf Sekunden selbst neu

Der Zusammenhang zwischen Ebene 3 und 4: Der Metrik-Sammler identifiziert Container über ihre eindeutige ID. Bei jedem Neustart bekommt ein Container eine neue ID – und damit legt der Sammler jedes Mal einen kompletten neuen Satz Zeitreihen an. Ein Container, der sich zehntausendfach neu startet, erzeugt so eine Flut immer neuer, kurzlebiger Kennzahlen.

Diese Flut trieb den Speicher der Zeitreihen-Datenbank über eine Schwelle, ab der die automatische Speicherbereinigung ihrer Laufzeitumgebung dauerhaft lief – über alle verfügbaren Kerne. Das waren die 280 Prozent. Alle ein bis zwei Stunden räumte die Datenbank ihren Zwischenspeicher auf, der Druck fiel, die CPU kollabierte – bis die nächste Runde begann. Das Sägezahnmuster.

Weiches vs. hartes Speicherlimit: Viele Laufzeitumgebungen setzen bei Erreichen eines weichen Limits nicht etwa ab, sondern lassen die Speicherbereinigung permanent laufen, um unter der Grenze zu bleiben. Das Ergebnis ist kein Absturz, sondern eine unauffällige, zähe CPU-Dauerlast. Genau deshalb war das Symptom „hohe CPU“ und nicht „Container abgestürzt“.

Der Crashloop und seine Folgen

Der eigentliche Übeltäter war der Netzwerk-Filter-Container einer Mailserver-Suite. Er überwacht periodisch, ob seine eigene Firewall-Kette an der richtigen Stelle steht, und startet sich neu, wenn das nicht der Fall ist – ein Selbstheilungsmechanismus. Nur heilte hier nichts: Er startete sich alle zwölf Sekunden neu, seit Tagen, zehntausende Male.

Die Fehlermeldung wirkte harmlos – ein Hinweis, dass die Kette „an Position X“ stehe. In Wahrheit war es ein Dauer-Absturz. Und er hatte zwei Nebenwirkungen, die schwerer wogen als die CPU-Last selbst. Dazu später mehr; zunächst zur Wurzel.

Die eigentliche Wurzel: Regel-Reihenfolge

Die Mailserver-Suite besteht darauf, dass ihre Firewall-Kette ganz oben in der Eingangs- und Weiterleitungskette steht. Steht etwas darüber, wertet sie das als Manipulation und startet den Container neu. Genau das passierte: Die Kette war nach unten gerutscht.

Der Grund war ein selbstgeschriebenes Skript zur automatischen Sperrung von Angreifer-IPs. Es fügte jede neue Sperre ganz oben in die Firewall ein – in der Annahme, die geschützte Kette der Mailserver-Suite stünde ohnehin an erster Stelle. Das war ein Trugschluss: An erster Stelle stand die Kette des VPN-Dienstes. Jede neue Sperre schob die geschützte Kette also eine Position weiter nach unten. Nach ein paar Dutzend Sperren war die Toleranz überschritten, und der Selbstheilungs-Neustart begann – endlos.

Die Lehre: Wer Firewall-Regeln automatisiert einfügt, muss die Reihenfolge beherrschen. „Ganz oben einfügen“ ist bequem, aber es kollidiert mit jedem anderen System, das ebenfalls auf eine feste Position angewiesen ist. Die Sperren gehören unterhalb der geschützten Ketten – und diese Position muss dynamisch ermittelt werden, nicht fest verdrahtet.

Die dauerhafte Lösung: ipset statt Einzelregeln

Die schnelle Reparatur wäre gewesen, das Skript ein paar Positionen tiefer einfügen zu lassen. Doch das kuriert nur das Symptom. Zwei tiefere Probleme blieben: Erstens stapelten sich mit der Zeit tausende einzelne Sperrregeln, die jedes Paket linear durchlaufen muss. Zweitens waren viele dieser Regeln längst abgelaufene Sperren, die nie entfernt worden waren – sie leckten.

Die saubere Lösung ist ein ipset: eine kernelseitige Menge von IP-Adressen, auf die eine einzige Firewall-Regel verweist. Statt tausend Regeln gibt es eine Regel und einen Satz von Einträgen.

Vorteile des ipset-Ansatzes:
  • Ein Eintrag, kein Regel-Wildwuchs: Neue Sperren sind Einträge in der Menge, keine neuen Firewall-Regeln. Die Kette bleibt kurz und stabil.
  • Kernel-Zeitlimit: Jeder Eintrag bekommt eine Ablaufzeit. Der Kernel entfernt ihn selbst. Verwaiste Dauer-Sperren sind damit strukturell unmöglich – das Leck-Problem verschwindet.
  • Konstante Nachschlagezeit: Die Prüfung, ob eine IP gesperrt ist, kostet unabhängig von der Anzahl der Einträge gleich viel – statt linear durch tausende Regeln zu laufen.
  • Feste Position: Die eine Regel liegt genau unterhalb der geschützten Kette und bewegt sich nie wieder.

Wichtig ist dabei, dass das Sperr-Skript die Zielposition dynamisch ermittelt – also bei jeder Sperre nachschaut, wo die geschützte Kette gerade steht, und sich direkt darunter einordnet. So heilt sich die Anordnung nach einem Neustart oder einem Umbau der Ketten von selbst.

Zwei stille Nebenwirkungen

Der CPU-Alarm war letztlich das harmloseste Symptom. Zwei Nebenwirkungen des Crashloops wogen schwerer:

Der Brute-Force-Schutz war tot. Bei jedem Neustart löschte der Netzwerk-Filter-Container alle bestehenden Sperren – so ist er gebaut. Bei einem Neustart alle zwölf Sekunden bedeutet das: Keine einzige Sperre überlebte länger als zwölf Sekunden. Der Brute-Force-Schutz des Mailservers war damit tagelang faktisch wirkungslos, ohne dass es irgendwo auffiel.

Die zweite Nebenwirkung waren die bereits erwähnten tausenden verwaisten Sperrregeln, die der Reboot dank Persistenz-Mechanismus sogar überdauerten. Beide Probleme verschwanden mit dem ipset-Umbau: Die Sperren leben jetzt in einer Menge mit Zeitlimit, und der Container startet nicht mehr neu, weil seine geschützte Kette stabil oben bleibt.

Verifiziert wurde das nicht durch Zusehen, sondern durch einen kontrollierten Test: eine harmlose Test-IP sperren, prüfen, dass der Eintrag mit Zeitlimit in der Menge landet, und wieder entsperren. Danach lief der Container stabil ohne einen einzigen weiteren Neustart – nach zuvor zehntausenden.

Lessons Learned

Die wichtigsten Erkenntnisse:
  • Ein CPU-Alarm ist ein Symptom, keine Ursache. Ein Sägezahnmuster deutet auf etwas hin, das im Takt wächst und periodisch aufgeräumt wird – nicht auf die App selbst.
  • Häufige Container-Neustarts sind teurer, als sie aussehen: Jede neue Container-ID erzeugt einen frischen Satz Kennzahlen und kann die Monitoring-Datenbank fluten.
  • Automatisch eingefügte Firewall-Regeln müssen die Reihenfolge respektieren. „Ganz oben“ kollidiert mit jedem System, das selbst eine feste Position braucht.
  • Für dynamische Sperrlisten ist ipset mit Kernel-Zeitlimit die robustere Wahl: eine Regel statt tausend, kein Leck, konstante Nachschlagezeit.
  • Ein Crashloop, der bei jedem Neustart Sperren löscht, legt still den Brute-Force-Schutz lahm. Das Symptom (CPU) verrät davon nichts.
  • Ziele auf die Ursache, nicht auf das Symptom. Das Limit hochzusetzen oder Kennzahlen zu filtern hätte die CPU beruhigt – und den eigentlichen Ausfall weiterlaufen lassen.

Am Ende steht eine unscheinbare Firewall-Regel gegen einen tagelangen, unsichtbaren Ausfall. Genau deshalb lohnt es sich, einem Alarm bis zur Wurzel zu folgen, statt ihn ruhigzustellen: Das lauteste Symptom ist selten das eigentliche Problem.