概要
Alpineベースのイメージが軽量イメージとして認知されていますが、Goの場合は単一バイナリで動くのでイメージ内にgolangが入っている必要はありません。
なので最も軽量と言われているScratch imageを利用することで、dockerイメージを軽量化することができます。
環境
- golang 1.8.3
- Docker 17.06.0-ce
ビルド手順
対象ファイル
以下のファイルを扱うことにします。
package main import ( "fmt" "io/ioutil" "net/http" "os" ) func main() { resp, err := http.Get("https://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)) }
コンパイル
通常のビルドをすると以下のようになります。
$ go build -o main .
$ file main
main: Mach-O 64-bit executable x86_64
このままでは動かないので、scratchイメージ用にクロスコンパイルします。
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main main.go
CGO_ENABLED=0
と-installsuffix cgo
をつけることでcgoの依存を排除しています。
通常のビルドだとOSのライブラリをdynamic linkするため、ビルド環境と実行環境が異なると上手く動きません。
なので異なる実行環境でも動くようにstatic linkにしておく必要があります。
-a
は依存パッケージをリビルドするためのオプションで、これによって依存パッケージのcgo依存を排除しています。
このワークアラウンドは以下のissueで話されてます。
また本番環境用であれば-ldflags="-s -w"
をつけることでよりファイルサイズを小さくすることも可能です。
こうしてビルドしたものは以下のようなバイナリになります。
$ file main main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
Dockerfile
FROM scratch ADD ca-certificates.crt /etc/ssl/certs/ ADD main / CMD ["/main"]
ca-certificates.crt
は内部でTLSリクエストをするケースで必要です。今回だとhttps://google.com
にリクエストしているため必要になります。
ca-certificates.crt
はUbuntuなど一般的なディストリビューションであれば入っているので、その辺から用意しておきます。
以下のようにgithubでca-certificates.crt
付きのscratchイメージを用意してる人もいますが、古かったり不正な証明書を含む可能性があるためプロジェクトではUbuntuなどのディストリビューションから自分で抽出することをオススメします。
docker build
$ docker build -t example-scratch .
imageサイズを確認してみましょう。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE example-scratch latest 55c6d909ff46 13 seconds ago 5.69MB example-alpine latest dc16f2d945c3 3 seconds ago 268MB
alpineが268MB
に対し、scratchは5.69MB
と非常に軽量になったことがわかります。
動作検証
$ docker run -it example-scratch 11875
問題なく動きました。
もし証明書が無いと、今回の場合以下のようなエラーが出ます。
Get https://google.com: x509: failed to load system roots and no roots provided
Docker multi stageを利用してビルド
docker 17.05から、multi-stageビルドが利用できます。
1つのDockerfileで複数のFROMイメージ間のファイルをCOPY --from
で直接を参照できるようになってます。
FROM golang:alpine AS build-env ADD . /workspace WORKDIR /workspace RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main main.go FROM scratch ADD ca-certificates.crt /etc/ssl/certs/ COPY --from=build-env /workspace/main /main CMD ["/main"]
これによって
- ビルド環境にgolangがなくても、dockerさえ動けば軽量イメージが作成できる
- 既存のイメージもscratchにバイナリを渡すだけでいいので手軽に移行できる
といったことが可能になります。
まとめ
Docker imageを軽量化することでデプロイ速度を上げることができます。
今回の対応で運用面のコスト改善であったり、オートスケール時の起動速度改善ができると思います。