今回は、TimeZoneを利用するコードを書いていたときに、Dockerコンテナ(Alpine Linux)上で`Asia/Tokyo’のローケーションが取得できなかったので、その理由や対処方法について紹介したいと思います。
目次
Alpine Linuxとは
Alpine Linuxは、軽量なLinuxディストリビューションの一つです。軽量なので、Dockerのコンテナサイズを小さくしたい場合などによく利用します。
timezoneを見るコード
timezoneを使うサンプル
package main
import (
"log"
"time"
)
func main() {
loc,err := time.LoadLocation("Asia/Tokyo")
if err != nil {
log.Fatal("%w", err)
}
log.Printf("%v", loc)
}
このサンプルをmacなどで実行すると下記のような形でローケーションが取得できています。
$ 2021/01/19 23:26:18 Asia/Tokyo
Dockerfile
FROM golang:1.15 AS builder
WORKDIR /go/src/
ENV GOPATH="/go" \
GO111MODULE="on"
ADD main.go .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o sample main.go
FROM alpine:latest
COPY --from=builder /go/src/sample .
CMD ["./sample"]
# build
$ docker build -t time-zone-sample .
# run
$ docker run time-zone-sample 2021/01/19 14:28:07 erro %wunknown time zone Asia/Tokyo
このような形でローケーションがわからないというエラーになります。
Filesystem Hierarchy Standard(FHS)を思い出してみる
LinuxなどのUnix系OSでは、主なファイル構造が定義されているのです。そのあたりから時間関連に対応するものは、下記のようなものがあります。
- /etc/localtime
- /usr/share/zoneinfo/
/etc/localtime
というタイムゾーンを設定するファイルです。私が普段利用しているmacOSでは、下記のようになっています。
$ ls -al /etc/localtime
lrwxr-xr-x 1 root wheel 36 12 16 02:24 /etc/localtime@ -> /var/db/timezone/zoneinfo/Asia/Tokyo
alpine linuxの実行結果です。
$ docker run -it --rm alpine /bin/ash
# ls -al /etc/localtime
ls: /etc/localtime: No such file or directory
/usr/share/zoneinfo/
ディレクトリには、各タイムゾーンに対応したファイルが格納されています。私が普段利用しているmacOSでは、下記のようになっています。
$ ls /usr/share/zoneinfo/
/usr/share/zoneinfo/@
$ ls /usr/share/zoneinfo/Asia/Tokyo
alpine linuxの実行結果です。 こちらも存在しないです。
$ docker run -it --rm alpine /bin/ash
# ls -al /usr/share/zoneinfo
ls: /usr/share/zoneinfo: No such file or directory
LoadLocation()の実装は、下記のようになっています。UTC, Localのタイムゾーンは、取得できるが、ZoneInfoから取得するようになっています。
// LoadLocation returns the Location with the given name.
//
// If the name is "" or "UTC", LoadLocation returns UTC.
// If the name is "Local", LoadLocation returns Local.
//
// Otherwise, the name is taken to be a location name corresponding to a file
// in the IANA Time Zone database, such as "America/New_York".
//
// 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.
func LoadLocation(name string) (*Location, error) {
if name == "" || name == "UTC" {
return UTC, nil
}
if name == "Local" {
return Local, nil
}
if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
// No valid IANA Time Zone name contains a single dot,
// much less dot dot. Likewise, none begin with a slash.
return nil, errLocation
}
zoneinfoOnce.Do(func() {
env, _ := syscall.Getenv("ZONEINFO")
zoneinfo = &env
})
var firstErr error
if *zoneinfo != "" {
if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
return z, nil
}
firstErr = err
} else if err != syscall.ENOENT {
firstErr = err
}
}
if z, err := loadLocation(name, zoneSources); err == nil {
return z, nil
} else if firstErr == nil {
firstErr = err
}
return nil, firstErr
}
対策
timezoneが存在しないので、インストールしてあげるとうい方法と、UTCであるということ日本時間がUTCから+9時間という性質を使ってあげるという2つの方法があります。
timezoneをインストールする方法
dockerのマルチステージビルドを使っている場合は、下記のようにビルドステージのゾーンファイルをコピーすることで解決できます。
FROM golang:1.15 AS builder
WORKDIR /go/src/
ENV GOPATH="/go" \
GO111MODULE="on"
ADD main.go .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o sample main.go
FROM alpine:latest
COPY --from=builder /go/src/sample .
COPY --from=builder /usr/share/zoneinfo/Asia/Tokyo /usr/share/zoneinfo/Asia/Tokyo
CMD ["./sample"]
UTCであるということ日本時間がUTCから+9時間する
下記のようにLocalのロケールを作成し、Azia/Tokyo
のロケールを利用しようといる部分のロケールを’Local’からとるようにする。
package main
import (
"time"
)
func main() {
time.Local = time.FixedZone("Local", 9*60*60)
loc, _ := time.LoadLocation("Local")
}
まとめ
Azia/Tokyo
タイムゾーンがunknowになった理由などをFHSと紐付けながら、問題に対処してみました。お疲れ様でした。