コンテナのzoneinfoとGoのLocationについて
2018-08-30 / Docker
以前書いたAlpine Linuxで時刻をJSTに設定する(Dockerfile)で、コンテナ内の時刻をJSTとして扱えるようにした。
しかし、以下が気になった。
- コンテナのイメージ作成においてタイムゾーンを変更するような記述を見かけたことがなかった
- もしかしてアンチパターンとかではないかと気になった
- コンテナのタイムゾーンを変更しなくても、アプリ内でJSTとして扱うことはできるので、不要なセットアップではないか
ということでアプリケーション(Go)の方で対処できないか、検証してみた。
結論
結果、以下のような挙動となった。
time.LoadLocation
はコンテナの中に該当するzoneinfoがないと失敗する- Alpine Linuxのデフォルトでは
Asia/Tokyo
などは存在しないため確実に失敗する /etc/localtime
が/usr/share/zoneinfo/Asia/Tokyo
などで上書きされていても、/usr/share/zoneinfo/Asia/Tokyo
自体がないとやはり失敗する
- Alpine Linuxのデフォルトでは
time.FixedLocation
は直接time.Location
を生成するためzoneinfoの有無の影響を受けない
軽量コンテナを使用する際にはUTC
を前提とするか、time.FixedLocation
を使用した方が良さそう。
もしJST
をデフォルトとしたコンテナを作成したかったら、以下のようなDockerfileにすべき。
FROM alpine:latest
# インストールしたtzdataは削除しない
RUN apk --no-cache add tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
以下、検証した内容。
Goのプログラム
以下のように4回時刻を出力するようにした。
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
fmt.Println(t)
jst, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(t.In(jst))
}
jst = time.FixedZone("JST", 9*60*60)
fmt.Println(t.In(jst))
time.Local = jst
fmt.Println(time.Now())
}
表示は以下のようになると予想していた。
time.Now
ではコンテナのタイムゾーンに基づいた時刻表示t.In(jst)
ではJSTの時刻表示がされるtime.LoadLocation
でerrが起きるとは思ってなかった
- 最後の
time.Now
ではJSTの時刻が表示される
バイナリは以下のようにしてtime-test
として出力しておく。
GOOS=linux GOARCH=amd64 go build -o time-test
検証1: zoneinfoを特に設定しない
まずはコンテナを作っとく。Dockerfileは以下の通り。
FROM alpine:latest
COPY ["time-test", "/"]
CMD [ "/time-test" ]
docker buildして、jst-time-test1というタグにしておく。
docker build -t jst-time-test1 -f Dockerfile1 .
docker runで実行する。
$ docker run --rm jst-time-test1
2018-08-30 00:36:56.4097601 +0000 UTC m=+0.000328601
open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directory
2018-08-30 09:36:56.4097601 +0900 JST
2018-08-30 09:36:56.4098805 +0900 JST m=+0.000448301
最初はUTCが出力され、最後はJSTが出力された。しかしtime.LoadLocation
ではエラーが出力された。
open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directory
と表示されるのは、time.LoadLocation
の説明にあるとおりで、
The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip.
最終的に$GOROOT/lib/time/zoneinfo.zip
を参照するということなので、システムのzoneinfoもないし、$GOROOT/lib/time/zoneinfo.zip
もない(GoのSDKは存在しない)ためエラーとなった様子。
検証2: JSTをローカルタイムに設定しzoneinfoを削除した状態
今度はAlpine Linuxで時刻をJSTに設定する(Dockerfile)で設定した環境で、dateコマンドだとJSTが表示される状態になっている。Dockerfileは以下の通り。
FROM alpine:latest
RUN apk --no-cache add tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
apk del tzdata
COPY ["time-test", "/"]
CMD [ "/time-test" ]
docker buildする。
docker build -t jst-time-test2 -f Dockerfile2 .
実行する。
$ docker run --rm jst-time-test2
2018-08-30 09:37:09.7907743 +0900 JST m=+0.000254901
open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directory
2018-08-30 09:37:09.7907743 +0900 JST
2018-08-30 09:37:09.7908918 +0900 JST m=+0.000372901
最初はJSTが出力され、最後はJSTが出力された。しかしtime.LoadLocation
ではエラーが出力された。
このコンテナでは/etc/localtime
は上書きされているが、zoneinfo(/usr/share/zoneinfo/Asia/Tokyo
)がないから、やはりエラーとなった様子。
検証3: JSTをローカルタイムに設定しzoneinfoを残した状態
検証2の結果から、Dockerfileを以下のように修正した。
FROM alpine:latest
# 前回実施していた apk del tzdata を消去
RUN apk --no-cache add tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
COPY ["time-test", "/"]
CMD [ "/time-test" ]
docker buildする。
docker build -t jst-time-test3 -f Dockerfile3 .
実行してみる。
$ docker run --rm jst-time-test3
2018-08-30 09:37:15.3019175 +0900 JST m=+0.000325101
2018-08-30 09:37:15.3019175 +0900 JST
2018-08-30 09:37:15.3019175 +0900 JST
2018-08-30 09:37:15.3020498 +0900 JST m=+0.000457301
今度はうまくいった!やはり参照するzoneinfoがなかったからエラーになっていたのだろう。
time.FixedLocation
を使用した場合にはすべて問題なく実行できたのも確認できた。
終わりに
今回検証した辺りのzoneinfo(tzdata)について詳しくなかったので、勉強になった。
とりあえず今後は
package main
import "time"
func init() {
time.Local = time.FixedZone("JST", 9*60*60)
}
としておくようにしよう。