薄いブログ

技術の雑多なことを書く場所

kaniko が何をしているか, 何ができるか

TL;DR

  • kaniko を理解してない限りコンテナから出してはいけない.
  • kaniko を使っていればmulti stage buildだろうとCIのcacheについて余計なことを考えなくてよい (Dockerfileの書き方はcacheを意識して)

kaniko とは

github.com

kanikoはGoogleが作っているコンテナの中やKubernetes上で動くコンテナのbuilderです.

Cloud Buildなどで使うことができます.

kaniko の内部実装

早速ですが kanikoのbuildの詳細を調査するために以下から実装を読み進めました.

8c732f6f52bb334727194d090826e8dbd1df3793 における実装の詳細です.

github.com

Directory

/kaniko 以下が内部状態

  • (1) /kaniko/<image name>/... 依存しているイメージのlayersが完全に展開されている
  • (2) /kaniko/stages/<image name> --fromで依存しているイメージのtar
  • (3) /kaniko/stages/<stage id> ステージごとのtar
  • (4) /kaniko/<stage id>/... 別のステージから依存されているファイルを置く

DoBuild

  1. Dockerfileをパースし, ステージごとに分割する (--target まで)
    1. ステージ名を内部でidに変換する
    2. ベースイメージとして別のステージから参照されているかどうか調べる
  2. .dockerignoreを解釈する
  3. 外部の依存イメージをフェッチする
    1. ステージが持っているCOPYコマンドの--fromから外部のイメージに依存しているかを確認する
    2. 依存している場合はダウンロードしてkaniko内部のStageを保持するディレクトリ(2) にtarとして保存される
    3. kaniko内部のディレクトリ (1) にイメージを展開する
  4. ステージ間の依存ファイルを調べる
    1. COPYの--fromで他のステージから参照されているファイルを抽出する
  5. ステージごとにbuildする
    1. ベースイメージの解決を行う
      1. scratchかどうか
      2. 他のステージがベースイメージかどうか
      3. CacheDirが設定されている場合そこから取得する
      4. リモートから取得するようにする
    2. キャッシュの設定が有効な場合, 最適化を行う
      1. 親イメージのハッシュまたは直前のコマンドのcache key, コマンド名, COPY/ADD で参照されるファイル(中身,UID,GID,権限)のMD5 からcache keyを計算する
      2. RUNの場合はcache keyで外部ストレージに保存されているか確認する
      3. 保存されていない, CacheTTLを超えている場合はその場で最適化終了
      4. Cacheが有効な場合はRUNの内部実装をCacheに置き換える
      5. RUN/ADD/COPY/WORKDIR 以外のコマンドは実行される (cache可能性に影響するので)
    3. そのステージがCacheに失敗したRUNを持っているか,別のステージからファイルを参照されている場合にベースイメージを / に展開する
      1. 展開の際に /proc/self/mountinfoに含まれているpath,/kaniko,/var/run,/etc/mtab 以下に書き込まないようにしている
    4. 最初のsnapshotを取る
      1. / 以下のすべてのファイルをスキャンする
      2. メモリ上にある前のsnapshotの状態と比較する
      3. メモリ上にあってファイルが存在しないものはwhiteout listに追加する
      4. メモリ上にない, またはメモリ上とhashが違う場合はメモリ上の状態を更新する
        1. snapshotの際のhashはsnapshot modeによって異なる. (timeとfullがある)
        2. timeはmtimeだけを使う
        3. fullはmodeとmtime,uid,gid,ファイルの中身を使う
    5. 各コマンドを実行する
      1. RUNはprocessを実際にexecする
      2. SingleSnapShotモードの場合は最後のコマンドだけ以下を実行する
      3. RUNコマンド以外はコマンドから差分のファイルがわかるので差分だけメモリ上の状態を更新しtarとして書き出す
      4. RUNコマンドは / 以下のfull scanから差分を求めて(5.4と同様の処理) tarとして書き出す
      5. キャッシュが有効で, キャッシュが効かなかったRUNコマンドの結果のlayerを非同期でregistryにpushする
      6. layerをイメージに追加する
    6. layer cacheのregistryへのpushが終わるのを待つ
    7. 後のステージのベースイメージになっている場合はディレクトリ(3)にtarとして保存
    8. 別のステージからCOPY --fromで参照されているファイルをディレクトリ(4)にコピー
    9. /proc/self/mountinfoに含まれているpath,/kaniko,/var/run,/etc/mtab以外のファイルをすべて削除する
  6. 最終ステージをpushする

ベースイメージのcache

上の流れでは毎回, ベースイメージをfetchしてきます.

もしkanikoが永続化されたstorageを使えるならgcr.io/kaniko-project/warmer--cache-dirを使うことでベースイメージのcacheができます.

buildの高速化を目指すなら取り組んでみてもよいと思います.

終わりに

kanikoは--cache=trueにすることでシームレスに実行したすべてのレイヤーをpushします.

既存のbuildだと生成されたイメージ単位でしかpushすることができませんでした.

そのため一度に複数のイメージをまたぐmulti stage buildだと状態を保持できない環境でのcacheが困難でした. (cache-from地獄)

orisano/castage のようなcache-fromを自動生成するツールであったり, cacheの効果が最大化するようなステージのみを明示的にbuildしてcache-fromすることで対応していました.

kanikoのシームレスなpushによってこのような煩雑な作業から開放され,高速なCIにおけるdocker buildが実現されます.

イメージ単位でのpushではなくレイヤー単位でのpush, それをシームレスに行うことによるcacheに気がついたとき感動してしまいました. (この記事を書くきっかけ)

どこまでもlayerのみで考えられておりmulti stage buildにおけるcache問題が解決されており, 状態が保持できないCIのような環境下向けに作られていると思います.

注意

kaniko自体はprocessの隔離等は行いません, コンテナ上で動かすこと前提としています.

実行される環境のファイルをめちゃくちゃに書き換えます. 間違えてもローカルでは実行しないでください.

まとめ

kanikoは上位のコンテナ技術に依存した作りになっています.

  • 自分ではプロセスの隔離などは行わずos.Execします
  • kanikoは隔離されている前提なので / にtarを展開したり, / から殆どのファイルを削除したりします
  • cacheの状態を簡単にremoteに置くことができる仕組みになっています
  • cacheのpushはシームレスに行われ, 最小単位はlayerです. multi stage buildでの悩みが解決されます