CircleCIの実行を速くした
先週仕事でやったのをメモします。 CI力が低いので、記事を公開することでツッコミをもらうのが目的です。
モチベーション
弊社ではCIの実行が1回あたり20分ほどかかっています。 これはわりとストレスなので速くしたいです。幸いにも削れそうなところが2つ見つかったので削ってみました。
Shallow Clone
CircleCIではデフォルトのcheckoutを使用するとリポジトリの全体をcloneしてきます。 これを回避するためにshallow cloneを導入しました。
まず、次のようなコマンドを定義します。
commands: shallow-clone: description: 'Git clone shallowly' steps: - run: name: 'shallow clone' command: | set -x echo "machine github.com login $GITHUB_ACCESS_TOKEN" > ~/.netrc cd /home/ubuntu/kibela repository_https_url=$(echo $CIRCLE_REPOSITORY_URL | sed -e "s|git@github.com:|https://github.com/|") git clone --depth=1 $repository_https_url . if [[ "$CIRCLE_BRANCH" =~ ^pull\/* ]]; then FETCH_SRC="${CIRCLE_BRANCH}/head" else FETCH_SRC=$CIRCLE_BRANCH fi git fetch --depth=10 origin $FETCH_SRC git reset --hard $CIRCLE_SHA1 || (echo "The branch was updated after the build was executed. Please rebuild." 1>&2; false)
そして、定義したコマンドをjob
定義のsteps
の中で使います。
jobs: build: steps: # - checkout を以下に置き換え - shallow-clone
この設定は次の記事を参考にしています。
- CircleCI 2.0 への移行の軌跡 - Sunsetting 1.0 - - GameWith Developer Blog
- CircleCI 2.0に移行して新機能を活用したらCIの実行時間が半分になった話 - クラウドワークス エンジニアブログ
- CircleCI Orb をいくつか作った話 - tech.guitarrapc.cóm
GameWithさんの設定をベースにし、うまく動かないところをguitarrapcさんのOrbを参考に修正しています。
今まで1~2分かかっていたcloneが、これによって3秒程度で終わるようになりました。
なお今回は試しませんでしたが、shallow cloneをする他にも、gitリポジトリをCircleCIのキャッシュに突っ込むという方法もあるようです。
bundle install結果のclean
以前からbundle install
の結果をキャッシュしていました。
ところがキャッシュをfetchするのに2分ほどかかっていました。
時間がかかっている原因は、bundle install
した結果をcleanしていないためでした。
つまり、Gemの更新があると古いバージョンのgemと新しいバージョンのgemの両方がキャッシュされてしまいます。
これを繰り返すと、どんどんキャッシュサイズが膨れていってしまいます。
これを防ぐためにbundle install
に--clean
オプションを渡すようにしました。
- restore_cache: name: "Restore Cache" keys: - rubygems-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "Gemfile.lock" }} - rubygems-{{ .Environment.COMMON_CACHE_KEY }}- - run: name: "Install" command: | bundle install --clean --path=vendor/bundle/ --jobs=4 --retry=3 --without=production --frozen - save_cache: name: "Save Cache" key: rubygems-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "Gemfile.lock" }} paths: - vendor/bundle/
これでbundle install
後に必要なくなったgemを消してからキャッシュに保存されるようになります。
この修正は以下の記事を参考にしました。
- Caching Dependencies - CircleCI
To prevent this behavior, add a step that cleans Bundler before restoring dependencies from cache.
- これ、"before restoring dependencies"じゃなくて"after restoring dependencies"か"before saving dependencies"じゃないと意味が通らないような?
- bundle install には --clean を指定する (特に Circle CI では) | Born Too Late
- CircleCI 2.0に移行して新機能を活用したらCIの実行時間が半分になった話 - クラウドワークス エンジニアブログ
- 先程もリンクしたクラウドワークスさんの記事です。
COMMON_CACHE_KEY
はここから借用しました。
- 先程もリンクしたクラウドワークスさんの記事です。
この修正によって、2分ほどかかっていたキャッシュのfetchが20秒ほどまで減りました。
(ところで、--clean
オプションがdeprecatedじゃないか確認してませんでした。手元で確認しづらいし気になる)
本当に基本的な修正しかしていませんが、これでCIの実行が多少速くなりました。 本当はもっともっと速くなってほしいですが、ここから先は金で殴るしかないですかねえ。
ツッコミお待ちしています。