Docker Timezone und Java

Benjamin Herbert bio photo By Benjamin Herbert

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