この記事は CircleCI Advent Calendar 2015 - Qiita の3日目の記事です。
2日目はtakusさんによる「AWS CodeDeploy と CircleCI で Docker コンテナを自動デプロイ」でした。
CircleCI Advent Calendar 2015 の 2 日目を書きました。 / “AWS CodeDeploy と CircleCI で Docker コンテナを自動デプロイ · takus's blog” https://t.co/3WI7JzoYuq
— takus (@takus_ja) December 1, 2015
3日目にして早くも2つ目です。後6枠ほど空いているようなので我こそはという方は是非お願いします。
さて、今回はCircleCIのキャッシュについての記事です。
そもそもCircleCIのキャッシュとは
CircleCIはビルドする度にコンテナ(Dockerコンテナではなく、CircleCI独自のコンテナ)が起動し、その中で一連のビルドやらテスト等のCIが行われ、終了するとそのコンテナは破棄されます。
つまりCircleCIのビルドは揮発性を持つということなので、コンテナの中で毎度ライブラリのダウンロードやその他ツールのインストールを行う必要があるということです。
とはいえ、ライブラリのダウンロードのようなコストのかかる処理を毎回行っていては効率的にCIを回すことができません。そこでCircleCIでは、cache_directoriesという仕組みが用意されています。cache_directoriesの使い方はコチラに書かれていますが、習うより慣れろなので実際にどのように設定して、どのような挙動をするかを確認してみましょう。
検証プロジェクト
キャッシュの挙動を検証するためのプロジェクトを作りました。
ちなみにビルドの内容はこちらから見れます。 Continuous Integration and Deployment
中身はcircle.ymlとREADMEとゴミファイルです。とりあえずキモとなるcircle.ymlはこんな感じ。
machine:
timezone: Asia/Tokyo
dependencies:
cache_directories:
- ~/advent
override:
- mkdir -p ~/advent
- |
if [ -e ~/advent/build.log ]; then
cat ~/advent/build.log
fi
- echo "`date +'%Y-%m-%d %H:%M:%S'` branch=$CIRCLE_BRANCH build_number=$CIRCLE_BUILD_NUM" >> ~/advent/build.log
- |
if [ -e ~/advent/build.log ]; then
cat ~/advent/build.log
fi
test:
override:
- echo testtest!
基本的にdependenciesフェーズだけ見てもらえればよいです。簡単に解説すると・・・
~/advent
を **cache_directories** として設定する~/advent
ディレクトリがなければ作成する~/advent/build.log
が存在すればその内容を表示する- 時間とブランチ名、ビルド番号を
~/advent/build.log
に**追記**する - 確認用にもう一度
~/advent/build.log
を表示する
という流れです。このdependenciesフェーズ後に、 ~/advent
ディレクトリはキャッシュとして保存され、次回以降のビルド時にリストアされます。実際の動きは以下のような感じになります。

リストアされたbuild.logにそのビルドのログが追記されたことがわかります。では具体的な動きの検証を次の項から。
1. masterから派生ブランチfeature/advent1を作る
まずはmaster
からfeature/advent1
という派生ブランチを作ります。ブランチができたらそのままGitHubにpushします。

リストアされたログの末尾はmaster
の最終ビルドを示しており、このビルドではfeature/advent1
のログが追記されています。つまり、派生ブランチビルドのキャッシュはmaster
の最終ビルドでキャッシュされたものが利用されていることがわかりますね。
2. 派生ブランチfeature/advent1をリビルドする
次に、feature/advent1
をリビルドしてみるとどうでしょうか。

リストアされたログの末尾が feature/advent1
ですね。つまり、前回のfeature/advent1
のキャッシュがリストアされていることがわかります。
3. feature/advent1から派生ブランチfeature/advent2を作る
今度はfeature/advent1
から派生ブランチfeature/advent2
を作ります。これもそのままGitHubにpushします。

リストアされたログの末尾がmaster
ですね(; ´∀`)。feature/advent1
の最終ビルドのキャッシュをリストアして欲しいなーという淡い期待がありましたが、どうやら新規のブランチのビルドはmasterの最終ビルドキャッシュを利用するということなのでしょう。
4. feature/advent1をmasterにマージする
新規ブランチの動きがわかったので、今度は作成したブランチをmaster
にマージしてみましょう。PRしてマージ(このとき、リモートブランチは削除してます)

PRがマージされると、master
のビルドが走ります。

リストアログを見ると、master
のみが存在します。master
の最終ビルドキャッシュが利用されており、feature/advent1
でのビルドキャッシュを持ち込んだりといったことはないようです。まあ、自然ですね。
5. 新たにmasterからfeature/advent1ブランチを作る
個人的には一番気になっていたところ。先ほどのfeature/advent1
がmaster
にマージされ、master
に適当にコミットを追加した状態から新たにfeature/advent1
を作ってpushします。

リストアログを見ると、以前作ったfeature/advent1
ビルドのログがリストアされているようです。新たにmaster
から派生したブランチではありますが、同名のブランチのビルドが過去に存在した場合は前回ビルドのキャッシュを利用するということですね。
CircleCIでは、リモートブランチを消して新たに同名のブランチを作っても、履歴はずっと残り続ける仕様です(しれっと消す機能追加されてたりしないですよね?)。以下を見ると#11
は再作成した方の最初のビルドですが、以前のビルドが見事に残っています。CircleCIのこの仕様からすると自然な動きかもしれません。
とはいえ、それなりの規模の開発をやってると、実は同名ブランチが存在したってことが結構出てきます。このケースでの弊害は、新しく作ったブランチなのにnpmやmaven、Gradle等の古いキャッシュに頼ったビルドになってしまうことです。
本来失敗するはずのビルドが古いキャッシュによって成功してしまったり、またその逆もあったりと何かとトラブルを引き起こします(ウチのプロジェクトでもたまに発生して、消耗するポイントになっています)。
まとめ
というわけでcache_directoriesの挙動を色々と試してみました。CircleCIを長く触ってきて、たぶんそうなんだろうなーと漠然と思っていたキャッシュの挙動について今回ようやくちゃんと検証できましたw
cache_directoriesを利用する場合(定義しなくてもnpmとかはデフォルトで効いてますからね!)は、以下の挙動を抑えておくと消耗せずにCircleCIを利用できると思われます。
- 派生ブランチビルドのキャッシュは
master
の最終ビルドでキャッシュされたものが利用される - 派生ブランチからさらに派生したブランチのビルドでも、
master
の最終ビルドでキャッシュされたものが利用される master
ビルドで利用されるキャッシュは基本的にmaster
の最終ビルドでキャッシュされたもの- 過去に同名のブランチのビルドが存在する場合、それが再利用されてしまうので適度に**without cache**ビルドでキャッシュクリアビルドをする
てか感じです。明日4日目はminodiskさんです。