SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor

Logverwaltung auf einem VPS: Loki, OpenSearch oder grep

Erfahren Sie, wann journald und logrotate genuegen und welche RAM-Untergrenze Loki oder OpenSearch benoetigen. Mit Retention-Regeln fuer ein kleines System.

Was Self-Hosting der Protokollverwaltung auf einem VPS wirklich kostet

Bei der selbst gehosteten Protokollverwaltung auf einem VPS (Virtual Private Server) läuft alles auf eine Frage hinaus: Benötigen Sie einen Such-Cluster oder reichen Rotation und grep? Die meisten Herstelleranleitungen beginnen mit drei Knoten und 12 GB RAM, noch bevor eine einzige Protokollzeile übertragen wird. Auf einem Server ist diese Empfehlung unbrauchbar. Deshalb vergleicht der folgende Abschnitt, welche Anforderungen die einzelnen Optionen an ein kleines System stellen, bevor es überhaupt Daten speichert.

Wenn Sie einen oder zwei Server betreiben und wissen möchten, was am vergangenen Dienstag passiert ist, erledigen systemd-journald und logrotate diese Aufgabe bereits. Dann können Sie nach dem nächsten Abschnitt aufhören. Wenn mehrere Rechner ihre Protokolle über Wochen hinweg an einer Stelle speichern und durchsuchbar machen müssen, eignet sich Grafana Loki für ein kleines System, weil es Labels und nicht den Text der Zeilen indiziert. Elasticsearch und OpenSearch bieten eine echte Volltextsuche. Dafür benötigen sie mehr Arbeitsspeicher, weil der Heap der JVM (Java Virtual Machine) eine Mindestgröße hat, die Sie nicht unterschreiten können.

Mit journald beginnen, weil die meisten hier aufhören

systemd-journald läuft bereits auf jedem aktuellen Ubuntu- oder Debian-Server. Es erfasst die Standardausgabe jeder Service-Unit, die Kernelmeldungen und alles, was an syslog gesendet wird. Vier Befehle decken die meisten Vorfälle ab.

journalctl -u nginx.service --since "2026-08-14 09:00" --until "2026-08-14 10:00"
journalctl -p err -b
journalctl -f -u ssh.service
journalctl --disk-usage

Der letzte Befehl gibt eine Zeile wie Archived and active journals take up 1.1G in the file system. aus. Diese Zahl entscheidet, ob Sie weitere Maßnahmen benötigen. Wenn sie einige hundert Megabyte beträgt und Sie mit -u und --since finden, was Sie benötigen, sind Sie fertig.

Ob das Journal einen Reboot übersteht, hängt von Storage= und davon ab, ob /var/log/journal vorhanden ist. Bei der üblichen Einstellung Storage=auto schreibt journald nach /var/log/journal, wenn dieses Verzeichnis vorhanden ist, und nach /run/log/journal, wenn dies nicht der Fall ist. /run liegt im Arbeitsspeicher. Auf einem System ohne dieses Verzeichnis werden daher beim Reboot alle Logs gelöscht, also genau in dem Moment, in dem Sie sie lesen möchten. Ubuntu-Images enthalten das Verzeichnis. Minimale und containerbasierte Images enthalten es häufig nicht.

ls -d /var/log/journal
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usage

Nach dem Neustart sollte journalctl --disk-usage eine Größe unter /var/log/journal statt unter /run melden. Die Standardwerte sind bereits begrenzt. Das ist der Hauptgrund, warum journald eine zuverlässige Lösung und kein Fallback ist. Die Manpage journald.conf setzt SystemMaxUse= auf 10 % der Dateisystemgröße und SystemKeepFree= auf 15 % und begrenzt jeden berechneten Standardwert auf 4G. SystemMaxFileSize= ist standardmäßig auf ein Achtel von SystemMaxUse= gesetzt, maximal jedoch auf 128M. Dadurch behalten Sie normalerweise sieben rotierte Dateien. MaxRetentionSec= ist standardmäßig auf 0 gesetzt. Dadurch ist die Löschung nach Alter deaktiviert. Lesen Sie diesen letzten Standardwert noch einmal: Standardmäßig wird das Journal nur durch seine Größe begrenzt, niemals durch sein Alter.

[Journal]
Storage=persistent
SystemMaxUse=2G
MaxRetentionSec=30day

