LoginSignup
2
0

More than 3 years have passed since last update.

Go言語アプリのDockerイメージをscratchで軽量化してみた

Last updated at Posted at 2020-08-10

こんばんは、ねじねじおです。

Go言語で書いたアプリの Docker イメージを軽量化するには、マルチステージビルドを使って scratch をベースに構築するのがよいと聞いて、やってみました。

準備

まず、サンプルとして小さな Web API を echo で作ります。

$ mkdir app
$ cd app
$ go mod init example.com/example
$ go get github.com/labstack/echo/v4

下記のディレクトリ構成で server.go と Dockerfie を追加します。

-example
 |-app
    │-Dockerfile
    │-go.mod
    │-go.sum
    |-server.go
server.go
package main

import (
    "net/http"
    "time"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    // Echo instance
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Routes
    e.GET("/", func (c echo.Context) error {
        return c.JSON(http.StatusOK, []string{
            "Hello, World!",
            time.Now().Format(time.RFC3339),
        })
    })

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}
Dockerfile
FROM golang:1.14.7
WORKDIR /go/src/app

COPY ./ ./
RUN go mod download
RUN go build -o ./server ./server.go

ビルドして実行してみます。

$ docker build ./ -t example
$ docker run -e TZ=Asia/Tokyo -p 1323:1323 example ./server

動作確認。

$ curl http://localhost:1323/
["Hello, World!","2020-08-10T19:51:15+09:00"]

成功!!

では、イメージサイズを確認してみます。

$ docker images example
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
example             latest              5ef805ca57ec        2 minutes ago       908MB

908MB って、でかいのかな?
基準がわからないので、レイヤーを確認します。

$ docker history example
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
5ef805ca57ec        2 minutes ago       /bin/sh -c go build -o ./server ./server.go     20.5MB              
13249ee42cff        2 minutes ago       /bin/sh -c go mod download                      77.3MB              
a3e0d3e82f45        3 minutes ago       /bin/sh -c #(nop) COPY dir:cccd1ec30e0093efe…   4.96kB              
aa1a8385bb42        44 hours ago        /bin/sh -c #(nop) WORKDIR /go/src/app           0B                  
baaca3151cdb        3 days ago          /bin/sh -c #(nop) WORKDIR /go                   0B    
... 省略 ...

マルチステージビルドを使うと、go mod download と go build の領域を節約できそうです。

マルチステージビルドを使う

Dockerfileを変更します。
builderステージでコンパイル、できたバイナリファイルをコピーしてproductionステージを作ります。

FROM golang:1.14.7 AS base
WORKDIR /go/src/app

FROM base AS builder
COPY ./ ./
RUN go mod download
RUN go build -o ./server ./server.go

FROM base AS production
COPY --from=builder  /go/src/app/server ./

ビルドします。

$ docker build ./ -t example

イメージのサイズとレイヤーを確認。

$ docker images example
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
example             latest              62b3a8a02af3        3 minutes ago       822MB

$ docker history example
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
62b3a8a02af3        3 minutes ago       /bin/sh -c #(nop) COPY file:3d74d9674cd3943c…   12.2MB              
aa1a8385bb42        44 hours ago        /bin/sh -c #(nop) WORKDIR /go/src/app           0B                  
baaca3151cdb        3 days ago          /bin/sh -c #(nop) WORKDIR /go                   0B                  
... 省略 ...

イメージサイズは、822MB。少し小さくなりましたね。

動作確認です。

$ docker run -e TZ=Asia/Tokyo -p 1323:1323 example ./server
$ curl http://localhost:1323/
["Hello, World!","2020-08-10T19:58:28+09:00"]

成功!

しかし、先ほどの docker history の出力をみると、バイナリをCOPYしてくるレイヤーは、たかだか 12.2MB 。
対して、イメージ全体のサイズは、822MB。
ベースのイメージが大きいのですね。

scratch をベースに最終イメージを構築する

Goの実行環境にはGoは不要なので、ミニマムに scratch をベースに最終イメージを構築してみます。

失敗 その1

Dockerfile
FROM golang:1.14.7 AS base
WORKDIR /go/src/app

FROM base AS builder
COPY ./ ./
RUN go mod download
RUN go build -o ./server ./server.go

FROM scratch AS production
WORKDIR /go/bin
COPY --from=builder /go/src/app/server ./

ビルドします。

$ docker build ./ -t example

イメージのサイズとレイヤーを確認。

$ docker images example
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
example             latest              e1ba3f7f81f0        About a minute ago   12.2MB

$ docker history example
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
e1ba3f7f81f0        2 minutes ago       /bin/sh -c #(nop) COPY file:3d74d9674cd3943c…   12.2MB              
06d1e3bea55e        12 minutes ago      /bin/sh -c #(nop) WORKDIR /go/bin               0B    

イメージサイズは、 12.2MB 。
劇的に小さくなりました!!

動作確認です。

$ docker run -e TZ=Asia/Tokyo -p 1323:1323 example ./server
standard_init_linux.go:211: exec user process caused "no such file or directory"

エラーが発生して、起動できない。
いろいろ調べて見ると、builder と production で実行環境が異なるので、コンパイル時に CGO を無効にする必要がありました。

失敗 その2

コンパイル時に CGO を無効にするように変更して再チャレンジです。

Dockerfile
FROM golang:1.14.7 AS base
WORKDIR /go/src/app

FROM base AS builder
COPY ./ ./
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./server ./server.go

FROM scratch AS production
WORKDIR /go/bin
COPY --from=builder /go/src/app/server ./

ビルドして実行します。

$ docker build ./ -t example
$ docker run -e TZ=Asia/Tokyo -p 1323:1323 example ./server

起動しました!
動作確認です。

$ curl http://localhost:1323/
["Hello, World!","2020-08-10T11:53:39Z"]

成功!
いや、否。
タイムゾーンが無視されている。。
タイムゾーンは、 アジアの都市、東京 です。"+09:00" です。

そして成功へ

こちらのサイトに答えが書いてありました。
Using local time in a Golang Docker container built from Scratch

今回は、builder ステージがから /usr/share/zoneinfo をコピーする方法を採用しました。

Dockerfile
FROM golang:1.14.7 AS base
WORKDIR /go/src/app

FROM base AS builder
COPY ./ ./
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./server ./server.go

FROM scratch AS production
WORKDIR /go/bin
COPY --from=builder /go/src/app/server ./
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

ビルドして実行します。

$ docker build ./ -t example
$ docker run -e TZ=Asia/Tokyo -p 1323:1323 example ./server

動作確認です。

$ curl http://localhost:1323/
["Hello, World!","2020-08-10T21:11:29+09:00"]

タイムゾーンが反映されています!

イメージのサイズを確認します。

$ docker images example
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
example             latest              e673e01668c6        14 minutes ago      13.3MB

$ docker history example
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
e673e01668c6        14 minutes ago      /bin/sh -c #(nop) COPY dir:b39c255c3688d7205…   1.21MB              
3865c579a2d0        25 minutes ago      /bin/sh -c #(nop) COPY file:bafd1cb7f8670973…   12.1MB              
06d1e3bea55e        34 minutes ago      /bin/sh -c #(nop) WORKDIR /go/bin               0B   

13.3 MB。

OK。
ねじねじおでした。

  • 2020年8月15日 追記
    • Go1.15でタイムゾーンデータベースをバイナリに組み込めるようになったので、こちら に方法を書きました。
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0