minminの備忘録

フルスタックになりたいエンジニアの備忘録 → https://zuminblog.com/ へ引越し中

dockerのmulti stage buildを理解する

今回は、dockerのmulti stage buildとやらを紹介したいと思います。

(※この機能は、Docker 17.05以降のversionで使用できます。)

multi stage buildとは

そもそもmulti stage buildとはなんぞやという感じだったので、そこから説明したいと思います。

以下のように、1つのDockerfileでFROMを複数使用してbuildを行うことをmulti stage buildと言います。

(詳しいコードの中身については、「使い方」の章で説明したいと思います。)

FROM golang:1.12 as builder
WORKDIR /app
COPY ./sample.go .
RUN GOOS=linux go build -o sample sample.go

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/sample .

いつ使うのか

golangなどのコンパイルしてコードを実行するような言語の環境を作成する時に使います。

1つのDockerfileで、コンパイルする環境と実行環境を分けて作成することができます。

なぜ使うのか

最終的に出力イメージのサイズを軽くすることができるからです。

イメージサイズを軽くすることで、配布する側もダウンロードして使用する側も負担が減ります。

なぜ軽くなるかというと、コンパイルで使用した一時的な環境を捨てて、実行環境のみをイメージとして作成することができるためです。

具体的にどれくらい軽くなるかについてですが、「使い方」の章で説明するサンプルコードだとイメージサイズが100倍近く軽くなっています。

使い方

今回はdockerの公式docでも紹介されていたように、golangの環境を作成するコードを書いていきたいと思います。

Dockerfileでは、以下の簡単な.goファイルをコンパイルして実行ファイルを作成し、配置するところまでやっていきたいと思います。

package main

import "fmt"

func main() {
    a := 1
    b := 2
    fmt.Println(a + b)
}

single stage build

まずは、single stage buildの復習からです。

Dockerfileは以下のように書きました。

FROM golang:1.12
WORKDIR /app
COPY ./sample.go .
RUN go build -o sample sample.go

イメージを作成し、コンテナを起動し入り込んでみます。

$docker build -t single_stage_build:latest .
$docker run -it single_stage_build /bin/sh
# ls
sample  sample.go
# ./sample
3

無事、実行できました!

multi stage build

続いて、multi stage buildです。

先ほど書いたDockerfileの再掲です。

FROM golang:1.12 as builder
WORKDIR /app
COPY ./sample.go .
RUN GOOS=linux go build -o sample sample.go

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/sample .

上記のコードについて、抜粋して説明していきます。


FROM golang:1.12 as builder

の部分では、as <name>をすることで、ステージに名前を指定することができます。ステージ名は後で使用します。


RUN GOOS=linux go build -o sample sample.go

の部分について、GOOS=linuxで、実行環境をLinuxに指定しています。コンパイルを行う環境と実行環境が違うため指定しています。(クロスコンパイル)

(もしかしたら指定しなくても良いかもしれません。なくても動作することは確認しました)


COPY --from=builder /app/sample .

の部分では、最初のステージからファイルをコピーしています。

どのステージから持ってくるかについては、--from=<stage name>で指定しています。



イメージの作成方法は、通常のDockerfileと同様に行います。

$docker build -t multi_stage_build:latest .

イメージからコンテナを起動して入り込んでみると、コンパイル済みの実行ファイルのみが置かれていることを確認できます。(もちろん実行もできます)

$docker run -it multi_stage_build /bin/sh
~ # ls
sample
~ # ./sample
3

イメージサイズの比較

$docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
multi_stage_build            latest              28c0ead58d63        4 minutes ago       7.58MB
<none>                       <none>              35ed8be9c145        4 minutes ago       824MB
single_stage_build           latest              5438467d2c15        45 minutes ago      816MB

single stage buildのイメージは816MB、multi stage buildのイメージは7.58MBと100倍近く軽いイメージを作成することができました!!


実は中間イメージ(<none>のやつ)が残ってしまっていますがこれについては、以下のサイトでdockerのメンテナーの方がバグではなく仕様だと説明しています。

簡単に説明すると、「buildするたびに毎回0からイメージを作成するなんて無駄だから、キャッシュとして残してるんだよ」みたいな感じで説明しています。

詳しく見たい方は、以下のサイトを参考にしてもらえればと思います。

github.com

おまけ

dockerの公式docにはRUNの部分で、

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o sample sample.go

みたいな感じで書いてあったのですが、以下のサイトによるとgolangのver1.10以降では

CGO_ENABLED=0 go build -a -installsuffix cgo

のオプションはいらないとのことです。

github.com

参考