Schreiben Sie diese Einstellung nach /etc/systemd/journald.conf.d/99-size.conf, starten Sie journald neu und prüfen Sie anschließend, ob sich journalctl --disk-usage auf Ihre neue Obergrenze zubewegt hat. Um jetzt Speicherplatz freizugeben, statt auf die nächste Rotation zu warten, führen Sie sudo journalctl --vacuum-size=500M oder sudo journalctl --vacuum-time=14d aus. Beide Befehle geben jede entfernte Datei aus. Eine Ausgabe ohne Treffer bedeutet daher, dass nichts gelöscht werden konnte.

Alles außerhalb des Journals, beispielsweise /var/log/nginx/access.log, fällt in den Zuständigkeitsbereich von logrotate. Es wird täglich über einen systemd-Timer ausgeführt. Einen Fehler sollten Sie kennen, weil er wie ein Fehler in df aussieht. Nach einer Rotation ist die alte Datei nicht mehr in der Verzeichnisliste sichtbar, während der Daemon sie weiterhin geöffnet hält. Daher meldet df -h ein volles Dateisystem, während du -sh /var/log deutlich weniger freien Speicher anzeigt. Der Speicherplatz wird erst wieder freigegeben, wenn der Prozess seine Logdatei erneut öffnet. Dafür ist die postrotate-Reload-Zeile in der Konfiguration vorgesehen. sudo lsof -nP +L1 listet gelöschte Dateien auf, die weiterhin geöffnet sind, und nennt den Prozess, der die jeweilige Datei geöffnet hält. Testen Sie eine Regel, ohne Änderungen vorzunehmen, mit sudo logrotate -d /etc/logrotate.d/nginx.

Logs von mehreren Servern an einen Collector senden

Sobald mehr als ein System vorhanden ist, wird die Verwaltung mehrerer Linux-Server gleichzeitig einfacher, wenn deren Logs an einer zentralen Stelle eingehen. rsyslog ist auf den meisten Distributionen bereits installiert. Der einfachste zentrale Collector ist daher eine Datei auf jedem Sender.

*.* action(type="omfwd" target="logs.example.com" port="514" protocol="tcp")

Speichern Sie das als /etc/rsyslog.d/50-forward.conf. Prüfen Sie die Datei mit sudo rsyslogd -N1. Der Befehl validiert die Konfiguration und beendet sich, ohne etwas zu starten. Starten Sie rsyslog anschließend neu. Aktivieren Sie auf dem Collector den TCP-Eingang.

module(load="imtcp")
input(type="imtcp" port="514")

Beachten Sie zwei technische Einschränkungen. Einfaches syslog bietet weder Verschlüsselung noch Authentifizierung. Jedes System, das Port 514 erreichen kann, kann daher Logzeilen einschleusen, die exakt wie Ihre eigenen aussehen. Binden Sie den Dienst an ein privates Netzwerk oder ein VPN und schützen Sie den Port mit der Firewall. Außerdem befindet sich die Standard-Aktionswarteschlange im Arbeitsspeicher. Wenn der Collector nicht erreichbar ist, läuft die Warteschlange voll und Nachrichten werden ohne Kopie verworfen. rsyslog beschreibt für diesen Fall eine datenträgergestützte Warteschlange in seinem Tutorial zur zuverlässigen Weiterleitung.

Warum der ELK-Stack nicht auf einen kleinen VPS passt

ELK steht für Elasticsearch zur Speicherung und Suche, Logstash für die Ingestion-Pipeline und Kibana für die Oberfläche. Die Untergrenze bildet der JVM-Heap. Er wird festgelegt, bevor das erste Log eintrifft.

Die Dokumentation von Elastic empfiehlt, den Heap auf höchstens 50% des für jeden Elasticsearch-Knoten verfügbaren Gesamtspeichers zu setzen, weil der Prozess zusätzlich Off-Heap-Puffer verwendet und auf den Dateicache des Betriebssystems angewiesen ist, um Indexdateien schnell zu lesen. Ein Heap von 2 GB setzt daher einen Rechner mit 4 GB voraus, noch bevor Kibana und der eigentliche Einsatzzweck des Servers berücksichtigt werden. Elastic weist außerdem darauf hin, dass Elasticsearch die Heap-Größe automatisch anhand der Rollen des Knotens und des verfügbaren Gesamtspeichers festlegt. Ein kleiner Rechner erhält daher einen kleinen Heap und verbringt anschließend viel Zeit mit der Garbage Collection.

