CircleCI2.0の私的ベストプラクティス

2018年8月30日

CircleCI

CircleCI 2.0 を使うようになって 1 年くらい経ちました。ノウハウがたまってきたのでまとめておきます。

目次

  1. Docker Executorを使う
  2. ダウンロードしたファイルをキャッシュする
  3. ビルドを並列化する
  4. Workflows はできるだけ使わない

Docker Executorを使う

CircleCI には実行環境として Docker ExecutorMachine Executor がありますが、可能な限り Docker Executor を利用するようにしています。

Docker Executor はローカル開発用の CLI ツールを利用できる

Docker Executor は ローカル開発用の CLI ツール を利用できます。ワークフローやキャッシュに対応していなかったりしますが、大抵のことをローカル環境で試すことができます。
.circleci/config.yml があるディレクトリで、こんな感じで使います。オプションでブランチや環境変数を指定できます。

$ circleci build \
  --branch develop \
  -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
  -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
  -e AWS_REGION=${AWS_DEFAULT_REGION}

構文エラーのチェックもできます。

$ circleci config validate

Docker Executor はビルドのリソースを変更できる

Machine Executor のリソースは固定ですが Docker Executor は resource_class を使用してビルドのリソースをある程度コントロールできます。

以下のように resource_class を指定します。

version: 2
jobs:
  build:
    docker:
     - image: circleci/ruby:2.5
    resource_class: medium+

Machine Executor はビルドの開始に時間がかかることがある

Docker Executor と Machine Executor の比較にも書かれていますが、Machine Executor はビルドの開始までに 1 分くらい待たされることがあります。多くの場合はすぐにビルドが開始されるので、CircleCI の混み具合によるようです。

ダウンロードしたファイルをキャッシュする

ビルドに必要なファイルを毎回ダウンロードしているとビルドが遅くなるので、一度ダウンロードしたファイルはキャッシュして再利用するようにします。

パッケージマネージャー編

以下は Ruby の Gem パッケージをキャッシュする例です。

version: 2
jobs:
  build:
    steps:
      - checkout
      # Restore cache
      - restore_cache:
          key: bundle-{{ checksum "Gemfile.lock" }}
      # Install package
      - run: bundle install --path vendor/bundle
      # Save cache
      - save_cache:
          key: bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle

checksum "Gemfile.lock" のようにしてキャッシュのキーを作成することで、Gemfile.lock ファイルに変更があった場合はキャッシュを利用せず、新しくファイルを取得するようになっているのがポイントです。checksum 以外にも利用できる変数は Using Keys and Templates から確認できます。言語ごとのサンプルは Caching Dependencies Bundler(Ruby) から確認できます。

wget curl 編

jq のようにパッケージマネージャーを使用せず wget で取得したファイルをキャッシュする例です。

まず、.jq-version ファイルにバージョン番号を書いて Git にコミットしておきます。

$ cat .jq-version

1.5

.jq-version ファイルの checksum をキーに、ダウンロードしたファイルをキャッシュします。

version: 2
jobs:
  build:
    steps:
      - checkout
      # Restore cache
      - restore_cache:
          name: Restore jq
          keys:
            - jq-{{ checksum ".jq-version" }}
      # Download package
      - run:
          name: Download jq
          command: |
            if [ ! -e /usr/local/bin/jq ]; then
              version=$(cat .jq-version)
              wget https://github.com/stedolan/jq/releases/download/jq-${version}/jq-linux64 -O /usr/local/bin/jq
              chmod +x /usr/local/bin/jq
            fi
      # Save cache
      - save_cache:
          name: Save jq
          key: jq-{{ checksum ".jq-version" }}
          paths:
            - "/usr/local/bin/jq"

ビルドを並列化する

Rails で RSpec のテストを 3 並列で実行する例です。詳しくは Running Tests in Parallel - CircleCI を参照。

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.5
    parallelism: 3
    steps:
      - checkout
      - run: circleci tests glob "spec/**/*.rb" | circleci tests split | xargs bundle exec rspec

Workflows はできるだけ使わない

CircleCI2.0 から使えるようになった Workflows ですが、可能な限り、ギリギリまで使うことを避けます。経験上、Workflows を使うと保守性が落ちることが多く、デバッグに労力を割くことが多いです。ローカルの CLI ツールが Workflow に対応していないのもデメリット。条件分岐をしたくなったときは、Workflows で条件分岐 せず、まずは if 文でやりたいことが実現できないか考えるようにしています。
ブランチ名でデプロイ先を分岐させるケース。

version: 2
jobs:
  build:
    steps:
      - deploy:
        command: |
          if [ "${CIRCLE_BRANCH}" == "master" ]; then
            # deploy to production environment
          elif [ "${CIRCLE_BRANCH}" == "develop" ]; then
            # deploy to development environment
          fi

0.0.1 の形式でタグが打たれているときにデプロイするケース。

version: 2
jobs:
  build:
    steps:
      - deploy:
          command: |
            if [[ "${CIRCLE_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
              # deploy to production environment
            fi

-技術ブログ
-