Dockerを触り始めた頃、巨大なリポジトリをcloneしてきて必要なファイル群をコピーしたりchownしたりといった操作を一つのステージでまとめて行ってしまっていたことがありました。これによって5GB近いサイズのイメージが出来上がっていた訳ですが、Dockerfileをマルチステージな構成を活かせる形に書き換えることで、イメージサイズを半分以下にまで落とすことができました。
この仕組みを調べたことがDockerの理解に役立ったため、当時の自分やDockerを触り始めたばかりの方に向けてまとめてみようと思います。今回行った最適化のポイントは、マルチステージビルドの活用とCOPY時の—chownオプションの活用の二つになります。
マルチステージビルドの活用
イメージサイズ肥大化への対処
Dockerのイメージは、ベースイメージのファイルシステムに対する一連の操作の重ね合わせとして認識されます。ビルドの際は、RUN, COPY, ADDなどの各操作が行われる度に新しいレイヤが作られ、それらのレイヤはスタックのようにベースイメージの上に重ねられて、最終的なイメージを形成します。
そのため、何も考えずにイメージをビルドすると、
- ビルドツールと依存ライブラリ(コンパイラやビルドツール)
- ビルド前のソースコードファイル
- テストコードやテストツール
などの本番では必要のないファイル群が混ざってしまうことになります。
この問題に対処するために、昔は中間イメージを作成する手法が一般的だったようです。必要なファイルの入った中間イメージをもとにコンテナを起動してビルドなどの作業を行い、その生成物を本番のイメージにコピーすることでサイズを抑えていました(参考: docker docs マルチステージビルドの利用)。この手法は依然として有効ですが、マルチステージビルドはこのプロセスをよりシンプルにし、ビルドプロセスを1つのDockerfileに統合することを可能にします。
Dockerfileのマルチステージ化
今回のリファクタでは、次のようなDockerfileを
FROM php:7.4-fpm-alpine
...
RUN git clone -b master https://github.com/sampleorg/bigrepo.git /homedir
...
RUN cp -r /homedir /var/www/app
RUN chown -R php.php /var/www/app
以下のように書き直しました。
FROM alpine:3.13 as build
...
RUN git clone -b master https://github.com/sampleorg/bigrepo.git /homedir
...
FROM php:7.4-fpm-alpine
...
COPY --from=build --chown=php:php /homedir /var/www/app
もとのDockerfileでは、cloneしてきたリポジトリのコピー・所有権の変更を全て本番ステージで行っていました。操作の度にレイヤが生成されるため、影響を受けるファイルの数やサイズが大きい操作が積み重なることで、イメージサイズは2倍3倍と肥大して行ってしまいます。
リファクタ後のDockerfileでは、cloneやその後の処理はビルドステージで行い、必要なファイル群のみを本番ステージに持ってくるようにしました。
マルチステージビルドの際にビルドステージで生成されるイメージは、実際にはDockerのローカルストレージ(一般的にはDockerホストのファイルシステム内)に一時的に保存され、後続のステージから参照できるようになります。本番ステージでCOPY —from=build
のような命令を使用することで、この一時的に保存されたビルドステージのイメージからファイルをコピーすることができるようになります。
ビルドが完全に終了すると、これらの最終イメージの作成のみに用いられる中間イメージはその後は不要なため通常は削除されます。このような仕組みによって、最終的に生成されるイメージのサイズを小さく抑えることができるようになりました。
REPOSITORY TAG IMAGE ID CREATED SIZE
before latest 8aabe296d57e 2 hours ago 4.87GB
after latest 72fc81f23bc1 2 hours ago 1.88GB
COPY時の—chownオプションの活用
chownすると差分にカウントされる
リファクタ後のDockerfileについて、本番ステージの記述は以下のようにすることもできます。
FROM php:7.4-fpm-alpine
...
# COPY --from=build --chown=php:php /homedir /var/www/app
COPY --from=build /homedir /var/www/app
RUN chown -R php:php /var/www/app
しかしこの場合、生成されるイメージは大きなままとなってしまっています。
REPOSITORY TAG IMAGE ID CREATED SIZE
after latest 72fc81f23bc1 2 hours ago 1.88GB
after-chmod latest fbe148476f5b 16 minutes ago 3.36GB
これは、chown対象のディレクトリごと、前のレイヤの状態に対する差分として扱われてしまうためだと考えられます。DockerfileのADD/COPY操作において--chownを使用することで、ADD/COPYの一連のステップの中で操作対象ファイルの所有者を変更することができるため、こちらも不要なレイヤを増やさないという観点からイメージサイズの抑制に有効そうでした。
まとめ
ここまで、マルチステージビルドの活用とCOPY時の—chownオプションの活用の二つのポイントを通してイメージサイズの軽量化について説明しながら、イメージがどのように構成されているかなどのDockerの基本的なところも合わせて復習して来ました。この先の理解の助けになれば幸いです。