Docker Timezones
Vor einiger Zeit hatte ich den Fall, dass innerhalb eines Docker-Containers die falsche Zeitzone eingestellt war. Das hat damit zu tun, dass Docker Containern eine eigene “interne” Zeitzone haben und nicht die des Host-OS nehmen.
> date
Mon Jan 11 19:22:38 CET 2016
Das Kommando im Container ergibt aber:
> docker run --rm busybox date
Mon Jan 11 18:22:54 UTC 2016
Wie man sieht, ist der Unterschied eine Stunde, im Container ist anscheinend UTC als Zeitzone Standard konfiguriert (Coordinated Universal Time). Auf dem Host jedoch CET (Central European Time) als Zeitzone.
In Linux-Containern lässt sich das relativ leicht beheben indem man die Datei /etc/localtime
aus dem Host in den Container mounted. Das gleicht die Zeitzone des Containers an die des Hosts an. Da der Container nicht auf die Datei schreiben können soll, fügt man :ro
hinter den Aufruf an: -v /etc/localtime:/etc/localtime:ro
. (Das ro
steht für read-only)
Der Aufruf wäre dann für das obige Beispiel:
> docker run --rm -v /etc/localtime:/etc/localtime:ro busybox date
Mon Jan 11 19:23:08 CET 2016
Siehe hierzu auch: https://github.com/docker/docker/issues/3359
Das ganze wurde noch etwas vertrackter, da innerhalb des Containers dann eine JVM immernoch die falsche Zeit angezeigt hat.
JVM Timezones
Der Container hatte zwar die richtige Zeitzone, aber innerhalb der JVM wurde nicht die richtige Zeitzone aufgelöst.
Oracle hatte hierzu wenig hilfreiche Information https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/time-zone.html
Der offizielle Lösungsweg ist, ein JVM-Argument user.timezone
mitzugeben und dort den richtigen Eintrag anzugeben. Beispielsweise für Deutschland: Europe/Berlin
:
Es gibt jedoch auch einen einfacheren Weg, denn die Umgebungsvariable TZ wird ebenfalls ausgewertet. Diese kann einfach beim Start eines Containers mitgegegeben werden:
docker run -e TZ="Europe/Berlin" ...
Das hat für unseren Anwendungsfall ausgereicht um innerhalb der JVM die korrekte Default TimeZone zu erhalten.
Selbst ausprobieren
In einem Ordner folgenden Inhalt in eine Datei TZ.java kopieren:
public class TZ {
public static void main(String[] args) {
System.out.println("user.timezone: " + System.getProperty("user.timezone"));
System.out.println("JVM: " + java.util.TimeZone.getDefault().getID());
}
}
Keine Zeitzone setzen
Starten wir einen Docker-Container und rufen dann unser kleines Programm auf:
> docker run --rm -v /etc/localtime:/etc/localtime:ro -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java TZ && rm TZ.class"
JVM: Etc/UTC
Wie man sieht ist der Container ist auf UTC gesetzt.
Zeitzone über Environment-Variable TZ setzen
Setzen wir eine Zeitzone mit -e TZ=Europe/Berlin
> docker run --rm -v /etc/localtime:/etc/localtime:ro -e TZ=Europe/Berlin -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java TZ && rm TZ.class"
JVM: Europe/Berlin
Das Ergebnis ist wie gewünscht Europe/Berlin
Setzen wir eine nicht existierend Zeitzone als TZ:
> docker run --rm -v /etc/localtime:/etc/localtime:ro -e TZ=FUU -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java TZ && rm TZ.class"
JVM: GMT
user.timezone
Nutzen wir das Property user.timezone erhalten wir folgendes Ergebnis
> docker run --rm -v /etc/localtime:/etc/localtime:ro -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java -Duser.timezone=Europe/Berlin TZ && rm TZ.class"
JVM: Europe/Berlin
Interessanterweise ist es egal ob man einen ungültigen TZ-Wert mitgibt, wie man im nächsten Abschnitt sieht.
user.timezone und ungültiger TZ-Wert
> docker run --rm -v /etc/localtime:/etc/localtime:ro -e TZ=fuu -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java -Duser.timezone=Europe/Berlin TZ && rm TZ.class"
JVM: Europe/Berlin
TZ und user.timezone gleichzeitig? user.timezone gewinnt
Setzt man mit -Duser.timezone
eine unterschiedliche Zeitzone, so hat TZ keinen Effekt.
> docker run --rm -v /etc/localtime:/etc/localtime:ro -e TZ=Europe/London -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java -Duser.timezone=Europe/Berlin TZ && rm TZ.class"
JVM: Europe/Berlin
Ungültige Parameter für user.timezone
Setzt man einen ungültigen Parameter, so wird GMT genutzt, obwohl ein gültiger Wert für TZ gesetzt wurde.
> docker run --rm -v /etc/localtime:/etc/localtime:ro -e TZ=Europe/London -v $(pwd):/tz -it openjdk:8-jdk /bin/sh -c "cd /tz && javac TZ.java && java -Duser.timezone=Fuu TZ && rm TZ.class"
JVM: GMT
Fazit
Die Grundeinstellung für Container ist UTC. Die Zeitzone kann man durch Umgebungsvariablen und durch Properties ändern.
Wenn user.timezone gesetzt ist, wird es auch ausgewertet. Das heißt: setzt man die Umgebungsvariable TZ und user.timezone, so hat TZ keinen Einfluss.
Wenn user.timezone fehlerhaft ist, wird das als GMT interpretiert.
Wichtige Empfehlung / Hinweis
Das hier ist eine Untersuchung, wie man im Container die Zeitzone korrekt setzen kann und wie sich das im Zusammenspiel mit der JVM verhält.
- Ich empfehle UTC zu nutzen.* - das muss ich hier nochmals wiederholen. Hintergründe gibt es unter anderem in diesem Artikel: http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html