Carpe Diem

備忘録

cgoを使わないGoのクロスコンパイル時に -installsuffix cgo が不要になってた

背景

以前Docker multi stage buildなどビルド環境と実行環境が異なる(クロスコンパイル)時に、単純にGOOSGOARCHをセットするだけではなく

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
  go build -a -installsuffix cgo -o main main.go

のように

  • CGO_ENABLED=0
  • -a
  • -installsuffix cgo

といったおまじないが必要ですよ、という記事を書きました。

ScratchイメージでGolangアプリの超軽量イメージをビルド - Carpe Diem

が、go 1.10からそれらが不要になったようです。

CGO_ENABLED=0 and -installsuffix cgo are no longer required since Go 1.10 | by Адам | Medium

環境

検証コード

go

以下のmain.goファイルを用意します。

package main

import (
        "fmt"
        "io/ioutil"
        "net/http"
        "os"
)

func main() {
        resp, err := http.Get("http://google.com")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(len(body))
}

Dockerfile

CGO_ENABLED=0 and -installsuffix cgo are no longer required since Go 1.10 | by Адам | Medium

の通りに

  • CGO_ENABLED=0
  • -a
  • -installsuffix cgo

を削ります。
alpine -> scratchのmulti stage buildです。

FROM golang:alpine AS build-env
ADD . /workspace
WORKDIR /workspace
RUN GOOS=linux GOARCH=amd64 go build -o main main.go

FROM scratch
COPY --from=build-env /workspace/main /main
CMD ["/main"]

検証

docker imageをビルド

$ docker build --tag hoge .

実行してみます。

$ docker run --rm --name piyo hoge
standard_init_linux.go:211: exec user process caused "no such file or directory"

あれ?コケましたね。

なぜコケたのか?

ドキュメントにはCGO_ENABLEDはデフォルトは1、クロスコンパイル時は0とあります。

The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling.

ref: cgo command - cmd/cgo - pkg.go.dev

そこでこんなDockerfileを用意してみます。

FROM golang:alpine
ADD . /workspace
WORKDIR /workspace
RUN go env
RUN GOARCH=amd64 go env
RUN GOARCH=ppc64le go env

実行してみましょう。

デフォルト(GOARCH未指定)

Step 4/6 : RUN go env
 ---> Running in 12010cc1f9ba
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"  # 1になってる
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build069180331=/tmp/go-build -gno-record-gcc-switches"
Removing intermediate container 12010cc1f9ba
 ---> 1e52bae6675d

ドキュメント通りCGO_ENABLED=1です。

GOARCH=amd64指定

Step 5/6 : RUN GOARCH=amd64 go env
 ---> Running in d108c8647275
GO111MODULE=""
GOARCH="amd64"  # 未指定と同じ
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"  # 1になってる
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build158549923=/tmp/go-build -gno-record-gcc-switches"
Removing intermediate container d108c8647275
 ---> a3db739ae176

指定したGOARCH=amd64デフォルトのGOARCHと同じなのでCGO_ENABLED=1なっています。

GOARCH=ppc64le指定

Step 6/6 : RUN GOARCH=ppc64le go env
 ---> Running in dfc4db342a73
GO111MODULE=""
GOARCH="ppc64le"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
GOPPC64="power8"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="0"  # 0になってる
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build362580251=/tmp/go-build -gno-record-gcc-switches"

ドキュメント通りCGO_ENABLED=0です。

つまり

GOARCHがビルド環境以外の値を指定すればデフォルトでCGO_ENABLED=0になるので問題は起きないですが、知らずに被ってCGO_ENABLED=1になる場合もあるので明示的にCGO_ENABLED=0を指定した方が良いです。

修正コード

ビルドする箇所にCGO_ENABLED=0を追記します。

FROM golang:alpine AS build-env
ADD . /workspace
WORKDIR /workspace
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go

FROM scratch
COPY --from=build-env /workspace/main /main
CMD ["/main"]

実行してみます。

$ docker run --rm --name piyo hoge
13277

今度は実行できました。

結論

Go 1.10以降でcgoを使わないクロスコンパイル時は

  • CGO_ENABLED=0は付けた方がいい
  • -a-installsuffix cgoは付けなくていい

とすれば良いです。