Docker×Goのマルチステージビルドで必要になるcgoのオプションについて
背景
- Dockerfileでマルチステージビルドの構築をしていたが、DockerImageのビルドに成功するも、docker runでエラーが出て動かなかった
FROM golang:1.18 as builder
WORKDIR /go/src/
COPY . ./
RUN go mod download
RUN go build -v -o server ./cmd
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/server ./
EXPOSE 8080
CMD ["./server"]
エラーメッセージ
standard_init_linux.go:211: exec user process caused "no such file or directory"
対策
- go envの環境変数CGO_ENABLED=0を設定しないとランタイムエラーになるので設定する
# 誤
RUN go build -v -o server ./cmd
# 正
RUN CGO_ENABLED=0 GOOS=linux go build -v -o server ./cmd
なぜ設定が必要なのか?
- CGO_ENABLEDは環境変数で、デフォルトが1になっている
- バイナリのサイズを小さく、かつ高速化するためにcgoを使ったモジュールはOS側にあるC/C++ライブラリを使う(動的リンク)設定でビルドされる
- クロスコンパイルするときは明示的に0を指定する
CGO_ENABLED=1のときになぜ上手く動かないか
- golangで書いたプログラムをgo buildすると、基本的には静的リンクになるがnetパッケージを使用していた場合、自動的に動的リンクになる
- これはnetパッケージがC版とGo版の2種があってデフォルトでは高速なC版が使われる
- このように動的リンクでビルドした場合、alpine linuxだとstandard pkgの場所が違う(パッケージが存在しない)問題が発生する
- よって正しくシングルバイナリにするときには CGO_ENABLED=0が必要。ただ、バイナリのサイズ(Dockerimageのサイズ)が大きくなる
参考文献
cgoを使わないGoのクロスコンパイル時に -installsuffix cgo が不要になってた -Carpe Diem
Alpine go builds with cgo enabled -Seb’s IT blog
スタティックリンク -「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
またビルド失敗しちゃった~… -ICTSC Tech blog
Golangのシングルバイナリをいい感じでDocker Image化する -blog.potproject.net
【Golang】“net” モジュールを使った静的リンクバイナリを Docker + scratch で使う際のビルドの注意【“net/http” “net/url” など】 -Qiita