Logstash überschreitet ein kleines Speicherbudget sofort. Auf der eigenen Seite zu den JVM-Einstellungen empfiehlt Elastic für typische Ingestion einen Heap von mindestens 4GB und höchstens 8GB. Das entspricht dem gesamten Speicher eines 4-GB-VPS – und zwar für nur einen Prozess in der Mitte der Pipeline.

ChartDocumented JVM heap settings, from each project's own docs
The data behind this chart
[
  {
    "label": "Loki plus Alloy (no JVM)",
    "documented_heap_mb": 0
  },
  {
    "label": "OpenSearch demo compose",
    "documented_heap_mb": 512
  },
  {
    "label": "OpenSearch production example",
    "documented_heap_mb": 2048
  },
  {
    "label": "Logstash recommended minimum",
    "documented_heap_mb": 4096
  }
]

Das sind die Werte, die die jeweiligen Projekte in ihrer eigenen Dokumentation veröffentlichen. Es handelt sich nicht um Messungen auf einem Testsystem, und Ihre Arbeitslast kann diese Werte verändern. Die Beispieldatei von OpenSearch für Compose setzt für eine Demo 512 MB pro Knoten und in ihrem Produktionsbeispiel 2048 MB an, während die empfohlene Untergrenze für Logstash 4096 MB beträgt. Die Heap-Spalte enthält für Loki und Alloy 0, weil es sich um Go-Programme handelt, die keinen JVM-Heap reservieren müssen. Der gesamte Unterschied zeigt sich in dieser einen Zahl: Eine JVM-Komponente reserviert diesen Speicher unabhängig davon, ob Logs eintreffen.

Wenn Sie den Elastic-Stack trotzdem auf einem kleinen Server betreiben möchten, lassen Sie Logstash weg und senden Sie die Logs direkt mit einem schlanken Collector an Elasticsearch. Logstash dient dazu, große Datenmengen zu parsen und umzuwandeln. Auf einem einzelnen Rechner können Sie diese Arbeit am Rand der Pipeline erledigen oder ganz überspringen.

Elasticsearch und OpenSearch benötigen außerdem einen erhöhten Wert für vm.max_map_count: 262144. Beide Programme bilden Indexdateien per Memory-Mapping ab, und der Linux-Standardgrenzwert ist dafür zu niedrig. Wenn ein Container auf einem frisch eingerichteten Rechner wenige Sekunden nach dem Start beendet wird, liegt es normalerweise genau daran.

OpenSearch oder Elasticsearch: Was können Sie bereitstellen?

Die kurze Lizenzgeschichte ist relevant, weil sie festlegt, was Sie ausführen dürfen. Im Januar 2021 stellte Elastic Elasticsearch und Kibana von Apache 2.0 auf ein duales Modell aus SSPL (Server Side Public License) und Elastic License 2.0 um. AWS forkte den letzten Code unter Apache 2.0 als OpenSearch, das weiterhin unter Apache 2.0 steht. Im September 2024 ergänzte Elastic AGPLv3 (GNU Affero General Public License version 3) als weitere Option für den frei verfügbaren Quellcode. Für das Self-Hosting durch eine Person auf einem VPS erlauben alle diese Lizenzen den beschriebenen Einsatz. Relevant werden die Einschränkungen, wenn Sie die Software anderen Personen als Managed Service anbieten.

Der praktische Unterschied auf einem kleinen System ist geringer, als die Geschichte vermuten lässt, weil beide Produkte im Kern dieselbe Engine verwenden. Die Bezeichnungen unterscheiden sich: Das Index-Lifecycle-Management heißt bei OpenSearch ISM (index state management) und bei Elasticsearch ILM (index lifecycle management). Ab August 2026 startet OpenSearch 2.12 und höher beim ersten Start nicht ohne gesetztes Admin-Passwort.

sudo sysctl -w vm.max_map_count=262144
printf 'vm.max_map_count = 262144\n' | sudo tee /etc/sysctl.d/99-opensearch.conf
docker run -d -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" \
  opensearchproject/opensearch:latest

