Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CircleCIでLayer Cachingを使わずにdocker buildを高速化する

CircleCIでLayer Cachingを使わずにdocker buildを高速化する

【東京】CircleCI ユーザーコミュニティ ミートアップ #9
https://circleci.connpass.com/event/242577/

URLリンクも有効なGoogle Slide版は
https://docs.google.com/presentation/d/1UpRyHsdx4bpBWE9P0VWznVCcupmjVWfmEXRoMbcAeIQ/edit?usp=sharing

Kenta Kase

April 21, 2022
Tweet

More Decks by Kenta Kase

Other Decks in Programming

Transcript

  1. CircleCIでLayer Caching
    を使わずにdocker buildを
    高速化する
    加瀬健太 @Kesin11
    【東京】CircleCI ユーザーコミュニティ ミートアップ #9
    2022/04/21

    View Slide

  2. 自己紹介
    所属:DeNAのSWETグループ(SoftWare Engineer in Test)
    Twitter: @Kesin11, GitHub: Kesin11
    CI/CDサービスのマニアなので有名なサービスは大体チョットワカル
    主な業務
    モバイルゲーム開発のJenkins構築、運用、パイプライン構築
    社内CircleCI Serverの運用チーム

    View Slide

  3. docker build 毎日しますよね?

    View Slide

  4. ビルドは早いほうが嬉しい
    CircleCIではどうしますか?

    View Slide

  5. https://circleci.com/docs/ja/2.0/docker-layer-caching/

    View Slide


  6. View Slide

  7. 完全に正しいが・・・
    実際どうしてますか?
    DeNAではCircleCI Server(オンプレ)なので実質無料
    docker buildする場合にはバンバン使ってもらっている

    View Slide

  8. Layer Cachingのお値段
    ドキュメントによると1ジョブごとに300クレジット
    Freeプランの無料枠は30,000クレジットなので100ビルド相当
    Docker/Linux Medium(2CPU, 4GB)が10クレジット/分
    Layer Cachingを有効にするだけで30分相当のクレジット
    キャッシュによってビルド時間自体を減らせる
    高いと見るか安いと見るか・・・

    View Slide

  9. Layer Cachingに頼らずにビルドを
    早くする方法はないだろうか?🤔

    View Slide

  10. 発表内容
    docker buildの各種キャッシュ方式の解説
    CircleCI上でのビルド実験
    実験の結果の考察
    おまけCircleCIでdocker buildする際の重要ポイント

    View Slide

  11. まずbuildkitの有効化
    DOCKER_BUILDKIT: 1
    マルチステージビルドが並列ビルドされるので早くなる
    もう基本中の基本なので語ること無し
    とはいえ最新のcimg/base:stableを使っていても明示的に環境変数でONに
    する必要があるので忘れないように

    View Slide

  12. Inline cache
    docker build --build-arg BUILDKIT_INLINE_CACHE=1
    pushするイメージにキャッシュ用のメタデータを書き込む
    次回ビルドでキャッシュとしてpush済みのイメージを参照する
    マルチステージビルドにおいては普通に使ってもキャッシュが有効になる場面
    はほぼ無い
    忘れてOK

    View Slide

  13. 補足:buildx
    この後の各種キャッシュ機能はbuildkitの機能を使います
    dockerからbuildkitのフル機能を使うにはdocker buildxが必要
    とりあえずdocker buildx buildを使うのが最新のビルド方法という認識で大
    丈夫です

    View Slide

  14. RUN –mount=type=cache
    (正式な名称はよくわからない。便宜上Mount cacheと呼びます)
    DockerfileのRUNに追加する
    RUN --mount=type=cache,target=/root/.cache/go-build go build …
    RUN中にディレクトリ指定でマウントしたボリュームを次のビルドで使い回す
    パッケージやビルドキャッシュを使い回せると次のビルドが早くなる

    View Slide

  15. Registry cache
    docker buildx build -f Dockerfile \
    --cache-to=type=registry,mode=max,ref=$CACHE_IMAGE:cache \
    --cache-from=type=registry,ref=$CACHE_IMAGE:cache \
    -t $IMAGE:latest .
    ビルドするイメージとは別にキャッシュ用レイヤーを別イメージとしてpushする
    mode=maxにするとマルチステージの中間も含めて全レイヤーがpushされる
    マルチステージビルドでもキャッシュを有効に使える

    View Slide

  16. Registry cache
    docker buildx build -f Dockerfile \
    --cache-to=type=registry,mode=max,ref=$CACHE_IMAGE:cache \
    --cache-from=type=registry,ref=$CACHE_IMAGE:cache \
    -t $IMAGE:latest .
    キャッシュとしてのイメージはdocker run可能なものではないので混ざらない
    ようにイメージ名を別にした方が安全
    CircleCIはコンテナレジストリの機能を提供していないので
    GitHub/AWS/GCPあたりのレジストリを自分で用意する

    View Slide

  17. Local cache
    docker buildx build -f Dockerfile \
    --cache-to=type=local,mode=max,dest=/tmp/docker_cache \
    --cache-from=type=local,src=/tmp/docker_cache \
    -t $IMAGE:latest .
    キャッシュ用レイヤーをローカルに書き出す
    キャッシュとしての効果はRegistryと同じ
    mode=maxの効果も同じ

    View Slide

  18. Local cache
    docker buildx build -f Dockerfile \
    --cache-to=type=local,mode=max,dest=/tmp/docker_cache \
    --cache-from=type=local,src=/tmp/docker_cache \
    -t $IMAGE:latest .
    CircleCIのキャッシュ機能と併用して次回のビルドで使い回せる
    注意:書き出されるディレクトリの中身は上書きではなく太り続ける
    キャッシュ戦略をミスると古いファイルも残り続けてしまい、無駄なネット
    ワーク転送が増え続けて本末転倒になってしまう

    View Slide

  19. 番外:dockerでビルドしない方法
    何を言っているのか??

    View Slide

  20. 番外:dockerでビルドしない方法
    CircleCIのremote dockerはスペックが固定されている(2core, 8GB)
    docker build時にコンパイルなどCPUをぶん回す場合は頭打ちになる
    (別解としてハイスペックmachine executorを使う方法もある)
    最大で20CPU, 40GBまでスペックを上げられるCircleCIなのにもったいない

    View Slide

  21. 番外:dockerでビルドしない方法
    ハイスペックなコンテナ内でビルドしてCOPYだけでイメージを焼く
    例:go buildでバイナリを生成、DockerfileはCOPYだけ
    Dockerのキャッシュの代わりに普段どおりCircleCIのキャッシュを使う
    Dockerが担保していた再現性のあるビルドは担保されなくなるので注意
    とはいえCircleCIはコンテナでビルドしているので元々再現性は高い

    View Slide

  22. 実験
    CircleCI-Public/circleci-cliをforkしてdocker build中でバイナリをビルド
    するようなDockerfileを作成
    中身はほぼ go mod, go build
    これをCircleCI上でビルドさせる
    https://github.com/Kesin11/my-circleci-docker-build-sandbox
    詳細が気になる人はDockerfileや.circleci/config.ymlを参照

    View Slide

  23. 実験
    キャッシュの有無で差が生じやすいようにsmall(1core, 2GB)
    以下を比較
    キャッシュを使わない通常のdocker build(DOCKER_BUILDKIT=1)
    Mount cache
    Registry cache
    Local cache
    dockerでビルドしない

    View Slide

  24. 実験
    キャッシュが有効なケースと無効なケースを意図的に作り出す
    有効なケース
    CircleCIで再ビルド。何も変更しないのでキャッシュがフルに効く
    無効なケース
    go.modに適当なコメント行を追加し、不正なキャッシュを生成
    わざと不正なキャッシュを参照させることで意図的に無効化させる

    View Slide

  25. 結果
    それぞれのケースで5回計測
    横軸はジョブ全体の時間(秒)
    remote dockerの立ち上げに0-90秒
    程度の振れ幅があったため、最小値、中
    央値、最大値を表示
    評価は中央値で行う(ソートも)
    プロジェクト構成によって結果はかなり変
    わるのであくまで参考程度

    View Slide

  26. 考察 Mount cache
    同一マシンにしかキャッシュが保持され
    ない仕組み
    CircleCIは毎回マシンが変わるので
    キャッシュの意味がないことは予想通り
    デメリットもなさそうなのでDockerfileに
    書いておくとローカルマシンでのビルドが
    早くなるメリット

    View Slide

  27. 考察 Registry cache
    キャッシュが有効な場合はLocalとほぼ
    同等の早さ
    ネットワーク的にLocalの方が有利と予
    想していたので意外だった
    Localはrestore_cache, save_cache
    のキーとパス設計が複雑になりがちなの
    でRegistryの方が簡単

    View Slide

  28. 考察 Local cache
    キャッシュ無効の場合に特に遅い
    ログを見るとキャッシュの書き出しに時間
    がかかっていた
    CircleCIはremote dockerなのでネット
    ワーク越しのIOであり実際にはローカル
    ではないので時間がかかる?
    書き出されたサイズは約1.5GBでこれを
    CircleCIのキャッシュに送るのも時間が
    かかっていた

    View Slide

  29. 考察 dockerでビルドしない
    キャッシュが無効なら早い、有効なら
    RegistryやLocalに負ける
    レイヤーキャッシュがフルに効く場合は
    dockerはビルドをほぼスキップしている
    ため
    良く言えばキャッシュの有無に依らずに
    安定している

    View Slide

  30. 自分なりの結論
    buildxを有効にしてRegistry cache + Mount cacheが無難でシンプル
    ただしキャッシュ用イメージの分だけコンテナレジストリに費用が発生するのを
    お忘れなく
    キャッシュ用イメージのタグ戦略
    featureブランチでは--cache-fromをmain、--cache-toは無し
    キャッシュ書き出しの時間を節約
    mainブランチでは--cache-fromをmain、--cache-toもmain
    CIでよく使うキャッシュ戦略と同一

    View Slide

  31. おまけ:CircleCI上のbuildxのセットアップ
    クライアントとリモートのdockerのバージョンがある程度新しい必要がある
    クライアント
    cimg系のコンテナであればよほど古くない限り多分大丈夫
    リモート(デフォルトが17.09.0-ceなのはそろそろ更新をお願いしたい・・・)
    setup_remote_docker:
    version: 20.10.11
    CircleCI Serverの場合
    remote docker用のVMのdockerでバージョンが固定されているため、古い場合は管
    理者にAMIのアップデートをしてもらう必要がある

    View Slide

  32. おまけ:CircleCI上のbuildxのセットアップ
    実はRegistry cacheを使うには以下のコマンドでbuildxの設定が必要
    docker buildx create --use
    ローカルでは問題ないがCircleCIだと以下のようなエラーになった
    error: could not create a builder instance with TLS data loaded from
    environment. Please use `docker context create ` to create
    a context for current environment and then create a builder instance with
    `docker buildx create `
    ワークアラウンドとして以下のようにセットアップすれば動く
    docker context create circleci
    docker buildx create --use circleci
    参考
    https://support.circleci.com/hc/en-us/articles/360058095471-How-To-Use-Docker-Buildx-in-Remote-Docker-
    http://www.er.crichardson.com/post/microservices/2022/01/18/build-multi-arch-docker-images-circleci.html

    View Slide

  33. おまけ:.dockerignoreが超重要
    何気ない `COPY . .` がキャッシュを無効化する
    docker build時にディレクトリの中身がcontextとしてビルダーに送られる
    contextも完全に一致しないとレイヤーキャッシュが効かない
    docker buildするのに本来は不要なファイルが含まれていないか?
    .git/の中身はコミットされるごとに変わる
    .circleci/config.ymlは当然CircleCIの設定を変更すると変わる
    .gitは気をつけていたが、.circleciを忘れていて実験を何度かやり直すハメになり
    ました・・・キャッシュの振る舞いが変だと思ったら.dockerignoreをチェックしよう

    View Slide

  34. 参考
    RUN —mount=type=cacheの公式ドキュメント(buildkit)
    buildkitのREADME(Cacheの項目)
    docker buildx buildの公式ドキュメント
    How To Use Docker Buildx in Remote Docker?(CircleCI Support
    Center)
    BuildKitでイメージをビルドする(buildkitとbuildxの解説)
    Dockerイメージのビルドで使うキャッシュの種類 - レイヤーキャッシュ、BuildKit
    の--mount=type=cache

    View Slide