概要
よく忘れるので、ここにメモメモ。。。
マルチステージビルドを利用すると、ビルド時のステージと実行時のステージを分けることが出来るので
余計なものが入っていないイメージをつくることが出来ます。
さらに、Googleが管理しているDistrolessイメージをベースにすることでさらに小さなサイズにすることが出来ます。
Distrolessイメージにはシェルすらも入っていないのでセキュリティ的にも良しですね。
DistrolessイメージはGoと相性がいいので、今回はGoのプログラムをコンテナの中で実行するようにします。
サンプルとなるプログラム
helloworldだけを普通に出力するプログラムだとimportされるパッケージが少ないので、少しだけ非同期入れて出力するようにしたプログラムです。
処理の内容に意味はありません。
package main import ( "context" "fmt" "time" "github.com/devlights/gomy/ctxs" "github.com/devlights/gomy/iter" ) func main() { var ( rootCtx = context.Background() mainCtx, mainCxl = context.WithCancel(rootCtx) procCtx, procCxl = context.WithTimeout(mainCtx, 1*time.Second) ) defer mainCxl() defer procCxl() <-exec(procCtx).Done() } func exec(pCtx context.Context) context.Context { var ( tasks = make([]context.Context, 0, 5) ) for i := range iter.Range(5) { tasks = append(tasks, func(pCtx context.Context, i int) context.Context { ctx, cancel := context.WithCancel(pCtx) go func() { defer cancel() select { case <-ctx.Done(): break default: fmt.Printf("[%d] hello-go\n", i+1) } }() return ctx }(pCtx, i)) } return ctxs.WhenAll(pCtx, tasks...) }
マルチステージなしのDockerfile
alpineベースのイメージを使いました。
# syntax=docker/dockerfile:1-labs FROM golang:1.17-alpine WORKDIR /workspace ENV CGO_ENABLED=0 COPY go.* . RUN go mod download COPY . . RUN go build -o app main.go CMD [ "/workspace/app" ]
マルチステージありのDockerfile
ビルドステージは alpineベースのものを使って、実行時はDistrolessを使っています。
# syntax=docker/dockerfile:1-labs # ----------------------------------------------------- # base image # ----------------------------------------------------- FROM golang:1.17-alpine as base WORKDIR /workspace ENV CGO_ENABLED=0 COPY go.* . RUN go mod download COPY . . RUN go build -o app main.go # ----------------------------------------------------- # runner image # ----------------------------------------------------- FROM gcr.io/distroless/static:latest WORKDIR /app COPY --from=base /workspace/app app CMD [ "/app/app" ]
Makefile
何回もコマンド打つのが面倒なので make できるようにしました。
build: \ copy-gomod \ build-nostage \ build-multistage \ rm-gomod copy-gomod: cp ../go.* . rm-gomod: rm go.* build-nostage: docker buildx build -t multistage-ex:nostage -f Dockerfile.nostage ${PWD} build-multistage: docker buildx build -t multistage-ex:multistage -f Dockerfile.multistage ${PWD} run: time -p docker container run --rm multistage-ex:nostage time -p docker container run --rm multistage-ex:multistage clean: docker image rm multistage-ex:nostage multistage-ex:multistage docker image prune -f
実行
ビルド
$ make build docker buildx build -t multistage-ex:nostage -f Dockerfile.nostage /workspace/try-docker/multistage_build docker buildx build -t multistage-ex:multistage -f Dockerfile.multistage /workspace/try-docker/multistage_build
イメージサイズの確認
$ docker image list multistage-ex REPOSITORY TAG IMAGE ID CREATED SIZE multistage-ex multistage bde4552a21c8 41 seconds ago 4.33MB multistage-ex nostage edc5dfebefbf 52 seconds ago 376MB
マルチステージなしの場合は 376MB で、マルチステージありの場合は 4.33MB となっていますね。
参考情報
- How to Minimize Go Apps Container Image
- GoogleContainerTools distroless
- Dockerfileのベストプラクティス Top 20
- distroless imageを実用する
- 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場