Die Zeile sysctl -w wendet die Einstellung sofort an. Die Datei in /etc/sysctl.d/ sorgt dafür, dass sie nach einem Reboot erhalten bleibt. Prüfen Sie mit curl -k -u admin:<password> https://localhost:9200, ob der Container gestartet wurde. Der Container antwortet über https mit einem Demo-Zertifikat. Deshalb überspringt -k die Zertifikatsprüfung. Eine erfolgreiche Antwort ist ein kleiner JSON-Block mit dem Namen des Clusters und der Version. Auf der OpenSearch-Installationsseite wird außerdem für Docker-Desktop-Benutzer empfohlen, dem Host mindestens 4 GB Arbeitsspeicher zuzuweisen. Das zeigt, welche Ressourcen der Prozess ungefähr erwartet.

Wie Loki klein bleibt: Labels statt eines vollständigen Volltextindex

Loki verwaltet einen Index über Labels und speichert die Logzeilen als komprimierte Chunks. Eine Abfrage wählt zuerst Streams aus und filtert anschließend den Text. {unit="ssh.service"} |= "Failed password" wählt den Stream anhand seines Labels aus und durchsucht danach diese Chunks nach der Zeichenfolge. Der Inhalt einer Zeile wird nicht indexiert. Dadurch bleibt die Verarbeitung beim Einlesen kostengünstig, und es muss kein invertierter Index im Arbeitsspeicher vorgehalten werden. Die Kosten fallen stattdessen zur Abfragezeit an. Das ist ein sinnvoller Tausch, wenn Sie normalerweise wissen, welchen Dienst Sie untersuchen.

Die Dokumentation von Grafana ordnet den monolithischen Modus, bei dem Loki einschließlich -target=all in einem Prozess läuft, kleinen Lese- und Schreibvolumen von bis zu etwa 20GB pro Tag zu. Ein VPS liegt deutlich innerhalb dieses Bereichs.

Die Schwierigkeit ist die Label-Kardinalität. Jede unterschiedliche Kombination von Labelwerten bildet einen Stream. Die Anzahl der Streams bestimmt die Größe des Arbeitsspeichers und des Index von Loki. Ein Label mit einer Client-IP-Adresse oder einer Anfragekennung erzeugt für jeden Wert einen eigenen Stream. Ein ausgelasteter Webserver kann daher an einem Tag Zehntausende Streams erzeugen. Der Prozess wächst dann weiter, bis der Kernel ihn beendet. Beschränken Sie Labels auf Werte, die Sie auf Papier abzählen könnten: unit, host, job, level. Speichern Sie variable Details in der Zeile selbst. Dort kann ein Filterausdruck sie zur Abfragezeit finden.

Loki und Alloy auf einem VPS installieren

Zwei Prozesse übernehmen die Aufgabe. Loki speichert Logs und beantwortet Abfragen. Grafana Alloy liest Logs und überträgt sie. Promtail war früher für die Übertragung zuständig und erreichte am 2. März 2026 das Ende seiner Lebensdauer. Neue Installationen verwenden daher Alloy. Auch das eigene Docker-Beispiel von Loki enthält inzwischen eine Alloy-Konfiguration.

wget https://raw.githubusercontent.com/grafana/loki/v3.7.0/cmd/loki/loki-local-config.yaml -O loki-config.yaml

Lesen Sie diese Datei, bevor Sie sie verwenden. Sie setzt path_prefix: /tmp/loki mit Chunks unter /tmp/loki/chunks. Das ist für eine Demo korrekt, für einen Server jedoch ungeeignet: Nichts unter /tmp des Containers bleibt erhalten, wenn der Container neu erstellt wird. Beim nächsten Image-Update geht Ihre Historie daher verloren. Verweisen Sie stattdessen auf einen eingebundenen Pfad.

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory
docker volume create loki-data
docker run --name loki -d \
  -v $(pwd):/mnt/config -v loki-data:/loki \
  -p 127.0.0.1:3100:3100 \
  grafana/loki:3.7.0 -config.file=/mnt/config/loki-config.yaml
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3100/ready

