CircleCI2.0のWorkflowsを導入してみた

概要

CircleCI2.0にWorkflowsという機能が導入されました。

他のビルドツールで既にあった、ビルドパイプラインと考えればわかりやすいと思います。 これを導入すると何が嬉しいかというと、

  • ビルドの実行フェーズを複数に分けることで、どこでFailしたかなどが視覚的に掴みやすい
  • 並列にjobを実行させることで通常の同期的なビルドフローよりも効率良く回せる
  • 具体的にはbundle installとかnpm installとかを並列に回して、共有ディレクトリに結果を入れたりできる
  • 色々並列にjobを回したあと、それを待ち受けて最終的な結果を同期的に受け取ることもできる
  • branchや言語のバージョンによって別々でテストを回したい時などに別個かつ並列にビルドを行える
  • 外部APIにつなぎにいく時にfailした場合などでは、Rerun from failedができるので、ケースによっては全工程をなぞる必要がなくなる

などのメリットがあります。

早速自分のこれまでのcircle.ymlの設定を変えて試してみました。元にした設定が載っているのはこの記事です。

今回はcircleci公式のリポジトリの設定を参考に作り直してみたので、割とデファクトスタンダードな書き方によっているはずです。 書き方の面での変更点としては、

  • referencesを使って定数をまとめているところ
  • primary imageにdocker clientのインストールコマンドを組み込んだので、それをyamlから除去したところ

です。

具体的な設定

version: 2.0

references:
container_config: &container_config
docker:
- image: timakin/golang-and-ruby-image
working_directory: ~/ansible_build

workspace_root: &workspace_root
/tmp/workspace

attach_workspace: &attach_workspace
attach_workspace:
at: *workspace_root

load_code: &load_code
run:
name: load code from workspace
command: |
# Move all files and dotfiles to current directory
mv /tmp/workspace/sample-ansible/* /tmp/workspace/sample-ansible/.[!.]* .

docker_container_cache_key: &docker_container_cache_key
key: docker-image-{{ .Branch }}-{{ checksum "Dockerfile" }}

jobs:
checkout_code:
<<: *container_config
steps:
- checkout
- run:
command: |
mkdir -p /tmp/workspace/sample-ansible
mv * .[!.]* /tmp/workspace/sample-ansible/
- persist_to_workspace:
root: *workspace_root
paths:
- sample-ansible

spec:
<<: *container_config
steps:
- *attach_workspace
- *load_code
- setup_remote_docker
- restore_cache:
<<: *docker_container_cache_key
name: Restore a docker image tarball cache
paths:
- /image_cache/ansible_test_image.tar
- run:
name: Build Docker image
command: |
if [[ -e /image_cache/ansible_test_image.tar ]]; then
cat /image_cache/ansible_test_image.tar | docker import - timakin/ansible_test_image
docker load --input /image_cache/ansible_test_image.tar
else
docker build -t timakin/ansible_test_image .
if [[ ! -e /image_cache ]]; then
mkdir /image_cache -m 755
fi
docker save -o /image_cache/ansible_test_image.tar timakin/ansible_test_image
fi
- save_cache:
<<: *docker_container_cache_key
name: Save a docker image tarball cache
paths:
- /image_cache/ansible_test_image.tar
- run:
name: Attach provisioning settings to the container for ansible-test
command: |
ANSIBLE_TEST_CONTAINER_ID=$(docker run -dit timakin/ansible_test_image /bin/bash)
docker cp ansible/ $ANSIBLE_TEST_CONTAINER_ID:/ansible
docker exec $ANSIBLE_TEST_CONTAINER_ID /bin/sh -c 'ansible-playbook /ansible/ci.yml -s -i /ansible/inventory_localhost -c local -v && cd /ansible/spec && /home/root/.rbenv/bin/rbenv exec bundle install && /home/root/.rbenv/bin/rbenv exec bundle exec rake spec'

workflows:
version: 2
build_and_test:
jobs:
- checkout_code
- spec:
requires:
- checkout_code

長いので端折って書くと、jobsで個別のフローを定義して、workflowsでそれをどんな依存関係で、どんな順番で行うかを定義しています。

具体的に何をしているかというと、サンプルとして2つのジョブを実行していまして、以下がその内容です。

checkout_code job

  • ソースをcheckoutしてくる

spec job

  • ansibleのテスト用のdocker containerを立てる
  • serverspecテストの実行

workflowsで特徴的な点

いくつかworkflowsを利用するにあたって設定を変更しましたが、syntax以外に下記の点が重要になってきます。

それぞれのジョブで必要な環境のセットアップ

<<: *container_configという書き方でビルド用のprimary imageを読み込んでいます。

今回は一個のimageを使っているので、全てのジョブで上記のコンテナ設定を読み込めばいいですが、

もし言語のバージョンごとにビルド用のprimary imageを変える時などは、上記設定を個別に行う必要があります。

ジョブ間での成果物共有

persist_to_workspaceというオプションで、checkout_codeジョブでcheckoutしてきたコードを、specジョブに共有しています。

これがないと、各ジョブ内部の環境は区別されて、せっかくフローの流れを定義しても意味がないので注意しましょう。

依存パッケージのインストールなどを並列で行う場合は、上記のオプション経由で一つのディレクトリにまとめた後、deployなりtest回すなりすればいい、ということになります。

ビルドの依存関係・順序の定義

以下の設定が、今回のworkflowsの導入によるメインの差分です。 build_and_testのところは、build_and_test_and_deployなり好きに設定してください。

requiresというオプションで、そのジョブにはどんな依存関係があるかを記載しています。 もしこれを設定しなければ、そのジョブに依存する次のジョブまでは、特に制限なく並列でビルドが行われます。

また、filtersというオプションでビルド用ブランチを指定したりできます。

注意点として、requiresのオプションを、specという文字列から半角スペース4つ分あけて記入しないと、ビルドがそもそも始まらなくてエラーも出ないなどのバグが起きます。特定に時間がかかって体力が消費されました。

workflows:
version: 2
build_and_test:
jobs:
- checkout_code
- spec:
requires:
- checkout_code

まとめ

上記の通り、少し設定を変えるだけでworkflowsの恩恵を得ることができます。

今回の例は2ステップしかないのと、別に並列に回すものでもなかったのですが、フロントエンド、バックエンドの両方で依存パッケージが多い、中・大規模なAPIのビルド & デプロイには重宝しそうです。