🤖

🤖

:gijutsu_burogu:

Go製CLIツールを使うDockerイメージをダイエットしてみた

なぜ Docker イメージを軽くするのか

  • Docker イメージのダウンロードが早くなる
  • Docker イメージのアップロードが早くなる
  • CI やプロダクションなど各環境へのイメージの配布が効率的に行える
  • ホストのディスク容量を圧迫しない

どう Docker イメージを軽くするのか

  • 軽いベースイメージを使う
  • 不要なファイルの削除する
  • レイヤの結合してレイヤ数を減らす
  • マルチステージビルドする

やってみた

以下の記事で Dockerfile を作成したが、パフォーマンスについては考慮されていなかった。 この Dockerfile を減量する。 減量することにより、Docker Hub からのイメージのダウンロード時間が短くなりユーザがより素早くブログ移行をすることができる。

kotaroooo0-dev.hatenablog.com

初期状態(869MB)

FROM golang:latest

RUN go get -u github.com/tenntenn/qiitaexporter
RUN go get -u github.com/x-motemen/blogsync

RUN apt-get update
RUN apt-get install gettext-base -y

WORKDIR /Documents
COPY blogsync.template /Documents
COPY setup.sh /Documents

RUN mkdir -p ~/.config/blogsync
COPY config.yaml.tmp /root/.config/blogsync

RUN chmod +x setup.sh
ENTRYPOINT ./setup.sh

軽いベースイメージ、不要ファイル削除、レイヤ結合(410MB)

FROM golang:1.14-alpine

RUN apk --no-cache add libintl && \
    apk --no-cache add git && \
    apk --no-cache add --virtual .gettext gettext && \
    cp /usr/bin/envsubst /usr/local/bin/envsubst && \
    apk del .gettext && \
    go get github.com/tenntenn/qiitaexporter \
    github.com/x-motemen/blogsync && \
    rm -rf $GOPATH/src $GOPATH/pkg

WORKDIR /Documents
COPY blogsync.template /Documents
COPY setup.sh /Documents

RUN mkdir -p ~/.config/blogsync
COPY config.yaml.tmp /root/.config/blogsync

RUN chmod +x setup.sh
ENTRYPOINT ./setup.sh

Alpine Linux を使う

golang:latestが 803MB に対して、golang:alpineは 370MB である。 軽いベースイメージを使うだけで 400MB 以上の軽量になる。 frolvlad/alpine-goの方がイメージサイズが少し小さかったが、安心と安全の公式イメージを使う。 alpine はパッケージ管理がapkになる。 gitも入ってないので、go getするためにはgitをインストールする必要はある。

不要ファイルの削除

go getで取得したソースやapk関連で不要になったファイルを削除を削除することでイメージが軽量になる。

RUN を&でまとめる

Docker イメージではレイヤーが構成されている。 そのため、層を減らすことで軽量化できる。 RUNを分けて書くとその分レイヤーが生成されるため重くなってしまう。

以下が一番軽量。

RUN go get github.com/tenntenn/qiitaexporter \
    github.com/x-motemen/blogsync && \
    rm -rf $GOPATH/src $GOPATH/pkg

以下は不要なファイルを削除していない分だけ重たくなる。

RUN go get github.com/tenntenn/qiitaexporter \
    github.com/x-motemen/blogsync && \

以下はファイルを削除していないものに、さらにレイヤーを増やしただけになり一番重い。 たとえ、不要なファイルを削除しようともその前段のレイヤーは変わらず存在しているため軽くなることはない。

RUN go get github.com/tenntenn/qiitaexporter \
    github.com/x-motemen/blogsync

RUN rm -rf $GOPATH/src $GOPATH/pkg

マルチステージビルド(26.3MB)

上の作業をしている際に、これはわざわざgolangイメージをつかわなくとも、事前にgo getしておいてバイナリをローカルからイメージへコピーすればいいのではと思った。 ただ、そうすると Dockerfile を読むだけではなおさら理解が難しくなり、汎用性は下がるだろうと思った。

ところが、すでにその機能は公式にサポートされていた。 それが MultiStageBuild である。 ビルド用と実行用にイメージを分けることができる。 事前にローカルでバイナリを生成して、イメージにコピーするよりも Dockerfile 内でバイナリを生成して、渡すことで可読性も担保される。

FROM golang as builder

RUN CGO_ENABLED=0 go get github.com/tenntenn/qiitaexporter github.com/x-motemen/blogsync

FROM alpine

COPY --from=builder /go/bin/qiitaexporter /bin/qiitaexporter
COPY --from=builder /go/bin/blogsync /bin/blogsync

RUN apk --no-cache add libintl && \
    apk --no-cache add --virtual .gettext gettext && \
    cp /usr/bin/envsubst /usr/local/bin/envsubst && \
    apk del .gettext

WORKDIR /Documents
COPY blogsync.template /Documents
COPY setup.sh /Documents

RUN mkdir -p ~/.config/blogsync
COPY config.yaml.tmp /root/.config/blogsync

RUN chmod +x setup.sh
ENTRYPOINT ./setup.sh

ビルドごとにRUN CGO_ENABLED=0 go get github.com/tenntenn/qiitaexporter github.com/x-motemen/blogsyncが走ってしまうことが課題にある。

docker historyすることで、レイヤごとの重さをみることができる。 alpine と go の CLI ツールのサイズが大半を占めているのでこれ以上の減量は難しい。

[qiitatohatena] docker history adachikun/qiitatohatena
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
517f8dd94816        10 minutes ago      /bin/sh -c #(nop)  ENTRYPOINT ["/bin/sh" "-c…   0B
<missing>           10 minutes ago      /bin/sh -c chmod +x setup.sh                    248B
<missing>           10 minutes ago      /bin/sh -c #(nop) COPY file:654810ae2d9e4894…   119B
<missing>           10 minutes ago      /bin/sh -c mkdir -p ~/.config/blogsync          0B
<missing>           10 minutes ago      /bin/sh -c #(nop) COPY file:554e23819df70ab7…   248B
<missing>           10 minutes ago      /bin/sh -c #(nop) COPY file:8d2614cf1943aa71…   214B
<missing>           10 minutes ago      /bin/sh -c #(nop) WORKDIR /Documents            0B
<missing>           10 minutes ago      /bin/sh -c apk --no-cache add libintl &&    …   101kB
<missing>           10 minutes ago      /bin/sh -c #(nop) COPY file:006d3047ed766467…   11.1MB
<missing>           10 minutes ago      /bin/sh -c #(nop) COPY file:a2c93431654cceb7…   9.51MB
<missing>           8 days ago          /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>           8 days ago          /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b…   5.57MB

おわりに

一般的な軽量化手法を試すだけで、869MBから26MBまで減量することができた。 これにより、Qiitaからはてなブログへ移行したい人は30倍高速にDockerイメージをダウンロードすることができるようになった。

今回は Docker イメージの軽量化のみに注目した。 しかし、Docker イメージにはビルド高速化のためにキャッシュや buildkit など関連する要素があり今後記事にしたい。 また、docker-slim という Docker イメージを自動で減量するツールもあり試してみたいと思った。

参考

docs.docker.jp