TT Lab
はじめる
学ぶ 学習パス コース

イメージのビルド

マルチステージで削り落とす

TT Labで続きを見る

このラボは本物のVM上で動きます

この環境はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。

以前はこのラボがPodの中で動いていました。カーネル権限をすべて落とした環境だったため、コンテナを起動するステップが塞がれており、イメージアーカイブを自分で展開してみるという回り道で学んでいました。もう回り道は必要ありません。

知っておくべきことが2つあります。

目標

ビルダーステージで作ったアーティファクトだけを最終イメージへ移し、同じ結果をはるかに小さいイメージで作る過程を、サイズの数字で確認します。

なぜ重要なのか

イメージのサイズは見た目の問題ではなく、デプロイ速度とロールバック速度の問題です。ノードがスケールアウトするたびに、ロールバックするたびに、そのサイズをもう一度ダウンロードします。そして、サイズを減らす最大のてこは、圧縮や整理ではなく、「最終イメージに入ってはいけないものを、そもそもそのステージで作らないこと」です。レイヤーは巻き戻せないため、この原則を破ると、あとでどれだけ削除してもサイズは戻りません。

ステップ

  1. /root/build3を作成し、/root/build3/Dockerfileにステージを2つ作ります。最初のステージはpython:3.12-alpineを使い、名前はbuilderで、/out/app.txtにbuilt-in-builderを書き込みます。2つ目のステージはalpine:3.20です。labhub/ms:v1でビルドします。
  2. 最終ステージで、ビルダーの/out/app.txtだけを/app/app.txtへコピーするように修正し、labhub/ms:v2でビルドします。実行してファイルを読むと、built-in-builderが出力される必要があります。
  3. /root/build3/fat.Dockerfileに、単一ステージで同じ結果を作るバージョン(ベースはpython:3.12-alpine)を書き、labhub/ms:fatでビルドします。
  4. 最初のステージだけをビルドして、labhub/ms:builderイメージを作ります。その中には、/out/app.txtがある必要があります。
  5. Dockerfileの最終ステージに、busybox:1.36イメージから/bin/busyboxを/app/busyboxへ直接コピーする行を追加し、labhub/ms:v3でビルドします。
  6. labhub/ms:v3を実行して、python3がないことを確認します。イメージサイズは60MB未満である必要があります。
  7. /root/build3/scratch.Dockerfileを作成し、FROM scratchの上にbusybox:1.36の/bin/busyboxだけを載せ、引数として受け取った文字列をそのまま出力するように、ENTRYPOINTを配列形式で指定します。labhub/ms:scratchでビルドし、scratch-worksを引数として渡すと、その文字列が出力される必要があります。
  8. /root/build3/size.mdに、次の4行を実際の値で書きます。
fat_mb=<labhub/ms:fat 크기(MB)>
slim_mb=<labhub/ms:v3 크기(MB)>
scratch_mb=<labhub/ms:scratch 크기(MB)>
reduction_pct=<(fat_mb - scratch_mb) / fat_mb * 100 의 정수부>

(プレースホルダーは、labhub/ms:fatのサイズ(MB)、labhub/ms:v3のサイズ(MB)、labhub/ms:scratchのサイズ(MB)、そして(fat_mb - scratch_mb) / fat_mb * 100の整数部です)

参考

ステージを2つ作る

/root/build3を作成し、/root/build3/Dockerfileにステージを2つ作ります。最初のステージはpython:3.12-alpineを使い、名前はbuilderで、/out/app.txtにbuilt-in-builderを書き込みます。2つ目のステージはalpine:3.20です。labhub/ms:v1でビルドします。

FROMを2回書き、前のステージに名前を付けます。その名前は、あとでCOPYから参照します。

アーティファクトだけを持ってくる

最終ステージで、ビルダーの/out/app.txtだけを/app/app.txtへコピーするように修正し、labhub/ms:v2でビルドします。実行してファイルを読むと、built-in-builderが出力される必要があります。

ビルダーステージで作ったファイルだけを選んでコピーします。ステージ全体をコピーすると、マルチステージを使う意味がありません。

単一ステージとのサイズ比較

/root/build3/fat.Dockerfileに、単一ステージで同じ結果を作るバージョン(ベースはpython:3.12-alpine)を書き、labhub/ms:fatでビルドします。

同じ結果を、重いベースの上にそのまま残したイメージと比較してください。30%以上減っている必要があります。

中間ステージだけをビルドする

最初のステージだけをビルドして、labhub/ms:builderイメージを作ります。その中には、/out/app.txtがある必要があります。

デバッグするときは、ビルダーステージだけを別のイメージとして取り出せます。最終ステージまで進まずに止めるオプションがあります。

別のイメージから直接コピーする

Dockerfileの最終ステージに、busybox:1.36イメージから/bin/busyboxを/app/busyboxへ直接コピーする行を追加し、labhub/ms:v3でビルドします。

--fromが受け取るのは、ステージ名だけではありません。イメージ参照をそのまま渡すこともできます。

ビルドツールを残さない

labhub/ms:v3を実行して、python3がないことを確認します。イメージサイズは60MB未満である必要があります。

最終ステージで、ビルドにだけ使われたランタイムが消えているかを、実際に実行して確認してください。

空のベースの上に載せる

/root/build3/scratch.Dockerfileを作成し、FROM scratchの上にbusybox:1.36の/bin/busyboxだけを載せ、引数として受け取った文字列をそのまま出力するように、ENTRYPOINTを配列形式で指定します。labhub/ms:scratchでビルドし、scratch-worksを引数として渡すと、その文字列が出力される必要があります。

scratchにはシェルがないため、ENTRYPOINTは必ず配列形式にする必要があります。静的にリンクされたバイナリを選ぶ必要があります。

サイズのレポート

/root/build3/size.mdに、次の4行を実際の値で書きます。

3つのイメージの実際のサイズを測って書き、削減率を計算してください。1MB = 1048576バイトです。