CircleCI
docker
Cache
docker-compose
Friday-IO

CircleCi 2.0 で Dockerイメージをキャッシュしビルドを爆速で行う

More than 1 year has passed since last update.

Friday I/O です。
株式会社ワムウでは、毎週金曜日は 興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日 としております。

前回記事のおさらい

前回の記事は こちら
CircleCI で Docker Compose を使用して環境のビルドからテストまで任せましょう、便利だね、という内容でした。
ですが Dockerイメージのプル・ビルドに時間がかかる というのが問題点としてありました。

今回のゴール

CircleCIが提供しているcache機能を利用してDockerのイメージ群をキャッシュ化し、ジョブの実行時間を短くしよう

アプリケーションの構成

前回の記事と同様です。

項目 名称 バージョン
Language PHP 7.1
Framework Laravel 5.4
DB MySQL 5.7

大まかな処理の流れ

  • キャッシュを作成するジョブ
    • Docker イメージをプル・ビルドする
    • サーバに存在するDockerイメージをキャッシュ化(キャッシュが既に存在する場合スキップ)
    • キャッシュしたファイルをCircleCiに保存
  • テストを実行するジョブ
    • キャッシュしたファイルをCircleCiから読み出す
    • Dockerイメージのロード
    • Docker Compose からテスト実行

結果

.circleci/config.yml
---

version: 2
jobs:
  generate_cache:
    machine: true
    steps:
      - checkout
      - restore_cache:
          key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths: ~/caches/images.tar
      - run:
          name: Check cache file, if not exists then pull images and generate cache.
          command: |
            if [ ! -f ~/caches/images.tar ]; then
              docker-compose pull sample-mysql
              docker-compose build
              mkdir -p ~/caches
              docker save $(docker images | awk 'NR>=2 && ! /^<none>/{print $1}') -o ~/caches/images.tar
            fi
      - save_cache:
          key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths: ~/caches/images.tar

  test:
    machine: true
    steps:
      - checkout
      - restore_cache:
          key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths: ~/caches/images.tar
      - run:
          name: Load Docker images
          command: docker load -i ~/caches/images.tar
      - run:
          name: Run tests
          command: |
            docker-compose run --rm sample-laravel-app /bin/bash -c '\
              cp .env.example .env && \
              composer install --no-interaction && \
              php artisan key:generate && \
              ./artisan migrate:refresh && \
              ./vendor/bin/phpunit -c phpunit.circle.xml'

workflows:
  version: 2
  build:
    jobs:
      - generate_cache
      - test:
          requires:
            - generate_cache

上記yaml についてのポイント

CircleCi の機能で workflows という機能と restore_cache save_cache という機能を使用しています。

  • workflows 公式ドキュメント
    ジョブをシーケンシャルに実行する仕組み
    上記のyaml の場合 generate_cache, test の順でジョブを実行する
  • restore_cache, save_cache 公式ドキュメント
    キーバリューでファイルの保存を扱ってくれる仕組み
    キー名には任意のファイルのチェックサムを利用できる 一度作成したキーに対する値(ファイル)は残り続ける。(いつ削除されるのか不明なので詳しい方は教えてください。)

作っていて詰まったところ

docker save というコマンドでイメージ群をtarファイル化しています。
docker save -o ~/caches/images.tar のみの場合、Repository名が無いイメージとしてtarファイルが生成されるらしく、気がつくのに時間がかかりました。
そのファイルを docker load 後、 docker-compose run をしても対象のイメージが無いので、イメージのPull が始まりキャッシュしたのに何で?状態でした。
docker save 周りの疑問はこちらの記事 に助けられまた。

実際のジョブ実行時間について

以下に画像を添付します。

  1. 一枚目がキャッシュ化対応前(前記事)のビルド
  2. 二枚目が未キャッシュ時のビルド(本記事の内容)
  3. 三枚目がキャッシュ済時のビルド(本記事の内容)

スクリーンショット 2017-09-08 18.24.59.png


スクリーンショット 2017-09-08 18.21.50.png


スクリーンショット 2017-09-08 18.24.41.png

1 と 3 を比べて 1の方が(キャッシュ化対応前の方が)早かった。。。
扱うイメージが少ないのでキャッシュ化の恩恵が薄い、workflows でジョブを分けているのでその分時間がかかるのが原因かもしれません。
あまり込み入った環境でない限りキャッシュする必要は無さそう。
案件で扱った時はビルドが 約2分早くなった のが個人的観測値です。(もちろん制作する規模・環境によりますが)
こんなやり方もあるんだと参考までに。

サンプルのGithub のリポジトリはこちら