Worum es geht
Container-Management-Oberflächen wie Portainer oder Arcane erleichtern die tägliche Arbeit mit Docker: Container starten, Logs einsehen, Images aktualisieren. Für die reine Statusüberwachung sind sie jedoch nur ein Fenster auf den aktuellen Moment. Beim Wechsel des Tools ändert sich oft nicht nur die Oberfläche, sondern auch, wie und ob Zustände protokolliert werden.
Eine eigene Pipeline löst dieses Problem grundsätzlich. Sie fragt den Docker-Daemon direkt ab, unabhängig davon, welches Verwaltungswerkzeug gerade im Einsatz ist. Das macht dich unabhängig von Funktionsumfang und Update-Rhythmus einzelner Tools und gibt dir eine durchgängige Historie, die über einen Tool-Wechsel hinweg Bestand hat.
Der Nutzen liegt vor allem in der Kontinuität. Während du die Benutzeroberfläche austauschst, bleibt die Datenbasis für Alarmierung und Auswertung stabil. Das reduziert das Risiko, dass während der Migrationsphase ein Ausfall unbemerkt bleibt.
Anforderungen
Bevor du eine Überwachungspipeline aufsetzt, lohnt sich eine kurze Bestandsaufnahme. Zunächst brauchst du Zugriff auf den Docker-Socket oder eine vergleichbare API, um Containerstatus, Health-Checks und Neustarts auszulesen. Dieser Zugriff sollte unabhängig vom jeweiligen Management-Tool funktionieren.
Zweitens brauchst du einen Speicherort für die Verlaufsdaten. Für kleine bis mittlere Setups reicht eine lokale SQLite-Datenbank völlig aus. Sie benötigt keinen separaten Serverprozess und lässt sich leicht sichern oder migrieren.
Drittens brauchst du einen Mechanismus, der Abweichungen erkennt und Alarme auslöst. Das kann eine einfache Schwellenwertlogik sein (Container seit X Minuten nicht "running") oder eine Klassifikation über ein lokales Modell, etwa via Ollama, wenn du Logtexte semantisch einordnen willst. Für reine Statuswechsel reicht in der Regel die einfache Logik.
Viertens brauchst du einen Benachrichtigungskanal. ntfy, Telegram oder E-Mail eignen sich alle für kleine Setups. Wichtig ist, dass der Kanal zuverlässig zustellt und du Alarme bei Bedarf stummschalten kannst, ohne die Pipeline anzuhalten.
Fünftens solltest du einen Heartbeat einplanen. Ein täglicher Prüflauf, der bestätigt, dass die Pipeline selbst noch läuft, verhindert, dass ein stiller Ausfall des Collectors unbemerkt bleibt.
Möglichkeiten im Vergleich
Es gibt grundsätzlich drei Wege, Containerstatus zu überwachen. Die erste Option ist, sich vollständig auf das jeweilige Management-Tool zu verlassen. Das ist der geringste Aufwand, bindet dich aber an dessen Funktionsumfang und Update-Zyklus. Bei einem Tool-Wechsel startest du bei der Überwachung quasi wieder bei null.
Die zweite Option ist eine dedizierte Monitoring-Lösung wie Prometheus mit cAdvisor oder node-exporter. Das ist robust und skaliert gut, bringt aber zusätzliche Komponenten und Betriebsaufwand mit, der sich für kleine Setups oft nicht lohnt.
Die dritte Option ist eine schlanke, selbst geschriebene Pipeline, wie sie in diesem Artikel beschrieben wird. Sie ist bewusst minimal gehalten: ein Collector-Skript, eine SQLite-Datenbank, eine einfache Alarmlogik und ein Benachrichtigungskanal. Der Vorteil liegt in der vollständigen Kontrolle über Datenstruktur und Alarmregeln. Der Nachteil ist, dass du Wartung und Weiterentwicklung selbst übernimmst.
Für die meisten Indie-Setups und kleinere Teams ist die dritte Option ein guter Kompromiss. Sie bleibt klein genug, um sie an einem Nachmittag zu verstehen, und liefert dennoch eine belastbare Historie.
Umsetzung
Das folgende Beispiel zeigt einen Collector, der den Status laufender Container abfragt, in SQLite protokolliert und bei einer Statusänderung eine Benachrichtigung über ntfy auslöst. Der Code nutzt die offizielle Docker-Python-Bibliothek und das Standardmodul sqlite3.
"""Collector fuer Container-Status, Speicherung und Alarmierung."""
from __future__ import annotations
import logging
import sqlite3
from pathlib import Path
import docker
import requests
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("container_watch")
DB_PATH = Path("container_status.db")
NTFY_TOPIC = "https://ntfy.sh/container-alerts"
def init_db(db_path: Path) -> sqlite3.Connection:
"""Legt die Statustabelle an, falls sie fehlt."""
conn = sqlite3.connect(db_path)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS container_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
status TEXT NOT NULL,
checked_at TEXT DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.commit()
return conn
def get_last_status(conn: sqlite3.Connection, name: str) -> str | None:
"""Liefert den zuletzt gespeicherten Status eines Containers."""
row = conn.execute(
"""
SELECT status FROM container_status
WHERE name = ?
ORDER BY id DESC LIMIT 1
""",
(name,),
).fetchone()
return row[0] if row else None
def notify(message: str) -> None:
"""Sendet eine Benachrichtigung ueber ntfy."""
try:
requests.post(NTFY_TOPIC, data=message.encode("utf-8"), timeout=5)
except requests.RequestException as exc:
logger.warning("Benachrichtigung fehlgeschlagen: %s", exc)
def collect_and_check() -> None:
"""Fragt Containerstatus ab und loest Alarme bei Aenderung aus."""
client: docker.DockerClient | None = None
conn: sqlite3.Connection = init_db(DB_PATH)
try:
client = docker.from_env()
containers = client.containers.list(all=True)
for container in containers:
name = container.name
status = container.status
previous = get_last_status(conn, name)
conn.execute(
"INSERT INTO container_status (name, status) VALUES (?, ?)",
(name, status),
)
conn.commit()
if previous is not None and previous != status:
notify(f"Container {name}: {previous} -> {status}")
logger.info("Statuswechsel bei %s: %s -> %s",
name, previous, status)
except docker.errors.DockerException as exc:
logger.error("Docker-API nicht erreichbar: %s", exc)
finally:
if client is not None:
client.close()
conn.close()
if __name__ == "__main__":
collect_and_check()
Das Skript lässt sich über einen systemd-Timer oder einen Cron-Job in festen Abständen aufrufen, etwa alle fünf Minuten. Für den täglichen Heartbeat reicht ein zusätzlicher einfacher Job, der prüft, ob in den letzten 24 Stunden Einträge geschrieben wurden, und bei Stillstand selbst einen Alarm auslöst.
Für komplexere Klassifikation, etwa das Einordnen von Fehlertexten aus Logs, lässt sich der Collector um einen Aufruf an ein lokales Modell über Ollama erweitern. Für reine Statuswechsel, wie im Beispiel, ist das jedoch meist nicht nötig.
Typische Fallstricke
Ein häufiger Fehler ist, den Docker-Socket ungeschützt für den Collector freizugeben. Achte darauf, den Zugriff auf den Socket auf den Benutzer zu beschränken, der das Skript ausführt, und vermeide unnötige Root-Rechte.
Ein weiterer Fallstrick ist das Fehlen einer Testphase beim Wechsel des Management-Tools. Parallelbetrieb von alter und neuer Lösung über einen begrenzten Zeitraum zeigt, ob deine eigene Pipeline dieselben Zustände erkennt wie das bisherige Tool.
Auch die Alarmschwelle verdient Aufmerksamkeit. Container, die regelmäßig neu starten (etwa bei Rolling-Updates), lösen sonst ständig Fehlalarme aus. Eine kurze Verzögerung oder ein Zähler für aufeinanderfolgende Statuswechsel reduziert diese Alarmflut deutlich.
Schließlich wird die SQLite-Datei oft vergessen, wenn Container-Hosts neu aufgesetzt werden. Sichere die Datenbankdatei regelmäßig, etwa über denselben Backup-Mechanismus, den du für andere Konfigurationsdaten nutzt.
Wann sich das lohnt (und wann nicht)
Eine eigene Watch-and-Alert-Pipeline lohnt sich, wenn du mehrere Docker-Hosts betreibst, regelmäßig zwischen Management-Tools wechselst oder eine durchgängige Historie für Audits brauchst. Der Aufwand für Collector, SQLite-Speicherung und Benachrichtigung ist mit den gezeigten Bausteinen gering und schnell wartbar.
Wenn du nur einen einzelnen Host mit wenigen Containern betreibst und dir die Statusanzeige im Management-Tool ausreicht, lohnt sich der zusätzliche Aufwand kaum. In diesem Fall ist die eingebaute Überwachung des jeweiligen Tools oft völlig ausreichend.
Für Teams mit größerem Bedarf an Skalierung und Dashboards ist eine dedizierte Monitoring-Lösung wie Prometheus die bessere Wahl. Die hier gezeigte schlanke Pipeline eignet sich vor allem für kleine bis mittlere Setups, die unabhängig von einzelnen Management-Tools bleiben wollen.