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のサイズ)が大きくなる

参考文献

Go -Environment variables

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