マルチステージで削り落とす
このラボは本物のVM上で動きます
この環境はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。
以前はこのラボがPodの中で動いていました。カーネル権限をすべて落とした環境だったため、コンテナを起動するステップが塞がれており、イメージアーカイブを自分で展開してみるという回り道で学んでいました。もう回り道は必要ありません。
知っておくべきことが2つあります。
- 最初の起動に1分ほどかかります。VMが起動してDockerをインストールするためです。Podのラボ(通常40秒)より遅くなります。
- ブラウザープレビューはありません。VMへの接続は、採点用のポート1つしか開いていません。Webサーバーを起動した場合は、VMの中で
curlを使って確認してください。
目標
ビルダーステージで作ったアーティファクトだけを最終イメージへ移し、同じ結果をはるかに小さいイメージで作る過程を、サイズの数字で確認します。
なぜ重要なのか
イメージのサイズは見た目の問題ではなく、デプロイ速度とロールバック速度の問題です。ノードがスケールアウトするたびに、ロールバックするたびに、そのサイズをもう一度ダウンロードします。そして、サイズを減らす最大のてこは、圧縮や整理ではなく、「最終イメージに入ってはいけないものを、そもそもそのステージで作らないこと」です。レイヤーは巻き戻せないため、この原則を破ると、あとでどれだけ削除してもサイズは戻りません。
ステップ
/root/build3を作成し、/root/build3/Dockerfileにステージを2つ作ります。最初のステージはpython:3.12-alpineを使い、名前はbuilderで、/out/app.txtにbuilt-in-builderを書き込みます。2つ目のステージはalpine:3.20です。labhub/ms:v1でビルドします。- 最終ステージで、ビルダーの
/out/app.txtだけを/app/app.txtへコピーするように修正し、labhub/ms:v2でビルドします。実行してファイルを読むと、built-in-builderが出力される必要があります。 /root/build3/fat.Dockerfileに、単一ステージで同じ結果を作るバージョン(ベースはpython:3.12-alpine)を書き、labhub/ms:fatでビルドします。- 最初のステージだけをビルドして、
labhub/ms:builderイメージを作ります。その中には、/out/app.txtがある必要があります。 Dockerfileの最終ステージに、busybox:1.36イメージから/bin/busyboxを/app/busyboxへ直接コピーする行を追加し、labhub/ms:v3でビルドします。labhub/ms:v3を実行して、python3がないことを確認します。イメージサイズは60MB未満である必要があります。/root/build3/scratch.Dockerfileを作成し、FROM scratchの上にbusybox:1.36の/bin/busyboxだけを載せ、引数として受け取った文字列をそのまま出力するように、ENTRYPOINTを配列形式で指定します。labhub/ms:scratchでビルドし、scratch-worksを引数として渡すと、その文字列が出力される必要があります。/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の整数部です)
参考
docker build --target builder -t labhub/ms:builder /root/build3で、中間ステージだけをビルドします。COPY --from=busybox:1.36 /bin/busybox /app/busyboxのように、ステージではないイメージも参照できます。- busyboxは静的にリンクされているため、scratchの上でも動作します。
busybox echo <문자열>の形で実行されます(プレースホルダーは文字列です)。 - よくある間違い1: ステップ2でステージのディレクトリをまるごとコピーすると、サイズがほとんど減りません。
- よくある間違い2: ステップ7でENTRYPOINTをシェル形式で書くと、scratchにはシェルがないため、すぐに失敗します。
ステージを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バイトです。