Der letzte Befehl sollte 200 ausgeben, weil /ready HTTP 200 zurückgibt, sobald Loki bereit ist, Netzwerkverkehr anzunehmen. Jede andere Ausgabe bedeutet, dass der Prozess noch startet oder die Konfiguration abgelehnt wurde. docker logs loki nennt den Grund. Zwei Details im Run-Befehl sind beabsichtigt. Der Port wird nur auf 127.0.0.1 veröffentlicht, weil die Beispielkonfiguration auth_enabled: false enthält und Loki keine eigene Benutzerauthentifizierung mitliefert. Jeder Prozess, der Port 3100 erreichen kann, kann daher alle Logs lesen und gefälschte Logs schreiben. Lassen Sie den Dienst auf Loopback gebunden oder schützen Sie ihn durch ein VPN oder einen authentifizierenden Reverse Proxy. Das benannte Volume ist wichtig, weil das Image unter dem Benutzer loki mit der UID 10001 ausgeführt wird. Ein per Bind-Mount eingebundenes Host-Verzeichnis, das root gehört, ist für den Container daher nicht beschreibbar.

sudo apt-get update && sudo apt-get install -y gpg wget
sudo mkdir -p /etc/apt/keyrings/
sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" \
  | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y alloy

Alloy liest /etc/alloy/config.alloy. Diese Konfiguration liest das System-Journal und eine Gruppe von Dateien und überträgt beides an den lokalen Loki.

loki.write "local" {
  endpoint {
    url = "http://127.0.0.1:3100/loki/api/v1/push"
  }
}

loki.relabel "journal" {
  forward_to = []
  rule {
    source_labels = ["__journal__systemd_unit"]
    target_label  = "unit"
  }
}

loki.source.journal "read" {
  forward_to    = [loki.write.local.receiver]
  relabel_rules = loki.relabel.journal.rules
  labels        = {job = "systemd-journal", host = "app-01"}
}

local.file_match "nginx" {
  path_targets = [{"__path__" = "/var/log/nginx/*.log", "job" = "nginx", "host" = "app-01"}]
}

loki.source.file "nginx" {
  targets    = local.file_match.nginx.targets
  forward_to = [loki.write.local.receiver]
}

Die Relabel-Regel kopiert das Journal-Feld __journal__systemd_unit in ein Label mit dem Namen unit. Dadurch funktioniert {unit="ssh.service"} später. Ohne diese Regel steht der Name der Unit innerhalb des Eintrags und nicht in einem Label. Sie können danach nicht auf diesen Namen filtern, und jede Abfrage muss alle Einträge durchsuchen.

sudo systemctl reload alloy
systemctl show -p User alloy
sudo journalctl -n 5
sudo -u alloy journalctl -n 5

Hier bleiben die meisten Installationen hängen. Alloy läuft unter einem eigenen Dienstkonto und nicht als root. Für das Lesen des System-Journals muss dieses Konto Mitglied der Gruppe systemd-journal sein. Dateien unter /var/log/nginx gehören unter Debian und Ubuntu zur Gruppe adm. Ersetzen Sie in dem letzten Befehl das Konto durch den Wert, den systemctl show ausgegeben hat. Gibt der Befehl deutlich weniger Einträge zurück als beim Lauf als root, kann dieses Konto das System-Journal nicht lesen. Loki bleibt dann leer, unabhängig davon, wie korrekt Ihre Konfiguration ist. Fügen Sie die Gruppen hinzu und starten Sie den Dienst mit sudo usermod -aG systemd-journal,adm alloy und anschließend sudo systemctl restart alloy neu.

curl -G -s "http://127.0.0.1:3100/loki/api/v1/query_range" \
  --data-urlencode 'query={job="systemd-journal"}' \
  --data-urlencode 'limit=5' | jq '.data.result | length'

Ein Wert über 0 bedeutet, dass Streams mit diesem Label vorhanden sind und Einträge enthalten. Ein Wert von 0 bedeutet, dass unter diesem Label bisher nichts eingegangen ist. Eine Standardeinstellung verursacht häufig einen Fehlalarm: loki.source.journal setzt max_age auf 7h. Beim ersten Start werden daher die letzten sieben Stunden des Journals gelesen, ältere Einträge jedoch nicht. Für eine grafische Oberfläche führen Sie Grafana auf demselben Server aus und verweisen eine Loki-Datenquelle auf http://127.0.0.1:3100.. Container-Logs benötigen eine andere Datenquelle: Alloy erkennt laufende Docker-Container und liest deren Logs fortlaufend ein. Genau so funktioniert auch das eigene Getting-Started-Beispiel von Loki. In einem einzelnen k3s-Knotencluster auf einem VPS wird dieser Job auf das Verzeichnis für Pod-Logs umgestellt, in das kubelet schreibt.

Aufbewahrung: Legen Sie fest, wann Ihre Logs gelöscht werden

