はじめに
この記事は Go7 Advent Calendar 2019 の 22 日目の記事です。 最近GoをDockerに乗せて運用することが多いのですが、Goをクロスコンパイルする方法をググってると、結構みんな違うこと言っててどれが正しいのか疑問に思ってたのでちゃんと調べてみました。
結論
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build "-ldflags=-s -w -buildid=" -trimpath -o server
※ CGOを使ってない場合のみ可
※ GOOS/GOARCHは適宜設定。対応表がこちらにまとまってます
※ “ldflags …” と -trimpath はなくても全く問題ありません
フラグの説明
-ldflags
内部的に使うgo tool link
へのフラグ(一覧)
- ”-ldflags=-s -w”: バイナリサイズ小さくなる
$ go build main.go
$ wc -c <main
1109624
$ go build "-ldflags=-s -w" main.go
$ wc -c <main
851576
-trimpath
バイナリから全てのファイルシステムパスを削除する(v1.13以降)
$ go build main.go
$ ./main
goroutine 1 [running]:
main.main()
/Users/tomokatsuiguchi/Desktop/main.go:12 +0xaf
# ↑どこでビルドしたのか丸見え
$ go build -trimpath "-ldflags=-buildid=" main.go
$ ./main
goroutine 1 [running]:
main.main()
command-line-arguments/main.go:12 +0xb8
# ↑ファイルパスが無くなった!
”-ldflags=-buildid=” を足すと完全に再現性のあるバイナリが生成できる https://github.com/golang/go/issues/34186#issuecomment-529390306
Dockerに乗せる
スタティックバイナリ生成する理由として、DockerImageのサイズを小さくしたいからというのが多いと思います。
FROM scratch
COPY --from=golang:1.13 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=golang:1.13 /usr/share/zoneinfo /usr/share/zoneinfo
COPY server /app/
CMD ["/app/server"]
ファイルサイズを小さくするためにscratchで作ってます。
あと最低限必要な、ssl通信するときに必要なルートCA証明書とtimezone情報を別imageから持ってきてます。
まとめ
ldflagsに渡せるフラグはここに紹介したもの以外にも、ビルド時に変数に値をセットできる -ldflags=-X
とかあって便利そうなので今度使ってみたい。
あと、CGO使うときのstaticバイナリ生成時のフラグは結構ややこしそうなのでこの万能フラグ追加されてほしい…
余談
不要なフラグ
- -a -installsuffix cgo -tags netgo
ほとんどのpackageはprebuildされてるみたいなので、
CGO_ENABLED=0
とだけ書いておけばわざわざ指定する必要ないみたいですね。特に -a は強制リビルドするのでめっちゃ遅くなります。ググってると、
netgo
を使っているものがヒットしたのですが、 netgoも不要ぽいIn case of Linux/musl (i.e. Alpine), for example, there is probably no need to use osusergo or netgo tags.
https://github.com/golang/go/issues/26492#issuecomment-407336850
- -extldflags “-static”
これもcgo使ってないからいらない気がするんですが、裏付けるソースが見つけられなかった…
upx使ったらもっとファイルサイズ小さくなります
$ go build "-ldflags=-s -w" main.go
$ wc -c < main
1429360
$ upx main
$ wc -c < main
524304