なぜ cache-from を指定しなければいけないか
orisanoさんの https://t.co/tgT7fSDNPE これ読んだしコードも読んだけど、pull しておくだけではダメで cache fromで指定してあげないとキャッシュ効かない理由がわからない…。多分レイヤーをちゃんと理解してないな
— totem (@takhirata) 2019年12月2日
気になるツイートを見かけ, 僕も理由がわからなかったので調査してみました.
TL;DR
- build 時に cache-from をつけないと dockerd がメモリ上に保持している情報からしか子イメージを取得しない
- build では子イメージの情報をメモリ上に登録する
- pull は子イメージの情報をメモリ上に登録しない
- なので pull してきたイメージは cache-from をつけないとキャッシュ対象にならない
わかっていること
- docker buildしたイメージは cache-from をつけなくても cache が効く
- docker pullしたイメージは cache-from をつけないと cache が効かない
- イメージが等しい条件
- build の際にイメージが cache が使われる条件
- 共通の親イメージを持ち, イメージが等しいなかで最も新しいもの
- イメージが親イメージの情報のみを持つ連結リストの構造であること
- すべてのイメージは親をたどるとscratchという特殊なイメージにたどり着くこと
- dockerdが子イメージに伸びる辺をメモリ上に保持している
未知のこと
- build したイメージと pull したイメージの差分
- cache-from した場合としなかった場合の差
ソースコードを読む
moby/moby 2019/12/3時点でのHEAD (3152f9436292115c97b4d8bb18c66cf97876ee75) を参照する. https://github.com/moby/moby/tree/3152f9436292115c97b4d8bb18c66cf97876ee75
docker/cli 2019/12/3時点でのHEAD (54d085b857e954a8fc431a9a400f11bc3308efc1) を参照する. https://github.com/docker/cli/tree/54d085b857e954a8fc431a9a400f11bc3308efc1
imageStoreについて
- NewDaemon (docker/daemon/daemon.go) で imageService が作られる.
- NewFSStoreBackend で FSStoreBackend を $root/image/$graphDriver/imagedb に作る
- NewImageStore の中で restore を呼び fs にある情報から子イメージの情報を構築する
- 親から子に辺を張る(メモリ上)
pullの実装について知る 実装
- cliで認証, 検証, タグの解決を行う
- 認証情報, 解決されたタグの情報を /images/create に POST する
- postImagesCreate(import,pull共通)の処理が実行される (docker/api/server/router/image/image_routes.go)
- ImageService.PullImage の処理が実行される (docker/daemon/images/image_pull.go)
- ImageService.pullImageWithReference の処理が実行される (docker/daemon/images/image_pull.go)
- config.ImageStore は distribution.NewImageConfigStoreFromStore(ImageService.imageStore)
- config.ReferenceStore は ImageService.referenceStore
- config.DownloadManager は ImageService.downloadManager
- distribution.Pull が実行される (docker/distribution/pull.go)
- newPuller によって作られた v2Puller の Pull が実行される (docker/distribution/pull_v2.go)
- v2Puller の pullV2Repository が実行される (docker/distribution/pull_v2.go)
- pullV2Tag が実行される (docker/distribution/pull_v2.go)
- マニフェストを取得する
- pullSchema2 が実行される (docker/distribution/pull_v2.go)
- pullSchema2Layers が実行される (docker/distribution/pull_v2.go)
- config.ImageStore に digest が存在するか確認してある場合はそちらを使う
- pullSchema2Config で Config を取得する (docker/distribution/pull_v2.go)
- config.DownloadManager がある場合は RootFS を Download メソッドによってダウンロードする
- Config と Download した RootFS が等しいか検証する
- config.ImageStore に Put する
- ImageStore って名前だが ImageConfigStore
- Put すると image.Store に対して Create を実行する
- Create は子イメージの情報がない状態で追加する (docker/image/store.go)
- config.ReferenceStore がある場合は, tag の内容が更新されたかを確認して反映する
buildの復習
- 基本的な流れは here
- build 時に imageStore を触るタイミングはイメージを commit するとき
- container の id から取得した read write の layer の tar を読み込む
- layerStore に登録する
- 親イメージIDを取得する
- 今の状態を image.Store で Create する
- SetParent で parent という metadata ファイルに書く
- 親がいる場合は更新する, 元の親の children から自分の id を削除する
- 親の children に自分の id を登録する
cache-fromの挙動
- MakeImageCache (docker/daemon/images/cache.go) で builder.ImageCache が作られる
- cache-from が指定されていない場合は getLocalCachedImage を使う (docker/image/cache/cache.go)
- メモリ上の children だけを参照して,キャッシュの対象を見つける
- cache-from が指定されている場合は ImageCache.GetCache が呼ばれる (docker/image/cache/cache.go)
- メモリ上の children だけを参照して,キャッシュの対象を見つける
- キャッシュの対象が cache-from で指定したイメージの親イメージだった場合のみ使用される
- 親イメージを Store から取得する
- 親イメージと cache-from で指定したイメージで history が前方一致, RootFS の DiffID が前方一致しているイメージがキャッシュ対象になる
- メモリ上の children だけを参照して,キャッシュの対象を見つける
結論
- cache-from をつけない build はメモリ上で保持している children しか参照しない
- pull したイメージは親イメージ群に対して SetParent しないのでメモリ上で保持している children からは参照できない
- よって pull したイメージは cache-from をつけないと build 時のキャッシュ対象にならない
なんでこんな仕様になっているんでしょうか, 詳しい人教えて下さい