TL;DR
- cgoに気をつける
- ルートCA証明書に気をつける
- timezoneに気をつける
- [2020/08/12追記] Go 1.15ではtimezoneはtzdataをインポートするだけで良い
はじめに
DockerはDockerfileのFROM
にscratchを指定できます。scratch
はその名の通り何も入っていないためこれをベースにイメージを作れば非常に軽量になります。基本的にマルチステージビルドを利用してビルドします。
Goはlibc
に非依存のため静的リンクをするとバイナリ一つを持ち歩くだけで済みます。ですがいくつか気をつけないと困ることがあるためそれをまとめます。
cgo
唐突ですが、先ほどGoはlibc
に非依存だと言いましたがあれは嘘です。Go1.4からnet
パッケージなどでおそらく速度向上のためデフォルトではlibc
に依存しています。そのため、利用可能な環境ではcgo
を利用してdynamic linkを行なっているようです。
root@d466ec4c1889:/go/src# cat main.go
package main
import "net/http"
func main() {
http.ListenAndServe(":9090", nil)
}
root@d466ec4c1889:/go/src# go build main.go
root@d466ec4c1889:/go/src# ldd main
linux-vdso.so.1 (0x00007ffca11f5000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f5c65afe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5c6575f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5c65d1b000)
root@d466ec4c1889:/go/src#
しかしこれではscratch
ベースのイメージに持ってきた際に動かなくなり非常に不便です。解決策としてcgo
を切ります。go build
等を利用してビルドすると思いますが、その際に
CGO_ENABLED=0
の環境変数を渡すとcgo
が無効化されてビルドされます。
Dockerのマルチステージビルドを利用してビルドした場合は
ARG CGO_ENABLED=0
をgo build
の前に書いておけば大丈夫です。
ルートCA証明書
HTTPSでサーバに対して通信する際、サーバ側の証明書が正当なものか検量するために各システムにはルートCA証明書が保存されています。しかし、scratch
にはそれがありません。安全にHTTPS通信を行うためにはこれが必要です。
Goがどこに保存されているルートCA証明書を参照するかはcrypto/x509/root_linux.goに保存されています。Linuxに関してはこれです。
このファイルを別のイメージから持って来る必要があります。
COPY --from=golang:1.12 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
DockerのCOPY --from
はステージだけでなくイメージも利用できます。ステージではなくイメージを利用しているのはBuildkit等を利用した際並列化がしやすそうであるからで、golang
イメージを利用しているのはマルチステージでビルドするためのイメージとして利用している可能性が高いからです。デフォルトで/etc/ssl/certs
を持っていれば他のイメージでも大丈夫です。
タイムゾーン
Goがデフォルトのタイムゾーンを決定する仕組みについてはGolangのローカルのタイムゾーンが決まる仕組みと指定方法という記事がよく纏まっていてわかりやすかったです。
簡単に説明するとUNIX環境においては、TZ
環境変数で指定がなければ/etc/localtime
を読みに行き、これが失敗すればUTC
。UTC以外を取得した場合はtime/zoneinfo_unix.goのzoneSources
のリスト内のデータを利用してゾーンネームとオフセットの変換を行なっているようです。(Asia/Tokyo→+09:00など)
Dockerにおいては/etc/localtime
はなくともTZ
環境変数を指定すれば問題ありませんがzoneSources
がないとデータベースを使うときなどに面倒なことになりがちです。
これも先ほどと同じように
COPY --from=golang:1.12 /usr/share/zoneinfo /usr/share/zoneinfo
でコピーできます。また、別の解決策としてファイルをコピーせずにtime.Local
に直接コードから指定することもできます
Go 1.15の場合[2020/08/12追記]
日本時間2020/08/12でGo 1.15がリリースされました。Go 1.15ではtime/tzdata
パッケージが追加されました。Go 1.15以降では
import _ "time/tzdata"
のようにインポートするとシステム上に存在しなくてもtzdataパッケージを参照してくれます。
参照: https://golang.org/doc/go1.15#time/tzdata
alpineの場合
scratch
ほどでないもののalpine
はかなり軽量なイメージです。同じくデフォルトだと存在しませんが、パッケージマネージャがあるので
apk add --update --no-cache ca-certificates tzdata
このように追加できます。
まとめ
scratch
でイメージを作る際に私が苦労した点と解決策をまとめました。
他にも何かあれば追記していこうと思います。scratch
とGoで快適なDockerライフを!