Fast niemand legt einen Aufbewahrungszeitraum fest, bevor der Datenträger voll ist. Dann geschieht es um 3 Uhr morgens, während der Dienst nicht verfügbar ist. Legen Sie den Zeitraum am ersten Tag anhand von zwei Fragen fest: Wie weit zurück sehen Sie tatsächlich nach, und welche Daten müssen Sie bei einer Incident-Analyse im nächsten Monat noch haben? Für einen einzelnen Server sind 14 bis 30 Tage in der Regel ausreichend.

Loki löscht überhaupt nichts, bis Sie den Compactor aktivieren. Die Aufbewahrung ist standardmäßig deaktiviert. Das überrascht viele, wenn das Volume vollgelaufen ist, während retention_period ungenutzt in der Konfiguration stand.

limits_config:
  retention_period: 744h

compactor:
  working_directory: /loki/retention
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150
  delete_request_store: filesystem

744h entsprechen 31 Tagen. Für diesen Block gelten vier dokumentierte Regeln:

  • Die Aufbewahrung wird vom Compactor umgesetzt. Die Dokumentation von Grafana empfiehlt, den Compactor als einzelne Instanz auszuführen. Auf einem einzelnen VPS geschieht das automatisch.
  • Der minimale Aufbewahrungszeitraum beträgt 24h. Die Aufbewahrung funktioniert nur, wenn das Indexintervall 24h beträgt. Das Beispiel schema_config verwendet bereits period: 24h. Lassen Sie diese Einstellung unverändert.
  • delete_request_store ist erforderlich, sobald retention_enabled auf true gesetzt ist. Es bezeichnet den Speicher, in dem Löschanforderungen abgelegt werden. Bei einem einzelnen, dateisystembasierten Knoten entspricht es daher object_store: filesystem, das bereits im Schema festgelegt ist.
  • Chunks werden zunächst markiert und nach retention_delete_delay gelöscht. Hier sind das 2h. Der freie Speicherplatz wird daher später wieder verfügbar, als es die Richtlinie vermuten lässt. Bewerten Sie die Einstellung nicht anhand von df fünf Minuten nach einem Reload.

OpenSearch löscht ganze Indizes statt einzelner Zeilen. Deshalb werden Log-Indizes pro Tag erstellt. Eine ISM-Richtlinie führt einen Index durch mehrere Zustände und löscht ihn, sobald er alt genug ist. Ein ism_template bindet die Richtlinie an neue Indizes, sodass Sie sich darum nicht mehr manuell kümmern müssen.

ISM-Richtlinie, die Log-Indizes nach 14 Tagen löscht
{
  "policy": {
    "description": "delete log indexes after 14 days",
    "default_state": "hot",
    "states": [
      {
        "name": "hot",
        "actions": [],
        "transitions": [
          { "state_name": "delete", "conditions": { "min_index_age": "14d" } }
        ]
      },
      {
        "name": "delete",
        "actions": [ { "delete": {} } ],
        "transitions": []
      }
    ],
    "ism_template": { "index_patterns": ["logs-*"], "priority": 100 }
  }
}

Erstellen Sie sie mit einem PUT an _plugins/_ism/policies/logs-retention. Das Template gilt für Indizes, die nach dem Vorhandensein der Richtlinie erstellt werden. Für bereits auf dem Datenträger vorhandene Indizes muss die Richtlinie daher manuell angewendet werden.

Unabhängig vom eingesetzten System ist ein Aufbewahrungszeitraum nur so zuverlässig wie die dahinterstehende Prüfung des freien Speicherplatzes. Das Löschen nach 14 Tagen hilft nicht, wenn 10 Tage Logs das Volume bereits füllen. Kombinieren Sie die Richtlinie daher mit der Überwachung des Datenträgerzustands auf einem VPS und einem Alarm bei 80 % belegtem Speicherplatz.

Wie viel Speicherplatz pro GB an Logs

