Logging von Docker-Containern mit journald
Oft etwas vernachlässigt, bietet das Logging eine Möglichkeit, das diffuse Bild einer Container-Applikation aufzuklaren. Der generische Ansatz von Docker hilft dabei unterschiedlichsten Applikationen gerecht zu werden. Nach einer kurzen Einführung in das Docker-Logging soll dieser Artikel eine Anbindung an journald
erläutern.
Starten der Demo-Applikation
Um ein Gefühl für Logging mit Docker zu erhalten, benötigen wir zunächst einen Applikationscontainer. Ein nginx, der als Webserver fungiert und im schlanken Alpine-Gewand daherkommt, erfüllt diesen Zweck hervorragend. Auf einem Linux-Host starten wir also den Container ...
$ docker container run -p 80:80 -d --name=web nginx:1.15-alpine
docker container logs
Anschließend rufen wir im Browser die Demo-Seite ein paar mal auf, da nginx jeden Aufruf protokolliert. Die Logs des Containers rufen wir über den Namen oder die ID ab. In unserem Fall mit docker container logs web
. Zusätzlich gibt es noch eine Handvoll Flags für den Befehl, um beispielsweise genauere Timesstamps zu erhalten oder die Ergebnismenge einzugrenzen. Dies ist die Ausgabe der einfachen Befehlsform:
127.0.0.1 - - [21/Jul/2017:17:04:09 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"
127.0.0.1 - - [21/Jul/2017:17:04:10 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"
127.0.0.1 - - [21/Jul/2017:17:04:11 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"
Docker sammelt grundsätzlich alles, was die Applikation im Container in stdout
und stderr
schreibt. Im offiziellen Docker-Image des nginx linken die Standard-Logdateien „access.log“ und „error.log“ nach /dev/stdout
und /dev/stderr
, wie dem Dockerfile zu entnehmen ist. Ein Hand-Anlegen ist also nicht notwendig. Doch schauen wir uns den Container mit docker container inspect web
doch mal genauer an:
[
{
// [...]
"LogPath": "/var/lib/docker/containers/0c6f45-gekuerzt-json.log",
// [...]
"HostConfig": {
// [...]
"LogConfig": {
"Type": "json-file",
"Config": {}
},
},
// [...]
}
]
Unter dem angegebenen LogPath
werden die von Docker gesammelten Logs auf die Festplatte geschrieben. Zudem wird angezeigt, welcher Logging Driver für den verwendeten Container im Einsatz ist. Standardmäßig ist das json-file
. Ruft man diese mit cat /var/lib/docker/containers/0c6f45-gekuerzt-json.log
direkt auf, sieht das formatiert so aus:
{
"log": "127.0.0.1 - - [21/Jul/2017:17:04:10 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36\" \"-\"\n",
"stream": "stdout",
"time": "2017-07-21T17:04:09.625609119Z"
}
{
"log": "127.0.0.1 - - [21/Jul/2017:17:04:11 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36\" \"-\"\n",
"stream": "stdout",
"time": "2017-07-21T17:04:10.591183632Z"
}
{
"log": "127.0.0.1 - - [21/Jul/2017:17:04:12 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36\" \"-\"\n",
"stream": "stdout",
"time": "2017-07-21T17:04:11.134125994Z"
}
Wir erhalten einen deutlich tieferen Blick in Dockers Logging-Mechanismus und sehen zusätzlich noch aus welchem stream
(stdout/stderr) der Log-Eintrag stammt und erhalten nun auch genaueren Zeitstempel. nginx schreibt übrigens beim Aufruf eines nicht vorhandenen Pfads, also bei einem Fehler 404, in stderr
.
Logging Driver
Docker bringt von Hause eine Vielzahl von Logging Drivers mit. Neben dem Einsatz einer JSON-Datei können Logs beispielsweise zum syslog
Daemon des Host-Systems übergeben werden. Das Graylog Extended Log Format, kurz gelf
, kann gewählt werden, um die Log-Einträge an einen Graylog-Server oder Logstash zu übertragen. Produkte der bekannten Cloud-Provider sind natürlich ebenfalls nutzbar.
Ich möchte mich jedoch zunächst mit dem Treiber für journald
beschäftigen. journald
kommt zusammen mit dem Init-System systemd
und ist bei diversen Linux Distributionen vorinstalliert. Als dedizierter Logging-Dienst beherrscht er unter anderem Logrotation, was json-file
fehlt.
Die Zuweisung eines Logging Drivers kann auf vielen verschiedenen Ebenen stattfinden.
# Definition am Container
$ docker container run --log-driver journald --log-opt env=SOME_ENV_VAR [...]
# Definition am Service
$ docker service create --log-driver journald --log-opt env=SOME_ENV_VAR [...]
# Definition am Service im Docker Compose File
$ cat docker-compose.yml
version: "3"
services:
web:
image: nginx:1.12-alpine
ports:
- "80:80"
logging:
driver: "journald"
options:
env: "SOME_ENV_VAR"
An den Beispielen sieht man, dass je nach Log-Treiber auch noch Optionen übergeben werden können, wie Zugangsdaten zu einem externen Dienst. Welche das sind, erfährt man in der ausführlichen Dokumentation.
Logging im Docker Daemon
Neben den oben gezeigten Möglichkeiten zur Definition eines Logging-Treibers auf sehr kleinteiliger Ebene, können wir auch einen generellen Treiber im Docker Daemon bestimmen, der dann für die gestarteten Container genutzt wird. Laut Dokumentation wird nach dieser Config-Datei geschaut: /etc/docker/daemon.json
. Unter Debian kann ich das nicht bestätigen. Hier ist noch ein zusätzlicher Kniff notwendig, aber der folgt nach dem Inhalt der JSON-Datei.
$ cat > /etc/docker/daemon.json << EOF
{
"log-driver": "journald"
}
EOF
Unter Debian, und ich vermute auch Ubuntu, müssen wir dem Docker Daemon noch klarmachen, dass er die Config-Datei beim Start hinzuziehen soll. Wir erweitern daher das Docker-Profil in systemd
und starten den Dienst anschließend neu.
$ mkdir -p /etc/systemd/system/docker.service.d
$ cat > /etc/systemd/system/docker.service.d/dockerd.conf << EOF
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --config-file /etc/docker/daemon.json
EOF
$ systemctl daemon-reload
$ service docker restart
In der Ausgabe von docker info
taucht nun Folgendes auf: Logging Driver: journald
Verwendung von journald
Docker ermöglicht nun weiterhin die Nutzung von docker container logs web
, da Docker mit journald
umzugehen weiß, allerdings lassen sich die Logs nun auch mit dem journalctl
abrufen. Beispielsweise so:
$ journalctl CONTAINER_NAME=webserver
Detailliert auf journald
einzugehen, würde an dieser Stelle zu weit führen, weshalb ich auf die Dokumentation von journald und journalctl und den Eintrag zum Docker Logging Driver verweisen möchte.
Fazit
Zusammen mit den Healthchecks, die Aufschluss über den Gesundheitszustand unserer App geben und dem Monitoring, also dem Sammeln von Metriken, z. B. mit Prometheus, stellt das Logging ein essentielles Werkzeug zur Überprüfung dar. journald
ist dabei nur eine von vielen verschiedenen Möglichkeiten, die Log-Einträge der Applikationscontainer zu sammeln. Gemessen am verwendeten Standard Logging Driver json-file
, der als Basis-Einstellung zu verstehen ist, hat sich journald
allerdings im Linux-Umfeld einen Namen gemacht und findet sich daher in diversen Linux Distros, wo es an die eigenen Anforderungen, wie Logrotation und Persistenz, angepasst werden kann. Es lohnt sich daher dem Thema Logging etwas mehr Aufmerksamkeit zu schenken.
Bild von Patryk Grądys