Graylog¶
Siehe auch
- Verwandte Artikel
- Offizielle Dokumentation
- Linuxfabrik
Die unterstützten Releases sowie deren End-of-Life-Datum (EOL) findet man auf https://go2docs.graylog.org/current/setting_up_graylog/supported_versions.htm. In der Regel werden die letzten drei dot-Releases unterstützt; das kann sich je nach Versionsnummer mit Major Releases überschneiden. Etwa alle 4 bis 6 Monate erscheint ein neues dot-Release.
Es gibt keine Releases, die als besonders stabil oder instabil gelten. Graylog führt daher auch keine LTS-Versionen. Vor einer intensiven Fehleranalyse wird empfohlen, auf eine neuere Version zu aktualisieren.
Graylog Cloud für Europa wird von AWS in Frankfurt gehostet.
- Begriffe
Data Node: seit Graylog 6.0 als Standard empfohlen, ein von Graylog verwaltetes OpenSearch-Cluster. Übernimmt TLS-Erzeugung und Cluster-Bootstrap, sodass man OpenSearch nicht mehr eigenständig administrieren muss.
Extractor: Regel auf einem Input, die aus dem
message-Feld weitere strukturierte Felder extrahiert (Regex, GROK, Split, Copy, JSON). Wird pro Input definiert und läuft auf jedem eingehenden Event. Gilt mittlerweile als Legacy-Feature; für neue Parsing-Logik stattdessen Pipeline + Rule (siehe unten) verwenden.Index Set: Satz gleichartiger OpenSearch-Indices mit eigener Retention-Policy und Shard-/Replica-Konfiguration. Jeder Stream wird mindestens einem Index Set zugewiesen.
Input: Quelle, aus der Graylog Messages liest (Syslog UDP/TCP, Beats, GELF, Raw, AWS CloudWatch, Kafka, …). Jede Input-Instanz lauscht auf einem Port und verarbeitet ein Protokoll.
Pipeline + Rule: regelbasierte Message-Verarbeitung (weit mächtiger als Extractors). Mehrere Rules werden in Stages einer Pipeline gruppiert; Streams binden Pipelines ein.
Sidecar: Log-Collector-Manager, der auf zu überwachenden Hosts läuft und dort
filebeat,winlogbeat,nxlogoderauditbeatmit zentral gepflegter Konfiguration startet. Die Collector-Konfigurationen werden in Graylog verwaltet und an die Sidecars ausgeliefert.Stream: Gruppe von Messages, die durch Regeln (Stream Rules) aus dem gesamten Message-Fluss abgezweigt werden. Streams sind Voraussetzung für Alerts, Pipelines und Dashboard-Widgets.
Features, die laut https://go2docs.graylog.org der Graylog Enterprise Edition vorbehalten sind:
Audit Log (für Graylog selbst)
Correlation Engine
Custom Themes and Notifications
Google Inputs, Microsoft Defender for Endpoint Input, CrowdStrike Input
Graylog Security inkl. Security Interface, Security Events, Anomaly Detection, Asset Enrichment, Investigations, Sigma Rules und Risk Scores
Log Ingestion Limit Notification, Cluster-to-Cluster Forwarder
OIDC, On-Premise Okta Authentication
Routing und Enrichment: Data Warehouse, Indexer and Processing Failures Index, Restore an Archive, Enterprise Output Framework
Search Filters
Visualize: Reports
Installation¶
Bemerkung
Unbedingt die zu installierenden Versionen der abhängigen Software-Pakete beachten: https://go2docs.graylog.org/current/downloading_and_installing_graylog/installing_graylog.html
Graylog kann als Linux-Paket (deb, rpm) oder als Docker-Container (docker-compose, Kubernetes) installiert werden.
Minimum:
2x CPUs
6 GB RAM, besser 8 GB
8 GB Platz auf
/
Hier wird die Installation von Graylog 6.0.x beschrieben.
dnf --assumeyes install epel-release
dnf --assumeyes install dnf-plugin-versionlock
MongoDB 7.x (inkl. Pinning):
Installation siehe MongoDB
Nach der Installation ZWINGEND ausführen:
dnf versionlock add mongodb-org
OpenSearch 2.15.x (inkl. Pinning):
Installation siehe OpenSearch
Nach der Installation ZWINGEND ausführen:
dnf versionlock add opensearchOpenSearch für Graylog konfigurieren:
/etc/opensearch/opensearch.yml¶# for a minimum unsecured running state, single node cluster.name: graylog node.name: ${HOSTNAME} path.data: /var/lib/opensearch path.logs: /var/log/opensearch discovery.type: single-node network.host: 0.0.0.0 action.auto_create_index: false plugins.security.disabled: true indices.query.bool.max_clause_count: 32768
Die eigentliche Installation der Graylog Community Edition kann beginnen:
rpm --upgrade --verbose --hash \
https://packages.graylog2.org/repo/packages/graylog-6.0-repository_latest.rpm
dnf --assumeyes install graylog-server
dnf versionlock add graylog-server
Konfiguration - es müssen password_secret und root_password_sha2 generiert und konfiguriert werden.
password_secret(Passwort für alle Graylog-Nodes im Cluster):< /dev/urandom tr --delete --complement 'A-Za-z0-9' | head --bytes=96; echoroot_password_sha2:echo -n "Enter Password: " && head --lines=1 </dev/stdin | tr --delete '\n' | sha256sum | cut --delimiter=' ' --fields=1
Dann:
password_secret = 3JJgCxO...
root_username = graylog-admin
root_password_sha2 = 0621e81...
http_bind_address = 0.0.0.0:9000
elasticsearch_hosts = https://127.0.0.1:9200
elasticsearch_version_probe_attempts = 10
mongodb_version_probe_attempts = 10
SELinux:
setsebool -P httpd_can_network_connect=on
dnf --assumeyes install policycoreutils-python-utils
semanage port --add --type=mongod_port_t --proto=tcp 27017
systemctl daemon-reload
systemctl enable --now graylog-server.service
tail --follow /var/log/opensearch/opensearch.log
Nach der Installation verfügbare Ports:
9000 (tcp): Graylog-GUI und API-Browser per Swagger UI
9200 (tcp): Elasticsearch/OpenSearch
9300 (tcp): Elasticsearch/OpenSearch Node Communication
27017 (tcp): MongoDB
Port 9000 nach aussen öffnen.
Achtung
Wird Graylog das erste mal gestartet, findet sich der Link zum Login inkl. Initial-Passwort in /var/log/graylog-server/server.log.
Wer von Debian 11 auf Debian 12 aktualisiert, muss dafür sorgen, dass auch das openjdk-17-jre installiert ist, ansonsten kann sich der Graylog-Node nicht mehr am Cluster anmelden.
Konfiguration¶
- Index-Sets, Streams, Extractors
Neues Index-Set mit alternativen Retention Times / Retention Policies einrichten: System / Indices / Indices > Default Index Set > Edit; als Default Index Set einrichten.
Neuen Stream erstellen: Streams > Create stream, „Remove matches from ‚Default Stream‘“ setzen, neues Index-Set verwenden. Manage Rules > Add stream rule > Type: always match - so erhält der neue Stream alle Nachrichten in das neue Default Index-Set.
Extractors lassen sich unter System > Inputs > Extractors anlegen. GROK-Patterns orientieren sich an Oracles Java Pattern Class; ein Java-kompatibler Regex-Tester findet sich unter http://www.regexplanet.com/advanced/java/index.html.
- Config Ex- und Import
Export: System / Inputs > Content Packs: Create a content pack
Import: System / Inputs > Content Packs > Select content packs: Import content pack
- LDAP
Eine Gruppen-Synchronisation ist in Graylog Open nicht möglich (Enterprise-Feature). Eine bestimmte LDAP-Gruppe lässt sich aber per Search-Filter einschränken, beispielsweise
(&(&(uid={0})(objectClass=inetOrgPerson))(memberOf=cn=MYGROUP,cn=groups,cn=accounts,dc=example,dc=org))(im Beispiel heisst die GruppeMYGROUP).
Index Sets und Indizes¶
Ein Elasticsearch-„Index“ hat einen Namen, z.B. „graylog_0“.
Default Settings des „Default index set“ (seit Graylog 6.0):
Rotation strategy: Time-Based Size Optimizing - Graylog rotiert nach Zeit und Index-Grösse innerhalb einer konfigurierbaren Min-/Max-Lifetime, nicht mehr nach einer festen Message-Anzahl.
Retention strategy: Delete Index
Die früher voreingestellte zählerbasierte Rotation (Rotation nach Message-Anzahl, Default 20’000’000 Messages je Index, maximal 20 Indices) lässt sich beim Anlegen oder Bearbeiten eines Index Sets weiterhin manuell wählen.
Graylog rotiert die Indices selbst und reicht die Strategie nicht 1:1 an OpenSearch/Elasticsearch durch: Ein periodischer Job im Graylog-Server prüft pro Index Set anhand der OpenSearch-/Elasticsearch-Statistiken (Message-Anzahl, Index-Grösse, Alter), ob die konfigurierte Schwelle überschritten ist, legt bei Bedarf einen neuen Index an und schwenkt den Write-Alias um. Eine Ausnahme bilden Data-Stream-basierte Index Sets (Data Tiering): dort übergibt Graylog eine ISM-Policy an OpenSearch, das den Rollover und das Löschen dann selbst ausführt.
Wer eigene Streams definiert, sollte darauf achten, den Haken „Remove matches from ‚All messages‘ stream“ zu setzen, damit Nachrichten nicht doppelt gespeichert werden.
Theoretisch lassen sich mit diesen curl-Aufrufen nach einer Änderung die Indices neu bauen (in der Praxis funktioniert das oft nicht zuverlässig - auch nach einem einfachen Reboot des Graylog-Servers können die alten Indizes noch vorhanden sein):
# if Elastic is in read-only mode, switch it back to normal
curl --request PUT \
--header "Content-Type: application/json" \
--data '{"index.blocks.read_only_allow_delete": null}' \
http://elastic.example.com:9200/_all/_settings
# start retention job
curl --request POST http://graylog.example.com:9000/api/system/indices/ranges/rebuild
Indizes aus Elasticsearch auslesen:
curl 'http://elastic.example.com:9200/_cat/indices?v'
Indizes-Statistiken:
curl 'http://elastic.example.com:9200/_stats'
Indizes leeren:
curl --request DELETE 'http://elastic.example.com:9200/_all'
Inputs und Nachrichten¶
Input: Syslog¶
Soll Graylog Logdaten entgegennehmen, müssen die unter „System > Inputs“ konfiguriert werden.
Für das althergebrachte Syslog Port 1514 (udp) verwenden und lokale Firewall öffnen.
Bemerkung
Graylog kann nicht auf Port 514 hören, das führt zu einem „Permission denied, Failed to bind to: ip-address:514“, da es sich hier um einen privilegierten Port handelt. Daher wie in allen Beispielen Port 1514 verwenden. Um einen Bind auf jede IP-Adresse einzurichten, 0.0.0.0 verwenden, * funktioniert als Angabe nicht.
Nachrichtenformat¶
Welche Felder sind Standard und/oder Pflicht?
message: das einzige Pflichtfeld, muss gesetzt werden; ohne gesetzte „message“ wird Nachricht nicht angenommen.
Felder, welche automatisch gesetzt werden:
source(wenn nicht gesetzt, wird die IP des sendenden Hosts eingetragen (nicht die eines möglichen Docker-Containers))timestamp(wenn nicht gesetzt, wird aktueller Timestamp verwendet; überschreibbar mit UNIX-Timestamp; falls falsch überschrieben, wird die Nachricht verworfen)
Message-IDs sind UUIDs/GUIDs: ee1bdfe2-4c23-11e7-9f76-525400ce74cd.
Nachrichtenversand testen¶
Wichtig
Taucht in einer JSON-Nachricht an einen Syslog-Input der Key „level“ auf, wird die Nachricht von Graylog verworfen, sobald der dazu gehörende Value nicht kompatibel mit Syslog ist. Beispiel: „level“:“FINE“ wird verworfen, „level“:“5“ dagegen akzeptiert.
Syslog per 1514/udp:
logger --server graylog.example.com --udp --port 1514 "Test Syslog 1514/udp on $(date)"
Um GELF zu testen, wird ncat benötigt.
dnf --assumeyes install nmap-ncat
GELF per 12201/UDP:
# shortest message possible
echo -e '{"message": "Test GELF 12201/udp on '$(date)'"}' \
| ncat --wait 1 --udp graylog.example.com 12201
# longer message, should split up into fields
echo -e '{
"version": "1.1",
"host": "example.org",
"short_message": "Test GELF 12201/udp on '$(date)'",
"full_message": "Backtrace here\nsome stuff\nmore stuff",
"level": 1,
"_user_id": 9001,
"some_info": "foo",
"some_env_var": "bar"
}' | ncat --wait 1 --udp graylog.example.com 12201
GELF per 12201/TCP:
echo -e '{
"version": "1.1",
"host": "example.org",
"short_message": "Test GELF 12201/tcp on '$(date)'",
"full_message": "Backtrace here\nsome stuff\nmore stuff",
"level": 1,
"_user_id": 9001,
"some_info": "foo",
"some_env_var": "bar"
}' | ncat --wait 1 graylog.example.com 12201
Tipp
Graylog bei der Arbeit zuschauen: tail --follow /var/log/graylog-server/server.log /var/log/opensearch/*log
Log-Daten senden (Clients)¶
rsyslog¶
Soll rsyslog Events an einen Graylog Server weiterleiten, empfiehlt sich das GELF Protokoll, da hier einfach zusätzliche Felder mitgeschickt werden können:
# send server's FQDN instead of its short hostname
$PreserveFQDN on
template(name="gelf" type="list") {
constant(value="{\"version\":\"1.1\",")
constant(value="\"host\":\"")
property(name="hostname")
constant(value="\",\"short_message\":\"")
property(name="msg" format="json")
constant(value="\",\"timestamp\":")
property(name="timegenerated" dateformat="unixtimestamp")
constant(value=",\"level\":\"")
property(name="syslogseverity")
constant(value="\"}")
}
# syslog forwarder via UDP
action(type="omfwd" target="graylog.example.com" port="12201" protocol="udp" template="gelf")
Damit ein Client sinnvolle Daten weiterleitet, kann folgende rsyslog-Konfiguration als Basis verwendet werden. So können die Events schon auf dem Client gefiltert werden, und dann per Syslog oder GELF weitergeleitet werden.
# rsyslog v7 filter conditions:
# contains isequal startswith regex ereregex
# http://www.rsyslog.com/doc/v7-stable/configuration/filters.html
if (
$msg startswith "GSSAPI client step " or
$msg startswith "GSSAPI server step " or
($programname == "kernel" and $msg startswith "RULE ") or
($programname == "systemd" and ($msg startswith "Created slice " or $msg startswith "Removed slice ")) or
($programname == "systemd" and ($msg startswith "Starting user-" or $msg startswith "Stopping user-")) or
($programname == "systemd" and ($msg startswith "Starting Session " or $msg startswith "Started Session ")) or
($programname == "systemd-logind" and ($msg startswith "New Session " or $msg startswith "Removed Session "))
)
then
# ignore, do not foward
continue
else
# Syslog
*.* @graylog.example.com:1514;RSYSLOG_SyslogProtocol23Format
# GELF (requires the template from above)
# action(type="omfwd" target="graylog.example.com" port="12201" protocol="udp" template="gelf")
Beispiel einer Weiterleitung per rsyslog für einen JBoss-Applikationsserver (alternativ WildFly):
module(load="imfile" PollingInterval="10")
input(
type="imfile"
File="/opt/jboss/standalone/log/server.json"
Tag="jboss-eap"
StateFile="statefile-to6nnu8DgDug4ImsaaJCpkAQt0zPKb"
)
# just forward, do not log again in /var/log/messages followed by the rules later on in /etc/rsyslog.conf
if $programname == 'jboss-eap' then {
action(
type="omfwd"
Target="graylog.example.com"
Port="1514"
Protocol="udp"
)
stop
}
Apache¶
Am besten so: https://www.lisenet.com/2016/send-apache-logs-to-graylog/
LogFormat: direkt GELF-kompatibles JSON schreiben
CustomLog mit
ncatdirekt an Graylog pipen
# GELF Log Format
LogFormat "{\n\
\"version\": \"1.1\",\n\
\"host\": \"%v\",\n\
\"short_message\": \"%r\",\n\
\"timestamp\": %{%s}t,\n\
\"level\": 6,\n\
\"_user_agent\": \"%{User-Agent}i\",\n\
\"_source_ip\": \"%{X-Forwarded-For}i\",\n\
\"_duration_usec\": %D,\n\
\"_duration_sec\": %T,\n\
\"_request_size_byte\": %O,\n\
\"_http_status_orig\": %s,\n\
\"_http_status\": %>s,\n\
\"_http_request_path\": \"%U\",\n\
\"_http_request\": \"%U%q\",\n\
\"_http_method\": \"%m\",\n\
\"_http_referer\": \"%{Referer}i\",\n\
\"_from_apache\": \"true\"\n\
}" gelf
CustomLog "|/usr/bin/ncat --udp graylog.example.com 12201" gelf
journalctl per GELF¶
GELF/udp: die halboffizielle Implementierung, uralt - https://github.com/systemd/journal2gelf
GELF/udp - verbessert: https://github.com/nailgun/journal2gelf
GELF/udp oder tcp: https://github.com/oboukili/journald-to-gelf
Hinweis: Nachrichten per TCP müssen mit einem „0“ beendet werden.
Empfohlen wird nailgun/journal2gelf. Zunächst EPEL-Repo aktivieren, dann:
dnf --assumeyes install git python-pip gcc python-devel systemd-devel
python3 -m pip install --user git+https://github.com/systemd/python-systemd.git#egg=systemd
python3 -m pip install --user gelfclient
git clone https://github.com/nailgun/journal2gelf.git
cd journal2gelf
python setup.py install
journal2gelf --help
Test (geht per GELF an Port 12201/udp):
journal2gelf graylog.example.com:12201
[Unit]
Description=Journald to GELF (graylog) log relay service
[Service]
ExecStart=/usr/bin/journal2gelf graylog.example.com:12201
Restart=on-failure
RestartSec=30
[Install]
WantedBy=multi-user.target
Fortinet, Java Application Server¶
Bei Fortinet-Geräten wird statt Syslog besser das Common Event Format (CEF) verwendet.
Alle Log-Daemons arbeiten nach dem Prinzip „Zeilenumbruch = neue Log-Message“, so auch Graylog. Damit Stacktraces aus einem Java Application Server nicht in dutzende Einzelnachrichten zerfallen, wird im Java-Applikationsserver ein GELF-Appender im nativen, vom Entwickler eingesetzten Java-Logging-Framework (Log4j, Logback etc.) konfiguriert. Viele dieser Appender bauen auf dem GELF Java Client (https://github.com/Graylog2/gelfclient) auf.
Windows¶
Soll ein Windows-Server Event-Logs senden, wird ein auf Windows zu installierender Agent benötigt. Graylog empfiehlt die Kombi aus WinLogBeat (der eigentliche Client) und Graylog Sidecar (ein Daemon zur Konfiguration des Clients durch den Graylog-Server). Siehe auch https://go2docs.graylog.org/current/getting_in_log_data/ingest_windows_eventlog.html
Auf dem Graylog Server:
Für den Sidecar-Benutzer einen API Key unter „System > Users and Teams“ erstellen.
Ausserdem muss ein Beats-Input erstellt werden.
Installation von Sidecar auf dem Windows Host:
Sidecar herunterladen: https://github.com/Graylog2/collector-sidecar/releases
Sidecar installieren: Während des Setups kann direkt die API-URL wie http://graylog.example.com:9000/api sowie der API Key eingegeben werden. Das Tool WinLogBeat wird automatisch mit installiert und konfiguriert.
Zurück auf dem Graylog Server sollte das zuvor installierte Sidecar unter „System > Sidecars“ zu sehen sein. Der Collector auf dem Client muss allerdings noch wissen, welche Daten er sammeln soll - das wird in einer Collector-Configuration festgelegt:
System > Sidecars > Configuration
Create Configuration
Name für die Konfiguration vergeben (z.B. „winlogbeat-default“)
Collector: winlogbeat on Windows
Tags: Wer den Collector pro Applikation (z.B. für das Sammeln von Apache httpd-logs) konfiguriert, vergibt hier den Tag für die Applikation (also z.B. „Apache httpd“). Für Windows-Systemlogs bietet sich „OS - Windows“ an.
Eine brauchbare Konfiguration wäre (siehe https://go2docs.graylog.org/current/getting_in_log_data/ingest_windows_eventlog.html):
fields_under_root: true
fields.collector_node_id: ${sidecar.nodeName}
fields.gl2_source_collector: ${sidecar.nodeId}
output.logstash:
# this refers to the Beats input on Graylog
hosts: ["192.0.2.74:5044"]
ssl:
enabled: true
verification_mode: full
winlogbeat.event_logs:
- name: Application
level: critical, error, warning
ignore_older: 48h
- name: Security
processors:
- drop_event.when.not.or:
- equals.event_id: 129
- equals.event_id: 141
- equals.event_id: 1102
- equals.event_id: 4648
- equals.event_id: 4657
- equals.event_id: 4688
- equals.event_id: 4697
- equals.event_id: 4698
- equals.event_id: 4720
- equals.event_id: 4738
- equals.event_id: 4767
- equals.event_id: 4728
- equals.event_id: 4732
- equals.event_id: 4634
- equals.event_id: 4735
- equals.event_id: 4740
- equals.event_id: 4756
level: critical, error, warning, information
ignore_older: 48h
- name: System
processors:
- drop_event.when.not.or:
- equals.event_id: 129
- equals.event_id: 1022
- equals.event_id: 1033
- equals.event_id: 1034
- equals.event_id: 4624
- equals.event_id: 4625
- equals.event_id: 4633
- equals.event_id: 4719
- equals.event_id: 4738
- equals.event_id: 7000
- equals.event_id: 7022
- equals.event_id: 7024
- equals.event_id: 7031
- equals.event_id: 7034-7036
- equals.event_id: 7040
- equals.event_id: 7045
level: critical, error, warning
ignore_older: 48h
Jetzt kann die Collector-Configuration dem Client zugewiesen werden:
System > Sidecars > Administration
winlogbeat aktivieren > Assign Configurations > „winlogbeat-default“ auswählen
Die Konfiguration wird auf dem Client in der Regel unter C:\Program Files\Graylog\sidecar\generated\<RANDOM>\winlogbeat.conf abgelegt, und kann per C:\Program Files\Graylog\sidecar\winlogbeat.exe test config --c generated\...\winlogbeat.conf getestet werden. Die zu Graylog Sidecar gehörenden Windows-Services heissen „Graylog collector sidecar .*“ und „Graylog Sidecar“, und sollten überwacht werden.
Suche und Verarbeitung¶
Graylog Query Language¶
Beispiel für eine Suche in zwei Textfeldern und einem Integer-Feld:
NOT requestURI:"/billing/*" AND NOT requestURI:"/calendar/*" AND durationInMs:>500
Hosts ausschliessen:
NOT source:hostA and NOT source:hostB
Das Beispiel
source:host.example.* AND response-time:<30000
liefert auch Ergebnisse mit „response-time“ von 100’000 und mehr? Da drängt sich der Verdacht auf, dass es sich hier um eine Suche nach Strings und nicht nach Integern handelt.
So kann man überprüfen, wie Elasticsearch die Werte speichert - auf dem Log-Server:
curl 'http://localhost:9200/_mapping?pretty'
curl 'http://localhost:9200/_cat/indices?v'
curl 'localhost:9200/_cat/templates?v'
Pipelines¶
Unter System > Configuration > Message Processors Configuration die „Pipeline“ aktivieren und nach der „Message Filter Chain“ setzen, wenn man auf Felder prüfen möchte, die von Extraktoren gesetzt wurden.
Ablauf:
Pipeline anlegen
Stage anlegen
Rule anlegen
Rule zur Stage hinzufügen
Pipeline zu Stream connecten
Sinnfreies Beispiel für eine Rule mit Kommentaren, Abfrage auf einen Input und Wegwerfen einer Nachricht:
rule "unneeded if container_name contains _app_ or _cep_"
when
(
// to_string($message.gl2_source_input) == "5b16a9176e8bf60b6cf8bc6b" and
has_field("container_name") and
(contains(to_string($message.container_name), "_app_") or contains(to_string($message.container_name), "_cep_"))
)
then
set_field("unneeded_message", true);
drop_message();
end
Simulation:
GELF-Message: {
"version": "1.1",
"host": "example.org",
"short_message": "Short message",
"full_message": "Full message",
"level": 1,
"container_name": "my_container"
}
Wert nach Long konvertieren:
rule "Convert RT_FLOW to Numeric"
when
has_field("bytes-from-server" ) && $message.application_name == "RT_FLOW"
then
let serverre = to_long( $message.`bytes-from-server`);
let clientre = to_long( $message.`bytes-from-client`);
set_field("bytes-from-server_conv", serverre );
set_field("bytes-from-client_conv", clientre );
end
Events und Alerts¶
„Alerts“ bedeutet: Man definiert einen Event (also einen Match auf bestimmte Log-Einträge), welcher eine Notification auslöst.
Beispiel: Sende eine Nachricht nach Rocket.Chat, sobald das Feld „operator_alert“ in einer Nachricht in einem bestimmten Stream vorkommt.
Graylog GUI > Alerts > Event Definitions > Create event definition
Event Details
Title: Operator Alert
Filter & Aggregation
Search Query:
_exists_:operator_alertStreams: Stream auswählen
Search within the last: 1 hours
Execute search every: 5 seconds
Notifications
Add notification
Title: operator_alert Notification
Notification Type: Custom HTTP Notification
URL: Rocket.Chat Webhook URL
Body Template:
{ "channel": "my channel", "text": ":service_crit: Graylog ${event_definition_title}: ${foreach backlog message}${message.message}${end}" }
Message Backlog: 1
Sicherheit¶
TLS¶
Graylog kann so konfiguriert werden, dass die Kommunikation der Logdaten der Clients TLS-verschlüsselt passiert.
- Server-Konfiguration
Auf dem Graylog-Server muss beim gewünschten Input
Enable TLSaktiviert und die Pfade für das TLS-Zertifikat und den Private-Key beiTLS cert fileundTLS private key filehinterlegt werden. Sind Zertifikat und Private-Key im gleichen File vorhanden, so kann auch nur einer der beiden Pfade gesetzt werden.Das Zertifikat muss den Hostnamen der Inputs entsprechen, d.h. meldet sich ein Client beim Graylog-Server
example.comso muss für eine erfolgreiche Kommunikation das konfigurierte Zertifikat entweder den Common Name (CN) oder ein Subject Alternative Name (subjectAltName) fürexample.comaufweisen.Sobald bei einem Input
Enable TLSaktiv ist, werden nur noch TLS-verschlüsselte Verbindungen akzeptiert. Wenn einige Clients TLS nicht unterstützen, so muss entweder TLS deaktiviert werden oder ein separater, unverschlüsselter Input auf einem anderen Port angelegt werden.- Client-Konfiguration
Clients müssen abhängig vom Nachrichten-Protokoll konfiguriert werden. Werden Graylog Sidecars verwendet, nutzen die Sidecars automatisch TLS.
Ist das Zertifikat nicht von einer Certificate Authority ausgestellt, die im Hostsystem installiert und trusted ist (z.B. self-signed Zertifikate), so kann in der Sidecar-Collector-Konfiguration die CA manuell mit
ssl.certificate_authoritiesgesetzt werden (die CA muss schon auf dem Client vorhanden sein; sie wird nicht per Sidecar verteilt):... output.logstash: hosts: ["example.com:5044"] ssl: certificate_authorities: ["/opt/my-example.com-ca.crt"] enabled: true verification_mode: full ...
Benutzer und Rollen¶
Benutzer benötigt Decorator-Edit-Rechte? Sowas geht nur über das REST-API.
Verwendbare Berechtigungen abfragen:
curl --request GET \
--user admin:linuxfabrik \
'http://graylog.example.com:9000/api/system/permissions?pretty=true'
Ergebnis u.a.:
"decorators" : [ "create", "read", "edit" ]
Neue Rolle erstellen:
curl --request POST \
--user admin:linuxfabrik \
--header 'Content-Type:application/json' \
--data '{
"read_only": false,
"permissions": ["decorators:create:*", "decorators:read:*", "decorators:edit:*"],
"name": "Full Decorator Permissions",
"description": "Permission to create, read or delete decorators."
}' \
'http://graylog.example.com:9000/api/roles'
Anschliessend kann die neue Rolle im GUI > Authentication Management ganz unten unter „Roles“ den Benutzern zugeteilt werden.
Berechtigungen für den Benutzer prüfen:
curl --request GET \
--user admin:linuxfabrik \
"http://graylog.example.com:9000/api/users/$USERNAME?pretty=true"
API¶
Alle Methoden auf einen Blick: http://graylog.example.com:9000/api/api-browser
Authentifizierung entweder mittels Username und Passwort, oder per Access Token (letztere ist aus Sicherheitsgründen die bevorzugte Methode).
Access Token erstellen:
Benutzer erstellen, Standard-Rolle „Reader“ notfalls anpassen
Token zuweisen
curl --user user:linuxfabrik \
--header 'Accept: application/json' \
--request GET \
'http://graylog.example.com:9000/api/...' | jq
Cluster-Informationen auslesen - mit Username und Passwort:
curl --user admin:linuxfabrik \
--header 'Accept: application/json' \
'http://graylog.example.com:9000/api/cluster?pretty=true'
Cluster-Informationen auslesen - mit Access Token (Token als Benutzername nutzen; Passwort ist hier immer „token“):
curl --user 1crgamba51dkdn8jsrltpek90m167p1dmteaa0gr7ehscnb3bpce:token \
--header 'Accept: application/json' \
'http://graylog.example.com:9000/api/cluster?pretty=true'
Log-Einträge auf Basis einer Query als CSV exportieren:
# urlencoded query
#q=*
q=syslog_identifier%3Asshd%20AND%20opened%20AND%20NOT%20root
range=300 # in seconds
# comma-separated list of fields (%2C)
curl --user admin:linuxfabrik \
--header 'Accept:text/csv' \
'http://graylog.example.com:9000/api/search/universal/relative/export?query=$q&range=$range&fields=timestamp%2Csource%2Cmessage' \
| gzip --best > export.csv
Streams und Stream Rules:
Stream Rule Type 1: match exactly
Stream Rule Type 2: match regular expression
Stream Rule Type 3: greater than
Stream Rule Type 4: smaller than
Stream Rule Type 5: field presence
Stream Rule Type 6: contain
Stream Rule Type 7: always match
Stream Rule Type 8: match input
curl --user user:linuxfabrik \
--header 'Accept: application/json' \
--header 'X-Requested-By: cli' \
--data '{
"title": "All messages",
"description": "All messages are routed here",
"matching_type": "AND",
"rules": [
{
"field": "",
"type": 7,
"inverted": false,
"value": ""
}
],
"content_pack": null
}' \
--request POST \
'http://graylog.example.com:9000/api/streams' | jq
Upgrade¶
Vor dem Upgrade sollte man sich die Upgrading, Release Notes und den Changelog anschauen (Achtung: richtige Version im Dropdown oben auswählen).
Graylog muss inkrementell über jede Minor-Version aktualisiert werden, einzelne Versionen dürfen nicht übersprungen werden. Andernfalls drohen fehlgeschlagene Migrationen oder ein inkonsistenter Systemzustand. Ein Upgrade von 5.2 auf 7.1 erfolgt also schrittweise: 5.2 → 6.0 → 6.1 → 6.2 → 6.3 → 7.0 → 7.1. Bei jedem Zwischenschritt müssen in einem Cluster zuerst alle Nodes auf dieselbe Version gebracht werden, bevor der nächste Schritt folgt. Siehe Incremental Upgrade.
Achtung
MongoDB und self-managed OpenSearch muss immer vor dem Graylog Server auf eine kompatible Version aktualisiert werden. Der Graylog Datanode hingegen sollte nach dem Graylog Server aktualisiert werden.
Graylog empfiehlt kein Rolling Upgrade des Servers, weshalb eine Downtime geplant werden muss (aufgrund möglicher MongoDB-Schema-Migrationen werden keine gemischten Graylog-Server Versionen unterstützt).
Graylog Server aktualisieren:
MongoDB Dump erstellen
Anhand der Compatibility Matrix prüfen, ob MongoDB für die Ziel-Version aktualisiert werden muss; falls ja, MongoDB vor dem Graylog Server aktualisieren (siehe MongoDB)
Alle Graylog Server stoppen:
systemctl stop graylog-server.serviceVersion im Repo-File aktualisieren:
$EDITOR /etc/yum.repos.d/graylog.repoGraylog Server Package aktualisieren:
dnf update graylog-serverConfig File prüfen, eventuell Änderungen anhand von den Release Notes und den Changelog durchführen:
$EDITOR /etc/graylog/server/server.confZuerst den „leader“ Graylog Server Node starten:
systemctl start graylog-server.serviceDanach alle anderen Graylog Server Nodes starten:
systemctl start graylog-server.service
Graylog Datanode aktualisieren:
Falls drei oder mehr Datanodes betrieben werden, kann ein Rolling Upgrade ohne jegliche Downtime ausgeführt werden:
Dies kann man im Graylog WebGUI unter System > Cluster Configuration > Data Node Upgrade starten
„Start Upgrade Process“ beim ersten Node anwählen, dann auf dem Node:
Graylog Datanode stoppen:
systemctl stop graylog-datanode.serviceVersion im Repo-File aktualisieren:
$EDITOR /etc/yum.repos.d/graylog.repoGraylog Datanode Package aktualisieren:
dnf update graylog-datanodeConfig File prüfen, eventuell Änderungen anhand von den Release Notes und den Changelog durchführen:
$EDITOR /etc/graylog/datanode/datanode.confGraylog Datanode starten:
systemctl start graylog-datanode.service
Nun kann das Upgrade für den Node im WebGUI per „Confirm Upgrade“ bestätigt werden. Die Schritte sind für die weiteren Datanodes analog durchzuführen. Dabei muss man nach jedem Node abwarten, bis die alle Shards alloziert / verteilt wurden. Das kann einen Moment dauern.
Falls weniger als drei Datanodes betrieben werden, muss auch hier eine Downtime erwartet werden. Das Upgrade-Vorgehen auf den Nodes ist gleich wie beim Rolling Upgrade.
Backup und Restore¶
Beim Backup- und Restore-Prozess sind folgende Komponenten zu berücksichtigen:
MongoDB (Siehe Backup und Restore)
OpenSearch Snapshots (hier beschrieben)
Konfiguration (
/etc/), Plugins (/usr/share/graylog-server/plugin) und Zertifikate (hier nicht im Detail beschrieben, per file-basiertem Backup sichern, z.B. mittels duplicity). Konkret zu sichern:
Darüber hinaus gilt es, die exakten Versionen von MongoDB sowie Graylog Datanode und Server festzuhalten, damit im Wiederherstellungsfall keine Probleme aufgrund von Versionsunterschieden entstehen.
Bemerkung
Dieser Abschnitt behandelt ausschliesslich dateisystem-basierte Snapshot-Repositories. S3- und andere Repository-Typen sind hier bewusst nicht abgedeckt.
Ablauf für den Graylog Datanode (dateisystem-basiertes Snapshot-Repository):
Repository-Pfad in
/etc/graylog/datanode/datanode.confkonfigurierenClient-Zertifikat von Graylog-Server beziehen
Snapshot-Repository via Datanode-API registrieren
Snapshot anlegen
Repository-Verzeichnis anlegen und Pfad auf Datanode konfigurieren:
snapshot_repo_path='/mnt/opensearch-snapshots'
mkdir --parents "$snapshot_repo_path"
chown graylog-datanode:graylog-datanode "$snapshot_repo_path"
chmod 700 "$snapshot_repo_path"
path_repo = /mnt/opensearch-snapshots
Warnung
path_repo ist eine kommaseparierte Liste, kein YAML-Array. path_repo = ["/path/to/snapshot/repo"] wird vom Datanode stillschweigend akzeptiert, das eingebettete OpenSearch scheitert danach mit einer unklaren Fehlermeldung (siehe Graylog Issue #22478). Mehrere Pfade müssen so angegeben werden: path_repo = /pfad/a,/pfad/b.
Bemerkung
Der Datanode generiert opensearch.yml bei jedem Start selbst und übersetzt path_repo in die OpenSearch-Einstellung path.repo. opensearch.yml daher nie direkt editieren, manuelle Änderungen werden überschrieben.
Sobald ein Repository konfiguriert ist, ergänzt der Datanode die search-Rolle der Node automatisch und reserviert node_search_cache_size (Default 10gb).
Im Cluster muss das Repository-Verzeichnis auf allen Datanodes derselbe gemeinsame Speicher sein (z.B. NFS), am identischen Pfad gemountet. Ein rein lokales Verzeichnis funktioniert nur bei einem Single-Node-Datanode.
systemctl restart graylog-datanode.service
Client-Zertifikat von Graylog-Server beziehen. Der Endpunkt POST /api/ca/clientcert erwartet je nach Graylog-Version leicht unterschiedliche Argumente: certificate_lifetime existiert seit 6.3, das Einzelfeld role wurde in 7.0 durch die Liste roles ersetzt (role funktioniert dort weiterhin, gilt aber als veraltet), und password darf erst ab 7.0 null sein. Die drei folgenden Request-Bodies entsprechend passend auswählen.
graylog_url='http://localhost:9000'
graylog_admin_username='admin'
read -s graylog_admin_password
clientcert_base_path='/tmp/graylog-clientcert'
clientcert_lifetime='P30Y' # 30 years
clientcert_principal="$(hostname --fqdn)" # fqdn of datanode where cert will be stored
clientcert_role='manage_snapshots'
clientcert_path="$clientcert_base_path/$clientcert_principal"
mkdir --parents "$clientcert_path"
# Graylog 6.1
clientcert_request="$(cat <<EOF
{
"password": "",
"principal": "$clientcert_principal",
"role": "$clientcert_role"
}
EOF
)"
# Graylog 6.3
clientcert_request="$(cat <<EOF
{
"certificate_lifetime": "$clientcert_lifetime",
"password": "",
"principal": "$clientcert_principal",
"role": "$clientcert_role"
}
EOF
)"
# Graylog 7.0
clientcert_request="$(cat <<EOF
{
"certificate_lifetime": "$clientcert_lifetime",
"password": null,
"principal": "$clientcert_principal",
"roles": ["$clientcert_role"]
}
EOF
)"
clientcert_response="$(curl --silent \
--request POST "$graylog_url/api/ca/clientcert" \
--header 'Content-Type: application/json' \
--header 'X-Requested-By: cli' \
--basic --user "$graylog_admin_username:$graylog_admin_password" \
--data "$clientcert_request")"
jq --raw-output '.ca_certificate' <<<"$clientcert_response" >"$clientcert_path/ca.crt"
jq --raw-output '.private_key' <<<"$clientcert_response" >"$clientcert_path/private.key"
jq --raw-output '.certificate' <<<"$clientcert_response" >"$clientcert_path/private.crt"
Snapshot-Repository im Datanode registrieren:
datanode_url="https://localhost:9200"
snapshot_repo_path='/mnt/opensearch-snapshots'
snapshot_repo_name='opensearch-snapshots'
auth=(
--cacert "$clientcert_path/ca.crt"
--key "$clientcert_path/private.key"
--cert "$clientcert_path/private.crt"
)
# register filesystem-based snapshot repository
snapshot_repo_request="$(cat <<EOF
{
"type": "fs",
"settings": {
"location": "$snapshot_repo_path"
}
}
EOF
)"
curl --silent "${auth[@]}" \
--header 'Content-Type: application/json' \
--request PUT "$datanode_url/_snapshot/$snapshot_repo_name" \
--data "$snapshot_repo_request" | jq
# list registered snapshot repositories
curl --silent "${auth[@]}" \
--header 'Content-Type: application/json' \
--request GET "$datanode_url/_snapshot/_all" | jq
# verify the repository is reachable from all nodes
curl --silent "${auth[@]}" \
--header 'Content-Type: application/json' \
--request POST "$datanode_url/_snapshot/$snapshot_repo_name/_verify" | jq
Ein erfolgreicher Verify listet alle Nodes auf, die das Repository erreichen können. Jeder Eintrag unter nodes steht für eine Node, die lesend und schreibend auf das Repository zugreifen kann. Auf einem Single-Node-Datanode ist das genau eine Node, in einem Cluster müssen hier alle Datanodes erscheinen. Kann eine Node das Repository nicht erreichen (Pfad fehlt, fehlende Rechte, NFS nicht gemountet), liefert der Aufruf stattdessen einen Fehler.
{
"nodes": {
"2fK4KxqVQvSomutOqgVg0Q": {
"name": "localhost"
}
}
}
Der Schlüssel (hier 2fK4KxqVQvSomutOqgVg0Q) ist die interne Node-ID, name der Node-Name (node.name).
Nun kann man Snapshots anlegen. Ein Stoppen des Graylog-Servers ist dafür nicht nötig: OpenSearch-Snapshots sind Point-in-Time-Kopien der unveränderlichen Lucene-Segmente und laufen im laufenden Betrieb. Wer ein lückenloses Voll-Backup vor einer Migration anlegen will, kann die Ingestion vorher stoppen; für das Sichern einzelner, insbesondere bereits read-only gesetzter Indizes ist das unnötig. Statt aller Indizes ("indices": "*") lässt sich auch eine Teilmenge sichern, etwa "indices": "graylog_0".
# snapshot all indices
curl --silent "${auth[@]}" \
--request PUT "$datanode_url/_snapshot/$snapshot_repo_name/snapshot-1?wait_for_completion=true" \
--header 'Content-Type: application/json' \
--data '{
"indices": "*",
"ignore_unavailable": true,
"include_global_state": false
}' | jq
Manuell, also ohne SM-Policy, angelegte Snapshots werden über die OpenSearch-API gelöscht. Zuerst die Snapshots im Repository auflisten, dann den gewünschten löschen:
# list snapshots in the repository
curl --silent "${auth[@]}" \
--request GET "$datanode_url/_cat/snapshots/$snapshot_repo_name?v&s=id"
# delete one snapshot
curl --silent "${auth[@]}" \
--request DELETE "$datanode_url/_snapshot/$snapshot_repo_name/snapshot-1" | jq
Bemerkung
Da Snapshots inkrementell sind, gibt das Löschen nur jene Segmente frei, die kein anderer Snapshot im Repository mehr referenziert; gemeinsam genutzte Daten bleiben erhalten. Von einer SM-Policy verwaltete Snapshots nicht von Hand löschen, dafür ist deren Deletion-Schedule zuständig.
Statt Snapshots manuell anzulegen, übernimmt das die Snapshot-Management-Funktion (SM) von OpenSearch: ein Zeitplan erzeugt regelmässig Snapshots und entfernt alte nach Aufbewahrungsregeln.
Hier am Beispiel vom folgenden Use-Case: Das Snapshot-Repository liegt unter /mnt/opensearch-snapshots und wird von einem externen Backup-Tool täglich gesichert. Das SM-Repository ist damit die schnelle, cluster-lokale Restore-Stufe, die Langzeit-Aufbewahrung übernimmt das externe Backup-Tool. Die SM-Retention darf entsprechend kurz sein.
sm_policy_request="$(cat <<EOF
{
"description": "Daily Graylog snapshot, short-term on-cluster retention",
"creation": {
"schedule": { "cron": { "expression": "0 2 * * *", "timezone": "Europe/Zurich" } }
},
"deletion": {
"schedule": { "cron": { "expression": "30 2 * * *", "timezone": "Europe/Zurich" } },
"condition": { "max_count": 2, "min_count": 1 }
},
"snapshot_config": {
"repository": "$snapshot_repo_name",
"indices": "*",
"ignore_unavailable": "true",
"include_global_state": "false",
"date_format": "yyyy-MM-dd-HH-mm",
"timezone": "Europe/Zurich"
}
}
EOF
)"
curl --silent "${auth[@]}" \
--request PUT "$datanode_url/_plugins/_sm/policies/graylog-daily" \
--header 'Content-Type: application/json' \
--data "$sm_policy_request" | jq
Die Policy ist nach dem Anlegen aktiv. Status, letzte und nächste Ausführung lassen sich so prüfen:
curl --silent "${auth[@]}" \
--request GET "$datanode_url/_plugins/_sm/policies/graylog-daily/_explain" | jq
Aufbewahrung: Der Zeitplan erzeugt täglich um 02:00 einen Snapshot. SM kann nicht exakt einen Snapshot vorhalten: min_count muss grösser als 0 sein (Standard 1) und max_count muss grösser als min_count sein. Das Minimum ist daher max_count: 2 bei min_count: 1, es bleiben der neueste plus ein vorheriger Stand erhalten. Da Snapshots inkrementell sind, kostet der zweite Stand kaum zusätzlichen Platz. Die Versionshistorie übernimmt ohnehin das externe Backup-Tool, das /backup täglich sichert; lokal genügen diese ein bis zwei aktuellen Restore-Punkte.
Bemerkung
Der zeitliche Abstand zwischen Creation und Deletion ist für die Korrektheit unkritisch. Läuft die Snapshot-Erstellung noch, wenn die Deletion feuert (z.B. weil der erste, vollständige Snapshot länger als 30 Minuten dauert), überspringt SM den gerade entstehenden Snapshot: er wird im Zustand IN_PROGRESS herausgefiltert und nicht gegen max_count gezählt. In dieser Runde wird dann nichts gelöscht, die überzählige Sicherung räumt der nächste Deletion-Lauf weg. Es bleibt also kurzzeitig ein Stand mehr liegen, es gehen aber keine Daten verloren und es entsteht kein Konflikt.
Bemerkung
include_global_state: false ist bewusst gewählt (gilt auch für den manuellen Snapshot oben). Pro-Index-Daten und Mappings liegen ohnehin im Index-Teil des Snapshots und werden unabhängig von diesem Flag wiederhergestellt. Der globale Cluster-Status (Index-Templates, Ingest-Pipelines, Cluster-Settings) wird dagegen von Graylog beim Start neu gesetzt, und die massgebliche Graylog-Konfiguration liegt in MongoDB und wird separat gesichert. Würde man alten globalen Status mitsichern und zurückspielen, drohte ein Überschreiben aktueller Cluster-Einstellungen mit veralteten Werten. true lohnt nur bei einer Bare-Cluster-Disaster-Recovery ohne separates MongoDB-Backup.
Bemerkung
Den SM-Zeitplan so legen, dass der Snapshot abgeschlossen ist, bevor das externe Backup-Tool /backup kopiert, sonst sichert das Tool einen unfertigen Snapshot mit. Bereits abgeschlossene Snapshots im Repository bleiben dabei konsistent; lediglich ein im Moment der Kopie noch laufender Snapshot landet unvollständig im externen Backup und ist dort unbrauchbar. Das externe Tool darf /backup nur lesen, niemals selbst in das Repository schreiben.
Restore¶
Beim Restore gibt es zwei Varianten. Die Wahl hängt davon ab, ob der bestehende Index ersetzt werden soll oder die gesicherten Daten zusätzlich, parallel zu den bestehenden, bereitstehen sollen.
OpenSearch-Randbedingung: Ein Snapshot lässt sich nicht in einen bereits geöffneten Index mit gleichem Namen wiederherstellen. Ein vorhandener gleichnamiger Index muss vorher geschlossen oder gelöscht werden.
Variante A, abweichender Name (nicht-destruktiv, im laufenden Betrieb): Die gesicherten Indizes werden unter neuem Namen (hier mit Präfix restored_) zusätzlich zu den bestehenden eingespielt. Der Graylog-Server muss nicht gestoppt werden, da Graylog diese Indizes nicht verwaltet. Geeignet, um Daten zu prüfen oder einzelne Inhalte zu retten.
# restore alongside the originals under a different name; graylog-server keeps running
curl --silent "${auth[@]}" \
--request POST "$datanode_url/_snapshot/$snapshot_repo_name/snapshot-1/_restore?wait_for_completion=true" \
--header 'Content-Type: application/json' \
--data '{
"indices": "graylog_0",
"rename_pattern": "(.+)",
"rename_replacement": "restored_$1",
"include_global_state": false
}' | jq
rename_pattern ist ein regulärer Ausdruck auf die Index-Namen im Snapshot, rename_replacement der Ziel-Name ($1 referenziert die erste Capture-Gruppe). Damit Graylog die so wiederhergestellten Daten durchsuchen kann, müssten sie einem Index-Set zugeordnet und die Index-Ranges neu berechnet werden; für reines Sichten oder Extrahieren ist das nicht nötig.
Variante B, originaler Name (destruktiv, ersetzt den Index): Der bestehende Index wird durch den Snapshot-Stand ersetzt. Da Graylog diesen Index aktiv verwaltet, muss der Graylog-Server gestoppt sein, sonst drohen Inkonsistenzen bei Index-Ranges, Retention oder Rotation. Der Ablauf ist somit: Graylog-Server stoppen, alten Index löschen, Snapshot unter Originalnamen einspielen, Graylog-Server starten, Index-Ranges neu berechnen.
Warnung
Diese Variante eignet sich nur für alte, bereits rotierte Indizes, nicht für den aktiven Schreib-Index. Graylog schreibt alle neuen Nachrichten über den Alias <prefix>_deflector (z.B. graylog_deflector), der stets auf den aktuellen, höchstnummerierten Schreib-Index zeigt (z.B. graylog_5). Wird dieser Index unter seinem Originalnamen gelöscht und wiederhergestellt, zeigt der Alias ins Leere, laufende Schreibvorgänge schlagen fehl und Graylog gerät in einen inkonsistenten Zustand.
Muss der aktive Index ersetzt werden, muss vorher den Schreib-Index rotiert werden (System -> Indices -> Index-Set öffnen -> Maintenance -> Rotate active write index). Graylog legt dann einen neuen Schreib-Index an (z.B. graylog_6) und schwenkt den Deflector dorthin. Der bisherige Index (graylog_5) wird zum inaktiven, read-only gesetzten Alt-Index und damit zu einem sicheren Restore-Ziel.
Die Aliase können per curl "${auth[@]}" "https://localhost:9200/_cat/aliases?v&s=alias" geprüft werden.
systemctl stop graylog-server
# delete the existing index so the snapshot can restore under the same name
curl --silent "${auth[@]}" \
--request DELETE "$datanode_url/graylog_0" | jq
# restore under the original name
curl --silent "${auth[@]}" \
--request POST "$datanode_url/_snapshot/$snapshot_repo_name/snapshot-1/_restore?wait_for_completion=true" \
--header 'Content-Type: application/json' \
--data '{
"indices": "graylog_0",
"include_global_state": false
}' | jq
systemctl start graylog-server
Nach dem Start müssen die Index-Ranges neu berechnet werden: System -> Indices -> Index-Set öffnen -> betroffenen Index aufklappen -> Button „Recalculate index ranges“. Für ein ganzes Index-Set steht die Aktion stattdessen unter Maintenance -> Recalculate index ranges des Index-Sets.
Bemerkung
Statt den alten Index zu löschen, genügt OpenSearch auch ein Schliessen (POST /graylog_0/_close). Ein Löschen liefert aber einen sauberen, vollständigen Ersatz und ist daher vorzuziehen. Das Löschen eines Index wird durch index.blocks.write nicht blockiert, der read-only-Schreibblock alter Indizes muss dafür also nicht aufgehoben werden.
Migration zu Graylog Data Node¶
Seit Graylog 6.0 bietet Graylog die Data Node als offiziell gepflegte, in Graylog integrierte OpenSearch-Instanz an. Wer bis dahin eine eigenständige OpenSearch-Installation betreibt, kann entweder dabei bleiben (externe OpenSearch wird weiterhin supportet) oder auf die Data Node migrieren. Offiziell dokumentiert ist dafür ausschliesslich die In-Place Migration; alles andere sind pragmatische Vorgehen aus der Praxis.
- In-Place Migration (offiziell)
Der Graylog-Assistent in der Web-UI führt durch die Schritte. Daten werden dabei nicht kopiert: der Data Node übernimmt das bestehende OpenSearch-Datenverzeichnis und startet darauf weiter. Voraussetzung ist OpenSearch 1.13 oder neuer, bis einschliesslich 2.19.4; Elasticsearch-Quellen werden für In-Place nicht unterstützt. Rollback geht nur auf Cluster-Ebene, OpenSearch-Binaries und eine Dateisystem-Kopie des Datenverzeichnisses sollten bis zur Verifikation behalten werden.
Kompatibilitäts-Eckdaten laut Graylog Compatibility Matrix (Stand 2026-04):
Graylog
Unterstützte OpenSearch-Version (extern oder als Data Node)
6.1.x
1.1.x bis 2.15.x
6.2.x, 6.3.x, 7.0.x
1.1.x bis 2.19.4
Graylog 7.x unterstützt OpenSearch 3.x nicht. Vor jedem Minor-Upgrade die Matrix gegenchecken.
- Snapshot/Restore (inoffiziell)
OpenSearch-Indices werden per Snapshot-Repository (NFS, S3, o.ä.) gesichert und in den Data-Node-Cluster zurückgespielt. Die alte OpenSearch bleibt aktiv, bis bestätigt ist, dass alle Indices im Data-Node-Cluster vorhanden, abfragbar und von Graylog ansprechbar sind. Nützlich bei gleichzeitiger Host-Migration oder wenn die In-Place-Variante wegen Pfad- oder Versions-Constraints nicht greift. Graylog dokumentiert Snapshot/Restore nur als generelles OpenSearch-Backup (siehe OpenSearch Backup & Restore), nicht als offiziellen Migrationspfad zur Data Node. Lucene-Versionskompatibilität zwischen Quell- und Ziel-Cluster vor der Migration prüfen und in einer Testumgebung durchspielen.
- Side-by-Side (inoffiziell)
Ein zweites Graylog mit Data Node wird parallel aufgebaut und die Index-Sets stückweise auf den neuen Stack umgezogen. Graylog verwaltet pro Instanz nur einen Indexer-Cluster, echte zwei aktive Search-Backends unter einem Graylog gibt es nicht. Die Side-by-Side-Variante läuft damit faktisch auf zwei getrennte Graylog-Instanzen hinaus, mit entsprechend höherem Aufwand bei Log-Routing, Stream-Regeln und Forwarder-Umstellung. In der Praxis eher eine Notlösung für Umgebungen, in denen ein harter Cutover keine Option ist.
In-Place-Migration Schritt für Schritt¶
Konkrete Anleitung für die offizielle In-Place-Migration eines bestehenden,
unverschlüsselten OpenSearch-Clusters (plugins.security.disabled: true, siehe
OpenSearch) auf Graylog Data Nodes. Der Data Node übernimmt das vorhandene
OpenSearch-Datenverzeichnis weiter, Daten werden nicht kopiert. Läuft das OpenSearch
mit aktivem Security-Plugin und TLS, greift stattdessen die TLS-Secured Data Node
Migration
mit zusätzlicher JWT-Authentifizierung.
Achtung
Das Graylog-Server-Cluster muss vor der Migration vollständig auf der Zielversion laufen (In-Place offiziell ab Graylog 6.1, hier auf 7.0). Graylog 7.0 setzt MongoDB 7.0, Java 21 voraus. Der inkrementelle Upgrade-Pfad inklusive MongoDB ist im Abschnitt Upgrade beschrieben. Erst migrieren, wenn alle Server-Nodes auf derselben 7.0-Version laufen.
Voraussetzungen laut Data Node In-Place Migration:
alle Server-Nodes auf der Zielversion (siehe Upgrade)
OpenSearch zwischen 1.13 und 2.19; Elasticsearch-Quellen werden nicht unterstützt
gültige Backups von MongoDB und OpenSearch (siehe Backup und Restore)
Data Node wird auf denselben Hosts wie das bestehende OpenSearch installiert
Preflight - auf jedem OpenSearch-Node prüfen und sichern:
Freien Plattenplatz prüfen: mindestens 4 GB auf
/und 15 GB auf/var.Alle relevanten Versionen festhalten (OpenSearch, Graylog, Java, MongoDB)
Aktuellen Datenbestand sowie Index- und Shard-Zahl erfassen:
curl 'http://localhost:9200/_cat/indices?v' curl 'http://localhost:9200/_cluster/health?pretty'
MongoDB und OpenSearch sichern (siehe Backup und Restore).
Sicherstellen, dass der Paket-Mirror das
graylog-datanode-Paket in der zur Server-Version passenden Version bereitstellt.Indizes ohne Replica aufspüren - beim Stoppen eines OpenSearch-Nodes gingen deren Shards sonst verloren. Indizes mit
number_of_replicas: 0auflisten:curl --cacert "$elastic_cacert" \ --user "elastic:${ELASTIC_PASSWORD}" \ --request GET "https://$elastic_host:9200/*/_settings?expand_wildcards=all&filter_path=*.settings.index.number_of_replicas" \ --header "Content-Type: application/json" \ | jq 'to_entries[] | select(.value.settings.index.number_of_replicas == "0") | .key'
Replica-Zahl für die betroffenen Index-Sets hochsetzen (verlangt mindestens zwei Nodes; ein Single-Node-Cluster bleibt sonst gelb):
curl --silent --request PUT 'http://localhost:9200/graylog_*/_settings' \ --header 'Content-Type: application/json' \ --data '{"index": {"number_of_replicas": 1}}' curl --silent --request PUT 'http://localhost:9200/gl-system-events*/_settings' \ --header 'Content-Type: application/json' \ --data '{"index": {"number_of_replicas": 1}}'
Bemerkung
Die offizielle In-Place-Anleitung geht von einem Quell-OpenSearch mit aktivem
Security-Plugin aus und enthält daher einen JWT-Schritt (Eintrag in
opensearch-security/config.yml, signing key = base64-kodiertes
GRAYLOG_PASSWORD_SECRET), der dem Data Node die Autorisierung zum Daten-Import
gibt. Läuft das Quell-OpenSearch mit plugins.security.disabled: true (Plugin aus,
unverschlüsselt), entfällt dieser Schritt - es gibt keine
opensearch-security/config.yml und keine Auth, gegen die authentifiziert werden
müsste. Das untenstehende Runbook beschreibt diesen unverschlüsselten Fall; er ist
offiziell nicht eigens dokumentiert und sollte vorab in einer Testumgebung
durchgespielt werden. Bei aktivem Security-Plugin stattdessen der TLS-Secured Data
Node Migration
folgen.
Reihenfolge nach Data Node In-Place Migration. Vorbereitung ausserhalb der Web-UI:
Data Node auf jedem OpenSearch-Node installieren, entweder mit dem LFOps oder manuell (Graylog-Repo ist bereits eingerichtet, siehe Installation):
dnf --assumeyes install graylog-datanode dnf versionlock add graylog-datanode
/etc/graylog/datanode/datanode.confkonfigurieren. Die Anleitung nennt nuropensearch_data_location; für den Betrieb des Data Node werden ausserdempassword_secretundmongodb_urigesetzt:opensearch_data_locationauf das bestehende OpenSearch-Datenverzeichnis (path.data, im Standard/var/lib/opensearch)password_secretidentisch zumpassword_secretaus derserver.confmongodb_uriauf die vorhandene MongoDB
Im Migrations-Assistenten (Web-UI):
Assistenten öffnen: System > Cluster Configuration > Data Node Migration.
Interne Graylog-CA erzeugen (empfohlen) oder eigene CA (PEM oder PKCS#12) hochladen und die Renewal-Policy wählen (Automatic als Default, 30 Tage Zertifikatslaufzeit).
„Run directory compatibility check“ ausführen. Der Wizard prüft die OpenSearch-Version, sucht nach Nicht-Graylog-Daten und zählt die Indizes - das OpenSearch läuft dabei noch.
Data-Node-Zertifikate provisionieren („Provision Data Nodes with certificates“).
Journal-Downtime-Warnung bestätigen. Der Server muss eingehende Nachrichten puffern können, während das Search-Backend während der Migration nicht erreichbar ist.
Abschluss:
OpenSearch auf allen Nodes stoppen. Die Anleitung stoppt nur; ein zusätzliches
disableverhindert einen versehentlichen Neustart:systemctl disable --now opensearch.service
Eigentümer des Datenverzeichnisses auf den Data-Node-Service umstellen, sonst kann der Data Node die Daten nicht lesen (das Verzeichnis gehört
opensearch, der Dienst läuft alsgraylog-datanode). Die TLS-Secured-Anleitung dokumentiert den Schritt wörtlich:chown --recursive graylog-datanode:graylog-datanode /var/lib/opensearch
elasticsearch_hostsin/etc/graylog/server/server.confauf allen Server-Nodes deaktivieren, damit Graylog kein eigenständiges Search-Backend mehr sucht.Warten, bis alle Data Nodes den Status
AVAILABLEerreichen (kann einige Minuten dauern), dann den Graylog-Server neu starten:systemctl restart graylog-server.service
Den Assistenten mit „Next“ abschliessen.
Die Indizes, bei denen man die Replica-Zahl auf 1 gesetzt hat, wieder auf 0 setzen
curl --silent --request PUT 'http://localhost:9200/graylog_*/_settings' \ --header 'Content-Type: application/json' \ --data '{"index": {"number_of_replicas": 0}}'
Nach der Migration prüfen (das vom Data Node gemanagte OpenSearch läuft nun mit TLS,
deshalb über die UI statt über http://...:9200):
System > Data Nodes: alle Nodes grün.
System > Indices: alle Index-Sets vorhanden, Shard-Zahl mindestens so hoch wie vor der Migration.
System > Nodes: Verarbeitung aktiv, Journal-Auslastung sinkt wieder.
Bei
index_not_found_exception: System > Indices > Maintenance > „Cleanup & recalculate all index ranges“.graylog-serverundgraylog-datanodeperdnf versionlockgegen ungewollte Updates pinnen (MongoDB ist bereits gepinnt, siehe Installation).
Bis die Migration verifiziert ist, OpenSearch-Binaries und eine Dateisystem-Kopie des Datenverzeichnisses behalten - ein Rollback ist nur auf Cluster-Ebene möglich.
Endzustand: Der Data Node bringt sein eigenes, von Graylog gemanagtes OpenSearch mit
(/usr/share/graylog-datanode/dist); ein separates, security-deaktiviertes OpenSearch
gibt es danach nicht mehr. HTTP- und Transport-Layer laufen über automatisch
provisionierte Zertifikate (TLS), die Kommunikation zwischen Graylog-Server und
OpenSearch ist zusätzlich per JWT abgesichert (geteiltes password_secret). Die
Discovery läuft über die gemeinsame MongoDB: Der Data Node registriert sich dort, der
Graylog-Server findet ihn darüber - elasticsearch_hosts wird dann nicht mehr manuell
gepflegt. Das ist der Bruch zum bisherigen Setup mit externem OpenSearch.
Best Practices¶
Pipelines gegenüber Extractors bevorzugen. Pipelines werden pro Stream angewendet und bieten einen Simulator zum Testen; Extractors sind an einen einzelnen Input gebunden. Für neue Parsing-Logik ist die Pipeline in der Regel die flexiblere Option. Konkrete Empfehlung der Graylog-Community in der Upstream-Doku vor einer Umstellung gegenchecken.
Separate Streams für separate Retention. Security-Logs sollen oft länger aufbewahrt werden als z.B. Apache-Access-Logs. Separate Index Sets mit eigener Retention-Policy konfigurieren und entsprechende Streams darauf zeigen lassen.
OpenSearch-Heap bei 50 % RAM, maximal 31 GB. JVM kann darüber hinaus wegen Compressed-OOPs keinen effektiven Nutzen mehr ziehen. Restlicher RAM geht in den Filesystem-Cache - entscheidend für die OpenSearch-Leseperformance.
Sidecars für Logsammlung statt direkter Syslog-Forwarding-Konfigurationen auf den Hosts. Beats-basierte Collectors sind robuster gegen Netzwerkausfälle und können lokal buffern, wenn Graylog nicht erreichbar ist.
TLS auf allen Inputs. Besonders GELF-TCP und Beats sollten mit TLS verschlüsselt werden; der Beats-Input in Graylog unterstützt Client-Zertifikat-Authentifizierung.
Graylog-Admin-Account absichern. Nach der Erstinstallation einen dedizierten Admin-Account anlegen (z.B. via LDAP-Anbindung oder OIDC - letzteres in der OSS- Version nur eingeschränkt, siehe Feature-Liste oben) und den hardcodierten
admin-Account viaroot_password_sha2inserver.confdeaktivieren.Index-Rotation überwachen. Graylog rotiert Indices nach Zeit, Grösse oder Message-Anzahl.
Indices & Index Setsprüft, ob alle Indices grün sind; rote oder gelbe Shards bedeuten Datenverlust-Risiko.
Troubleshooting¶
- „415 Unsupported Media Type“ bei Verwendung des APIs
Der Server lehnt die Anfrage ab, weil der Request-Body in einem Format vorliegt, das die angefragte Ressource für diese Methode nicht unterstützt. Meist fehlt der Header
Content-Type: application/json.OpenSearchException[OpenSearch exception [type=illegal_argument_exception, reason=mapper [latency] cannot be changed from type [float] to [long]]]Die Mappings (OpenSearch / Elasticsearch Felder und deren Typ) werden dynamisch anhand der ersten Nachricht im jeweiligen Index bestimmt. Dies geschieht jedes Mal, wenn ein neuer Index begonnen wird, also auch, wenn der Index rotiert wurde. Das heisst, wenn der erste Eintrag
latency: 0ist, nimmt OpenSearch fälschlicherweise an, dass es sich um ein long handelt. Wenn man nun einen float-Wert schickt, wird dieser zwar im Graylog und in OpenSearch als float angezeigt, intern aber trotzdem als long behandelt (siehe https://xeraa.net/blog/2020_elasticsearch-coerce-float-to-integer-or-long/).Um dieses Problem zu beheben, kann man den Typ für das Feld
latencymanuell setzen:# show current mapping curl 'localhost:9200/lfops-default_4/_mappings?pretty' | grep latency -A1 # "latency" : { # "type" : "float" # create custom mapping, see https://go2docs.graylog.org/current/setting_up_graylog/elasticsearch.htm#CustomIndexMappings cat > lfops-latency-float.json << 'EOF' { "template": "lfops-default_*", "order": 10, "mappings": { "properties": { "latency": { "type": "float" } } } } EOF curl --request PUT \ --data @'lfops-latency-float.json' \ --header 'Content-Type: application/json' \ 'http://localhost:9200/_template/lfops-latency-float?pretty' # show current templates curl 'http://localhost:9200/_template?pretty'
Anschliessend muss im Graylog unter Indices der betroffene Index ausgewählt und über „Maintenance > Rotate active write index“ der aktuelle Index neu generiert werden.
- Datanodes werden bei der Installation nicht gefunden?
Prüfen, ob sie in der MongoDB eingetragen sind
mongosh use graylog db.datanodes.find()
- Zertifikats-Probleme mit einem Datanode Cluster?
Prüfen, ob sich alle Datanodes untereinander via DNS erreichen können (auch Firewall beachten). Besonders wichtig: Auch die
/etc/hostsprüfen -127.0.0.1bzw.::1dürfen ausschliesslich auf dielocalhost-Varianten zeigen. Falls zusätzlich der eigentliche Hostname angegeben wird, schlägt das Ausstellen der Zertifikate mit dem Fehlercertificate_unknownfehl.WARN [MongodbNodesProvider] Could not get MongodbNodes, returning fallback. Reason: Command execution failed on MongoDB server with error 13 (Unauthorized): 'not authorized on admin to execute command { replSetGetStatus: 1, $db: "admin", ...Seit Graylog 7.1 braucht der MongoDB-User für Graylog zusätzlich die
clusterMonitor-Rolle (für die „MongoDB Nodes“-Sektion unter System > Cluster Configuration). Manuell permongosh:mongosh use graylog db.grantRolesToUser("graylog", [{ role: "clusterMonitor", db: "admin" }])
Oder per LFOps-Rolle
linuxfabrik.lfops.mongodb:mongodb__users__group_var: - username: 'graylog' password: 'linuxfabrik' database: 'graylog' roles: # clusterMonitor is required for graylog 7.1+, for the "MongoDB Nodes" section in the System > Cluster Configuration - db: 'admin' role: 'clusterMonitor' - db: 'graylog' role: 'readWrite' - db: 'graylog' role: 'dbAdmin' state: 'present'