Die ehrliche Antwort hängt von Ihren Logzeilen und Feldern ab. Messen Sie daher mit Ihren eigenen Daten, statt einem veröffentlichten Verhältnis zu vertrauen. Die zugrunde liegenden Mechanismen unterscheiden sich deutlich. Dadurch lässt sich zumindest die Richtung abschätzen. OpenSearch und Elasticsearch schreiben für jedes indizierte Feld zusätzlich zum gespeicherten Dokument einen invertierten Index. Die auf dem Datenträger benötigte Größe ist daher größer als der reine Text. Jedes Replica vervielfacht sie außerdem. Setzen Sie auf einem einzelnen Knoten die Replica-Anzahl auf 0. Ein Replica-Shard auf demselben Knoten kann den Ausfall dieses Knotens nicht überstehen. Wenn der Wert auf 1 bleibt, verdoppelt sich der Speicherbedarf, und der Cluster bleibt dauerhaft im Status yellow. Loki schreibt komprimierte Chunks und einen kleinen Label-Index. Sein Speicherbedarf entspricht daher ungefähr der komprimierten Größe der Logzeilen.

sudo du -sh /var/lib/docker/volumes/loki-data/_data
curl -k -u admin:<password> "https://localhost:9200/_cat/indices?v&h=index,docs.count,store.size"

Führen Sie den jeweils passenden Befehl an zwei aufeinanderfolgenden Tagen aus. Die Differenz entspricht Ihrem täglichen Wachstum. Multiplizieren Sie diesen Wert mit der Aufbewahrungsdauer in Tagen. Addieren Sie anschließend etwa 30 % Reserve für Komprimierung und Merges. Vergleichen Sie das Ergebnis dann mit dem verfügbaren Volume. Wenn der Platz nicht ausreicht, verkürzen Sie zuerst die Aufbewahrungsdauer, bevor Sie zusätzlichen Speicher kaufen. Ein größeres Volume verschiebt dasselbe Problem nur um einige Wochen.

Was bei einem kleinen Server zuerst ausfällt

Der Arbeitsspeicher ist zuerst erschöpft. Der OOM-Killer des Kernels (Out of Memory) wählt einen großen Prozess aus. Auf einem Logging-Server ist der größte Prozess die JVM. journalctl -k | grep -i "killed process" zeigt den Kill mit dem Prozessnamen in eckigen Klammern. Nicht immer trifft es den Log-Stack. Stattdessen kann auch sshd oder Ihre Datenbank ausgewählt werden. So kann ein Logging-Experiment die Anwendung zum Ausfall bringen, von der Sie eigentlich Logs erfassen wollten. Geben Sie Containern explizite Obergrenzen. Dadurch tritt der Fehler dort auf, wo Sie ihn vorgesehen haben. Genau dafür sind Speicherlimits in Docker Compose gedacht.

Als Nächstes läuft der Speicherplatz voll. Suchmaschinen fallen dabei auf eine spezifische und eindeutig erkennbare Weise aus. Elasticsearch und OpenSearch überwachen die Festplattennutzung auf mehreren Ebenen. Der Low-Watermark liegt bei 85 %, der High-Watermark bei 90 %. Beim Flood-Stage-Wert von 95 % erhält jeder Index mit einem Shard auf diesem Knoten den Block index.blocks.read_only_allow_delete. Schreibvorgänge schlagen anschließend mit blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] fehl. Der Block wird aufgehoben, sobald die Nutzung wieder unter den High-Watermark fällt. Schaffen Sie zuerst freien Speicherplatz. Heben Sie den Block nur dann manuell auf, wenn er bestehen bleibt.

curl -k -u admin:<password> -X PUT "https://localhost:9200/_all/_settings" \
  -H 'Content-Type: application/json' \
  -d '{"index.blocks.read_only_allow_delete": null}'

Loki fällt unauffälliger aus. Es gibt keinen Read-only-Modus, auf den das System wechseln kann. Ein volles Volume zeigt sich daher beim Sender als fehlgeschlagene Pushes und in den Abfrageergebnissen als Lücken. Das Kardinalitätsproblem äußert sich nicht als Fehler, sondern als langsamer Anstieg des Speicherverbrauchs. Überwachen Sie die Größe des Verzeichnisses chunks regelmäßig und nicht erst nach einem Vorfall.

Der letzte Fehler besteht darin, die falschen Daten zu erfassen. Ein Log-System ist kein Metriksystem. Eine alle 10 Sekunden erfasste CPU-Auslastung, die als Text gespeichert wird, benötigt viel Speicherplatz und lässt sich nur umständlich grafisch darstellen. Diese Aufgabe gehört zu etwas wie einem Zabbix-Monitoring-Server auf Ubuntu 24.04. Anwendungsausnahmen benötigen Gruppierung, Deduplizierung und eine Ansicht des Stacktraces. Dafür ist ein selbst gehosteter Error Tracker vorgesehen. Ob eine Website überhaupt nicht erreichbar ist, ist wiederum eine eigene Aufgabe. Dafür eignet sich eine Uptime- und Statusseite wie Uptime Kuma. Verwenden Sie das Log-System für Textzeilen, die ein Mensch lesen soll.

