CircleCI速くした(9分→4分)

CircleCIDocker開発環境

環境

  • laradockで開発しているLaravelプロジェクト

CIで時間かかる処理

  • npmパッケージ取得
  • composerパッケージ取得
  • dockerイメージビルド

キャッシュによる高速化

  • キャッシュの仕組みがある

    • CircleCI 2.0からの機能らしい
    • save_cache

      • 依存ライブラリ等に名前を付けて、キャッシュを生成できる
    • restore_cache

      • 名前を指定してキャッシュを読み出す
      • 無ければ何もしない
  • キャッシュの名づけには、パッケージ管理ファイルのチェックサム等を使用する

    • シノニムは考えないことにする

npmパッケージ

.circleci/config.yml

      - run: cp ./src/package-lock.json ./src/package-lock.json.bak
      - restore_cache:
          keys:
            - npm-v1-{{ checksum "./src/package-lock.json.bak" }}
          paths:
            - ./src/node_modules            
      - run:
          name: Install JavaScript Packages
          command: |
            if [ ! -d ./src/node_modules ]; then
              pushd ./laradock
              docker-compose exec workspace npm install
            fi
      - save_cache:
          key: npm-v1-{{ checksum "./src/package-lock.json.bak" }}
          paths:
            - ./src/node_modules
  • package-lock.jsonのチェックサムをもってして同一性を検証
  • ただし、npm installpackage-lock.jsonが書き換わるようなのでバックアップを使用している
  • キャッシュ使用時はnpm installしない

composerパッケージ

.circleci/config.yml

      - run: cp ./src/composer.lock ./src/composer.lock.bak
      - restore_cache:
          keys:
            - composer-v1-{{ checksum "./src/composer.lock.bak" }}
          paths:
            - ./src/vendor
      - run:
          name: Install PHP Packages
          command: |
            if [ ! -d ./src/vendor ]; then
              pushd ./laradock
              docker-compose exec workspace composer install
            fi
      - save_cache:
          key: composer-v1-{{ checksum "./src/composer.lock.bak" }}
          paths:
            - ./src/vendor
  • おなじ

dockerイメージビルド

.circleci/config.yml

      - run:
          name: Archive Build Context (for checksum)
          command: tar --mtime="2000-01-01 00:00Z" -cvf laradock.tar laradock
      - restore_cache:
          keys:
            - laradock-v1-{{ checksum "./laradock.tar" }}
          paths:
            - ~/caches/images.tar
      - run:
          name: Docker Image Rebuild
          command: |
            if [ ! -f ~/caches/images.tar ]; then
              pushd laradock
              docker-compose build workspace php-fpm nginx mysql
            fi
      - run:
          name: Save Docker Images
          command: |
            if [ ! -f ~/caches/images.tar ]; then            
              mkdir ~/caches
              docker image save $(docker image ls --format="{{.Repository}}" | grep laradock_) -o ~/caches/images.tar
            fi
      - save_cache:
          key: laradock-v1-{{ checksum "./laradock.tar" }}
          paths:
            - ~/caches/images.tar
      - run:
          name: Load Docker Images
          command: docker image load -i ~/caches/images.tar
      - run: docker image ls
  • 先の例ほど単純ではないので分割

キャッシュ名生成部分

      - run:
          name: Archive Build Context (for checksum)
          command: tar --mtime="2000-01-01 00:00Z" -cvf laradock.tar laradock
  • docker-composeのビルドコンテキストはlaradock/ディレクトリ全体
  • CircleCIのchecksumは単一ファイルにしか対応していない。はず。
  • なので*.tarに固めてからchecksumに渡す必要がある
  • 下記では駄目:
tar -cvf laradock.tar laradock
  • tarコマンドは内容物のタイムスタンプを保存する
  • CircleCIがGitHubからコードをcloneした時刻によってタイムスタンプは変わるため、laradock.tarのチェックサムが毎回変わってしまう
  • ので、タイムスタンプを固定値にする:
tar --mtime="2000-01-01 00:00Z" -cvf laradock.tar laradock

キャッシュ読み出し部分

      - restore_cache:
          keys:
            - laradock-v1-{{ checksum "./laradock.tar" }}
          paths:
            - ~/caches/images.tar
  • mkdir ~/cachesはしなくていいみたい

dockerイメージビルド・ファイル書き出し部分

      - run:
          name: Docker Image Rebuild
          command: |
            if [ ! -f ~/caches/images.tar ]; then
              pushd laradock
              docker-compose build workspace php-fpm nginx mysql
            fi
      - run:
          name: Save Docker Images
          command: |
            if [ ! -f ~/caches/images.tar ]; then            
              mkdir ~/caches
              docker image save $(docker image ls --format="{{.Repository}}" | grep laradock_) -o ~/caches/images.tar
            fi
  • キャッシュを読み出せた場合は何もしない

    • ~/caches/images.tarの存在により判定
  • docker-compose buildで必要なイメージをビルドする
  • laradock_から始まるイメージのみファイルに書き出す

    • docker image ls --format="{{.Repository}}"はイメージ名一覧を得る
  • 【issue】laradock/.envCOMPOSE_PROJECT_NAME=laradockを変えると機能しなくなる

キャッシュ保存

      - save_cache:
          key: laradock-v1-{{ checksum "./laradock.tar" }}
          paths:
            - ~/caches/images.tar
  • どうってことはない

イメージ読み込み

      - run:
          name: Load Docker Images
          command: docker image load -i ~/caches/images.tar
      - run: docker image ls
  • キャッシュファイルからイメージ読み込む
  • キャッシュミスして1からビルドした場合も通るので効率悪い
  • けど判定・分岐が面倒くさい&頻繁なケースではないので手を抜いた

高速化による弊害と対策

  • docker-compose up -dでmysqlコンテナ起動後、mysqldの準備完了を待たずして後工程が容赦なく進むようになった
  • Composeの起動順番を制御

Compose はコンテナの準備が「整う」まで待ちません。 (つまり、特定のアプリケーションが利用可能になるまで待ちません) 単に起動するだけです。

  • DBが絡むテストでSQLSTATE[HY000] [2002] Connection refusedが出てしまうように
  • wait-for-itを用いてmysqldの準備完了を待つようにした
      - run:
          name: PHP CI (waiting for the invocation of mysqld)
          command: docker-compose exec workspace  /root/etc/wait-for-it.sh mysql:3306 -- composer run ci
          working_directory: ./laradock