キャッシュが壊れる場所を見つける
このラボは本物の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を使って確認してください。
目標
キャッシュがどこで壊れるのかをイメージIDで証明し、ユニオンファイルシステムでは削除してもサイズが小さくならないことを、2つのイメージのサイズの差で確認します。
なぜ重要なのか
「ビルドが遅い」という問題は、キャッシュの設定をオンオフする問題ではなく、順序の問題である場合が圧倒的に多いです。キャッシュキーが親のダイジェストに連鎖して結び付いているという事実を1つ理解すれば、直すべき箇所は常に「キャッシュが最初に壊れるその1行」だということが自明になります。その上は触る必要がなく、その下は手の打ちようがありません。サイズも同じです。レイヤーは巻き戻せないので、最終イメージに入ってはいけないものは、そもそもそのレイヤーで作らない必要があります。
ステップ
/root/build2を作成し、nginx:1.27-alpineのレイヤー数を数えて、/root/build2/nginx-layers.txtに数字だけを書きます。alpine:3.20のレイヤーごとの履歴を、切り詰められないように/root/build2/alpine-history.txtへ保存します。docker historyが表示する表は、イメージのconfigブロブのhistory配列そのものです。この環境では、skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'で同じ値を読み取ります。/root/build2にdeps.txtとsrc/main.txtを作成し、2つのファイルをそれぞれCOPYするDockerfileを書きます。labhub/cache:v1でビルドしたあと、イメージIDを/root/build2/id1.txtに書き、何も変更せずにもう一度ビルドして、IDを/root/build2/id2.txtに書きます。2つの値は同じである必要があります。deps.txtの内容を変更してlabhub/cache:v2でビルドし、IDを/root/build2/id3.txtに書きます。id1.txtとは異なる必要があります。/root/build2/ordered.Dockerfileを作成し、COPY deps.txtがCOPY src/より先に出るようにして、labhub/cache:v3でビルドします。/root/build2/big.logを作成し、/root/build2/.dockerignoreにログファイルの除外ルールを入れます。コンテキスト全体を/ctxにコピーするDockerfileでlabhub/cache:v4をビルドすると、イメージ内に/ctx/deps.txtはあり、/ctx/big.logはない必要があります。- 20MBのファイルを作成したあと、次のRUNで削除するイメージを
labhub/fat:badとして、同じRUNの中で削除するイメージをlabhub/fat:goodとしてビルドします。 /root/build2/cache.mdに、次の3行を実際の値で書きます。
bad_mb=<labhub/fat:bad 크기(MB)>
good_mb=<labhub/fat:good 크기(MB)>
diff_mb=<두 값의 차>
(プレースホルダーは、labhub/fat:badのサイズ(MB)、labhub/fat:goodのサイズ(MB)、2つの値の差です)
参考
docker image inspect <이미지> | jq -r '.[0].Id'でイメージIDを、jq -r '.[0].Size'でバイトサイズを取得します(プレースホルダーはイメージ名です)。- 20MBのファイルは、
dd if=/dev/zero of=/big bs=1M count=20で作成できます。 - よくある間違い1: ステップ3で、ビルドの間にファイルを触るとIDが変わります。
- よくある間違い2: ステップ8のMBは、1048576バイトが基準です。小数点以下を切り捨てた整数で書いてください。
イメージのレイヤー数を数える
/root/build2を作成し、nginx:1.27-alpineのレイヤー数を数えて、/root/build2/nginx-layers.txtに数字だけを書きます。
inspectの結果のRootFS.Layersは配列です。jqで長さを求められます。
レイヤーごとのコマンドを覗く
alpine:3.20のレイヤーごとの履歴を、切り詰められないように/root/build2/alpine-history.txtへ保存します。
docker historyが表示する表は、イメージのconfigブロブのhistory配列そのものです。この環境では、skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'で同じ値を読み取ります。
各レイヤーがどのコマンドで作られ、どれだけの容量を占めるかを表示するサブコマンドがあります。切り詰められないように保存してください。
変更がなければ同じイメージ
/root/build2にdeps.txtとsrc/main.txtを作成し、2つのファイルをそれぞれCOPYするDockerfileを書きます。labhub/cache:v1でビルドしたあと、イメージIDを/root/build2/id1.txtに書き、何も変更せずにもう一度ビルドして、IDを/root/build2/id2.txtに書きます。2つの値は同じである必要があります。
ビルドを2回行い、それぞれのイメージIDをファイルに残してください。すべてのレイヤーがキャッシュにヒットすれば、結果のイメージも同一です。
前のレイヤーをわざと壊してみる
deps.txtの内容を変更してlabhub/cache:v2でビルドし、IDを/root/build2/id3.txtに書きます。id1.txtとは異なる必要があります。
いちばん上でCOPYするファイルを変更すると、その下がすべて再実行されます。新しいタグでビルドして、IDを比べてください。
変更頻度の順に配置する
/root/build2/ordered.Dockerfileを作成し、COPY deps.txtがCOPY src/より先に出るようにして、labhub/cache:v3でビルドします。
ほとんど変わらないもの(依存関係の一覧)を上に、頻繁に変わるもの(ソース)を下に置きます。採点では、2つのCOPYの行番号を比較します。
ビルドコンテキストから除外する
/root/build2/big.logを作成し、/root/build2/.dockerignoreにログファイルの除外ルールを入れます。コンテキスト全体を/ctxにコピーするDockerfileでlabhub/cache:v4をビルドすると、イメージ内に/ctx/deps.txtはあり、/ctx/big.logはない必要があります。
除外ルールのファイルは、ビルドコンテキストのルートに置きます。必要なファイルまで除外すると、ビルドが壊れます。
削除しても小さくならないことの証拠
20MBのファイルを作成したあと、次のRUNで削除するイメージをlabhub/fat:badとして、同じRUNの中で削除するイメージをlabhub/fat:goodとしてビルドします。
作成して次のRUNで削除したイメージと、作成して同じRUNの中で削除したイメージを、それぞれビルドし、サイズを比べてください。
測定値のまとめ
/root/build2/cache.mdに、次の3行を実際の値で書きます。
3つの値は、すべて実際のイメージサイズから計算します。1MB = 1048576バイトを基準に、整数のMBで書いてください。