FAQ

Benötige ich Elasticsearch, um meine Server-Logs zu durchsuchen?

Nicht für einen oder zwei Server. journalctl filtert bereits nach Unit, Priorität, Boot-Vorgang und Zeitraum. Rotierte Dateien beantworten außerdem grep und zgrep. Ein Such-Cluster benötigt den zusätzlichen Speicher, wenn Sie viele Rechner haben, wenn Sie auf allen Rechnern gleichzeitig eine Volltextsuche benötigen oder wenn mehrere Personen eine gemeinsame Oberfläche verwenden. Darunter erledigt journald mit einer Größenbegrenzung und einer Aufbewahrungsdauer dieselbe Aufgabe, ohne zusätzlichen RAM zu benötigen.

Wie viel RAM benötige ich für die selbst gehostete Logverwaltung?

Verwenden Sie die veröffentlichten Angaben des jeweiligen Projekts statt einer Faustregel. Loki und Alloy sind Go-Programme und müssen keinen Heap vorab reservieren. Grafana dokumentiert für monolithisches Loki bis zu ungefähr 20GB pro Tag. Das Beispiel-Compose-Setup von OpenSearch setzt für eine Demo 512 MB Heap und im Produktionsbeispiel 2 GB. Elastic gibt an, dass der Heap höchstens 50% des Gesamtspeichers belegen darf. Ein Heap von 2 GB bedeutet daher vor Kibana einen Rechner mit 4 GB. Die Dokumentation von Logstash empfiehlt allein mindestens 4GB Heap. Das sind dokumentierte Einstellungen, keine Benchmarks. Messen Sie daher Ihre eigene Last, bevor Sie die Dimensionierung festlegen.

Was ist bei Logs der tatsächliche Unterschied zwischen Loki und OpenSearch?

Das Indexmodell. Loki indiziert nur Labels und speichert den Log-Inhalt als komprimierte Chunks, die zum Abfragezeitpunkt durchsucht werden. Dadurch sind Schreibvorgänge günstig, während breit angelegte Abfragen mehr kosten. OpenSearch indiziert den Inhalt der Felder. Dadurch ist eine beliebige Volltextsuche schnell, während sowohl der Speicherbedarf als auch der Festplattenbedarf für den Index steigen. Wählen Sie Loki, wenn Sie wissen, welchen Dienst und welchen Zeitraum Sie benötigen. Wählen Sie OpenSearch, wenn Sie nach Text suchen müssen, den Sie im Voraus nicht vorhersagen können.

Wie lange sollte ich Logs auf einem VPS aufbewahren?

Legen Sie die Dauer fest, bevor der Datenträger dies für Sie erledigt. Konfigurieren Sie sie pro System genau an einer Stelle: MaxRetentionSec= und SystemMaxUse= für journald, retention_period bei aktiviertem Compactor für Loki und eine ISM-Richtlinie mit min_index_age für OpenSearch. Für die meisten Setups mit einem einzelnen Server reichen 14 bis 30 Tage für die Fehlersuche und die Untersuchung von Vorfällen. Alles, was Sie länger aufbewahren müssen, gehört in eine Kopie außerhalb des Servers. Ein Log, das nur auf dem ausgefallenen Server gespeichert ist, ist keine Aufzeichnung.

Ist Promtail weiterhin die richtige Lösung, um Logs an Loki zu übertragen?

Nein. Promtail hat am 2. März 2026 das Ende seiner Lebensdauer erreicht, und Grafana Alloy ersetzt es. Das Docker-Installationsbeispiel von Loki liefert inzwischen eine Alloy-Konfiguration mit. Grafana stellt außerdem einen Konverter bereit, der eine vorhandene Promtail-Konfiguration in die Alloy-Syntax umwandelt. Eine vorhandene Promtail-Installation läuft weiter, erhält jedoch keine Fehlerbehebungen. Behandeln Sie die Migration daher als Wartungsaufgabe und nicht als Upgrade, das Sie unbegrenzt aufschieben können.

#logging#loki#opensearch#journald#monitoring