個人でも業務でもすごくお世話になっている CircleCI について説明したいと思います。 設定する際の Tips など個人的な観点を元に紹介していきます!
CircleCI で設定する .circleci/config.yml
ファイルの中身の構造について理解していきます。
config.yml
は大きく分けて、 version
, orbs
, executors
, commands
, jobs
, workflows
の6つのキーワードに分かれています。
この6つのキーワードを理解することで CircleCI がよくわからない方もざっくりと理解することができます。
version
キーワードを除くその他のキーワードを見てもらうとわかりますが、基本的に ****s
になっているので複数指定することができます。
では順番にそれぞれのキーワードについてざっくりと説明していきます。
version キーワードは CircleCI を動かすバージョンになります。 最新は 2.1 になっており、現在の CircleCI のドキュメントではほぼ 2.1 で書かれていることが多いです。 新規に CircleCI を構築する場合は、あえて古いバージョンを使用する必要はないのでここは何も考えずにとりあえず 2.1 を設定することがオススメします。
executors キーワードは CircleCI で実行するテストやデプロイを動かすための環境(job で使用する)になります。 docker を使ったり、ubuntu や macOS, Windows などを指定することができます。
コード部分
CircleCI画面
commands キーワードはいわゆるターミナルで実行するコマンドをひとまとめにしたものになります。 1つの実行コマンドだけでなく、複数の実行コマンドをまとめ、それに名前を付けることで何をする処理なのかわかりやすくなります。
コード部分
CircleCI画面
jobs キーワードは executors で設定した環境を指定し複数の command を実行させるまとまりになります。
コード部分
CircleCI画面
workflows キーワードは作成した job をどういう順番でどういう条件で実行するのか制御させるものになります。
コード部分
CircleCI画面
orbs キーワードは job, command, executor をまとめたものです。 CircleCI から特定の環境用の作られたものが大量に公開されており、そちらを使うことで簡単に CI/CD の構築ができるようになっています。
それぞれのキーワードをざっくりと図で表し、関係を理解しましょう!
CircleCI は公式ドキュメントがすごくしっかりしているので、公式ドキュメントに沿って構築していくことでほとんどのことを実装することができます。 そこでちょっとした公式ドキュメントを読む時の Tips を紹介します。
CircleCIのドキュメントが英語しか出てこないことがあります。 自分が見たいドキュメントの日本語版を探すのは大変です。そこで簡単に日本語のドキュメントを参照する方法があります。
英語のドキュメントのURLに ja
を追加するだけで簡単に日本語のドキュメントに変更することができます。
以下のドキュメントに https://circleci.com/docs/2.0/parallelism-faster-jobs/ に ja
を追加してみます。
URL のドメイン名の後に ja
を追加し https://circleci.com/ja/docs/2.0/parallelism-faster-jobs/ とすることで簡単に日本語のドキュメントを参照することができます。
日本語のドキュメントが表示されない時は諦めて英語のドキュメントを読みましょう!
config.yml を修正した際は push して動作を確認するのではなく、まずは circleci config validate
コマンドを使用して config.yml が構文的におかしいところがないかチェックしてもらいます。
昔は一々 push して確認していましたが、これは時間がかかるしとても非効率なので必ず circleci config validate
コマンドを使用することをオススメします!
circleci コマンドは brew でインストールできます。
$ brew install circleci
CircleCI には落ちた job にログインする機能がデフォルトであります。 何かしらうまく job が機能しない場合は一々 push して確かめるのではなく、ssh でログインして動く方法を確立してからそれを config.yml に落とし込む方が早く改修することができる ようになります。
Rerun
をクリックし、Rerun Job with SSH
をクリックします。
再度、job が実行されるので終わるまで待ちます。 job の実行が終わると、最後に ssh でログインするコマンドが出力されるのでそれを使ってターミナルからアクセスします。
公式ドキュメントにも書かれていますが、ドキュメントの改修時は CircleCI を実行させる必要は無いのでスキップさせることができます。
コミットのタイトルか説明に [ci skip], [skip ci]
と入力すると CircleCI がスキップされます。
詳しくは下記のドキュメントを確認してください。
Readme に CircleCI の実行結果のステータスバッジを置くことでカッチョ良くなります。 プロジェクトの以下のページにアクセスすることでコピーするだけでステータスバッジが設定できるようになります。
Markdownに貼り付けるだけで使えるようになっているので Copy ボタンを押して Readme に貼り付けてください。
Shields.io を使うと更に自分好みのステータスバッジに変更することができます。 以下のページから CircleCI で絞り込み、必要な情報を入力するといい感じにステータスバッジを作成してくれます。
今まで、いくつかの CircleCI を構築してきた経験でどういうふうに構築していくべきか自分が学んできたことを書いていきます。
CircleCI を実行する環境つまり executor の設定で注意することになります。
CircleCI が実行される環境がどこかのタイミングで変更されることがないようにすることです。 つまり いつも同じ環境でビルドとデプロイ(再現性のあるビルド)が行われるようにすること が大切です!
デプロイする成果物は ソースコードが同じであれば必ず同じものでなければいけません。再現性が担保されていない場合、つまり環境が変わった影響で成果物が変更されてしまう可能性があります。
またデプロイするには成果物と設定ファイル(環境変数など)の2つを合わせて行うことになると思いますがこの設定ファイルだけ変更し、なにかしらの影響でデプロイが失敗した時、デプロイ環境が変わってしまう状況で 設定ファイルを変更したことが原因 なのかそれとも デプロイ環境が変わってしまったことが原因 なのか判断することは簡単にできるでしょうか? こういう状況では簡単に判断することは難しいのではないかと思います。そういった理由で CircleCIが実行する環境は必ず常に同じもの でなければいけません。
詳しくは以下のスライド資料でも書かれていますので読むことをオススメします。
使用する docker のイメージは latest を使わず必ずバージョンを指定して設定 しましょう! latest を指定するとどこかのタイミングでバージョンが違うものがダウンロードされてしまうことがあるため、latest の使用は避けることです。
latest を指定していたことで私も何回もこれで苦労しました……。
executors:
base:
docker:
- image: cimg/ruby:2.6.6-node
CircleCIの設定での話ではないですが、 プロダクトでは必ずライブラリなどはバージョンをちゃんと指定し一定の頻度でライブラリのアップデートをする機会を設けることをオススメ します。
CircleCI がメンテナンスしている Docker イメージは次世代のイメージを使うようにしましょう。 公式のドキュメントにも書かれていますが、プレフィックスが circleci のレガシーイメージは 2021年12月31日に廃止 されるようです。
まだ古いレガシーイメージを使っている方は プレフィックスが cimg の次世代イメージを使う ようにしましょう! 移行ガイドについては公式のドキュメントにも書かれているのでそちらを参考にするとよいでしょう。
詳しくは公式のドキュメントを参考にするとよいでしょう。
- Legacy Convenience Image Deprecation - Announcements - CircleCI Discuss
- Announcing our next-generation convenience images: smaller, faster, more deterministic - CircleCI
次世代のイメージの Dockerfile については github の Dockerfile を確認すると良いでしょう。
circleci/ruby:2.3.0 → cimg/ruby:2.3.0
circleci/python:3.8.4 → cimg/python:3.8.4
Browser Tool Orb を使う必要があります。
-browsers → browsers + Browser Tool Orb
-browsers-legacy → browsers + Browser Tool Orb
-node-browsers- → browsers + Browser Tool Orb
-node-browsers-legacy → browsers + Browser Tool Orb
現在は Docker Hub社と CircleCI は提携されていて基本的にはレート制限に引っかからないようになっているそうです。ただし、一部の使用方法によるとレート制限に引っかかってしまう場合があるので調べておくとよいでしょう。
また仕様が代わり、レート制限に対応する必要が出てくる可能性があるのでその時、どう対応したらいいのか雰囲気理解しておくことをオススメします。
詳しくは以下のFAQを自分たちのユースケースにおいては大丈夫なのかさらっと見ておくと良いでしょう!
CircleCI ではキャッシュ的な機能が大きく2つあるのでそれぞれ有効に使っていきましょう。
JOB 単位でキャッシュを行うことができます。次回以降のワークフローの実行でキャッシュされたものが使われるようになるので CircleCI の高速化ができます。
画像は 公式ドキュメント - 依存関係のキャッシュ より
command で restore_cache
, save_cache
キーワードを使うことでキャッシュを使うことができる ようになります。ただ、自分で実装するのはめんどくさいですよね。なんと最近の CircleCI では何も考えずに使用することができます。
Orb に実装されていることが多いので Orb に実装されている command を使用しましょう。
circleci/ruby@1.2.0 の install-deps コマンドでキャッシュ機能が使われている箇所を見てみましょう!
steps:
- when:
condition: <<parameters.with-cache>>
steps:
- restore_cache:
keys:
- >-
<< parameters.key >>-{{ checksum
"<<parameters.app-dir>>/Gemfile.lock" }}-{{ .Branch }}
- >-
<< parameters.key >>-{{ checksum
"<<parameters.app-dir>>/Gemfile.lock" }}
- << parameters.key >>
- run:
command: |
if test -f "Gemfile.lock"; then
APP_BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")
if [ -z "$APP_BUNDLER_VERSION" ]; then
echo "Could not find bundler version from Gemfile.lock. Please use bundler-version parameter"
else
echo "Gemfile.lock is bundled with bundler version $APP_BUNDLER_VERSION"
fi
fi
if ! [ -z <<parameters.bundler-version>> ]; then
echo "Found bundler-version parameter to override"
APP_BUNDLER_VERSION=<<parameters.bundler-version>>
fi
if ! echo $(bundle version) | grep -q $APP_BUNDLER_VERSION; then
echo "Installing bundler $APP_BUNDLER_VERSION"
gem install bundler:$APP_BUNDLER_VERSION
else
echo "bundler $APP_BUNDLER_VERSION is already installed."
fi
if [ "<< parameters.path >>" == "./vendor/bundle" ]; then
bundle config set deployment 'true'
fi
bundle config set path << parameters.path >>
bundle check || bundle install
name: >-
Bundle Install <<^parameters.with-cache>>(No
Cache)<</parameters.with-cache>>
working_directory: <<parameters.app-dir>>
- when:
condition: <<parameters.with-cache>>
steps:
- save_cache:
key: >-
<< parameters.key >>-{{ checksum
"<<parameters.app-dir>>/Gemfile.lock" }}-{{ .Branch }}
paths:
- <<parameters.app-dir>>/<< parameters.path >>
ちゃんと restore_cache
, save_cache
キーワードが使われていますね。
下記の部分がキャッシュがどの状態の時に保存、復元するのかの戦略方法になります。{{}}
で囲まれている部分が CircleCI で用意されているテンプレート(どの状態でキャッシュをするのか)になっています。
この例だと「Gemfile.lockの内容」 と 「ブランチ」の組み合わせが変わるごとにキャッシュを保存する設定になります。
<< parameters.key >>-{{ checksum "<<parameters.app-dir>>/Gemfile.lock" }}-{{ .Branch }}
詳しくはCircleCIのドキュメントの キーとテンプレートの使用を見てみてください。 もし自前で実装する必要がある場合はドキュメントの 依存関係のキャッシュ を確認してみてください。
キャッシュは最長で15日間保持されるため、明示的にクリアしたい時はプレフィックスで使用されている key を変更することで可能になります。
下記の部分だと << parameters.key >>
の部分にあたります。v1
などとつけることが多いため、ここをカウントアップさせて v2
, v3
…… としていくことでキャッシュをクリアできます。
<< parameters.key >>-{{ checksum "<<parameters.app-dir>>/Gemfile.lock" }}-{{ .Branch }}
circleci/ruby@1.2.0 の install-deps コマンドでもパラメータで key を指定することができるようになっています。
job 単位のキャッシュでは横へのキャッシュでしたが、今回は縦単位のキャッシュでworkflow 間でデータの共有(キャッシュとは違うかもしれませんが……)を行うことができます。 WORKSPACE というところにデータを保存しそれを job の実行時に読み込むことで job 間でデータの共有を行うことができるようになっています。 job 間で同じ資材を使う場合はそれを使い回すことができるようになり、CircleCIの高速化に繋がります。
画像は 公式ドキュメント - ワークスペースによるジョブ間のデータ共有 より
この機能を使うことで仮に linter と test で job が分かれている場合、予め依存ライブラリのインストールが必要になると思いますが、最初の job でインストール後、次の job にインストールした依存ライブラリを共有することで再インストールが必要なくなります。
これは WORKSPACE 用のコマンドを用意しそれをそれぞれ、job ごと使用するようにしています。 setup という job で WORKSPACE を保存し、lint, test の job で保存した WORKSPACE を使うようにしています。イメージを掴むためのコードでいろいろと省略しています。
commands:
save-workspace:
steps:
- persist_to_workspace:
# 絶対パスまたは working_directory からの相対パスを指定する必要があります。
# WORKSPACE のルートディレクトリの設定です。
root: .
# ルート(上で設定した)からの相対パスを指定する必要があります。
# どこのディレクトリを WORKSPACE に保存するかの設定です。
paths: .
using-workspace:
steps:
- attach_workspace:
# 絶対パスまたは working_directory からの相対パスを指定する必要があります。
# 保存した WORKSPACE のファイルをどこに展開するかの設定です。
at: .
jobs:
setup:
executor: base
steps:
- checkout
- ruby/install-deps
# この場合はソースコードを clone してきて、依存関係をインストールしたものを WORKSPACE に保存しています。
- save-workspace
lint:
executor: base
steps:
- using-workspace
- ruby/install-deps
- ruby/rubocop-check:
label: Rubocop
test:
executor: base
parallelism: 2
steps:
- using-workspace
- ruby/install-deps
- ruby/rspec-test:
label: Run all RSpec
- store-coverage
Docker レイヤーキャッシュは Docker のイメージのビルドが CI/CD のプロセスの一環として定期的に行われる場合に効果を発揮する機能になります。 詳しくはドキュメントを確認してみてください。
並列に実行できるものに関しては並列に実行し CI/CD をより早くすることで開発速度をあげていくことができます。 ここでは並列に実行するための Tips を考えていきましょう。
CircleCI にはテストを並列で実行するための機能が備わっています。 こちらの並列実行の機能を使うことでテストを早く終わらせることができるようになります。
job を実行させるために executor を指定すると思いますが、こちらの executor を複数立ち上げて分割したテストを実行させることで並列実行をできるようにします。
画像は 公式ドキュメント - テストの並列実行 より
ただし、予め並列実行させるために準備が必要です。それを見ていきましょう。
CircleCI ではタイミングデータを使用してテストを分割することが最良の方法だと記載されています。 なのでテストの分割を最良の方法で行うためにタイミングデータを作成するようにします。
store_test_results を指定することでテストのメタデータを作成するようにします。こうすることで、test の job で CircleCI 上でテスト結果を詳細に確認することができるようになります。このメタデータを作成することでタイミングデータも作成されます。
ただし、JUnit フォーマッタを有効化しないと CircleCI では自動的に収集してくれないのでフォーマッタの有効化も必要になります。 詳しくは以下のドキュメントを参照してください。
job の詳細画面
Test insights画面
テストのメタデータを保存することができたら後は test のコマンドを変更していきます。 ruby で RSpec で行う場合は以下のようになるかと思います。サンプルなのでいろいろと省略してあります。
circleci tests glob
コマンドを使用してファイルの一覧を取得しそれをcircleci tests split --split-by=timings
コマンドでタイミングデータで分割する- 分割したテストを実行させる
- job で parallelism のキーワードを使用して executor の並列実行させる数を指定する
commands:
rspec-test:
steps:
- run:
# テストの格納フォルダを作成し指定する
# タイミングデータを元にテストの分割を行う
# タイミングデータで分割したテストを実行する
command: >
mkdir -p /tmp/test-results/rspec
TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests
split --split-by=timings)
bundle exec rspec $TESTFILES --profile 10 --format RspecJunitFormatter
--out /tmp/test-results/rspec/results.xml --format progress
- store_test_results:
path: /tmp/test-results/rspec
jobs:
test:
executor: base
# ここで executor の数を指定する
parallelism: 2
steps:
- using-workspace
- ruby/install-deps
- rspec-test:
label: Run all RSpec
- store-coverage
これで並列実行されるようになります。
実はこれらの機能は orb にすでに実装されているケースが多いです。なので特に考えずに orb を使うとよいでしょう。
もう一つの並列実行させる仕組みはテストを job 単位で分割して実行させる方法です。
例えば Rails なら spec フォルダ内のフォルダごと分割されているテストをいくつかのグループに分けて実行させる方法です。イメージとしては テストの job が複数ある感じです。
テスト結果(カバレッジなど)はアップロードし CircleCI で確認できるようにしましょう。 テスト結果を見れる状態にしておくことでカバレッジを確認できるようになるのでテストを書くモチベーションも上がります!
ARTIFACTSタブ
ARTIFACTSにアップロードされたindex.html
CircleCI のドキュメントに言語ごとのコードカバレッジの実装方法が記載されていますのでこちらを参考にすることで簡単に実装ができます。
ドキュメントを確認すると store_artifacts
のキーワードを使用していることがわかります。
今回はどちらもテストの結果を確認できる用途で使いましょうと説明しましたが store_artifacts に関しては別にテスト結果に限った話ではなく、何でも格納可能になっています。 しかし store_test_results に関してはテスト結果専用で使用するものとなっているので store_artifacts とは少し違います。
なぜテスト結果を格納するのに両方使うのかと言うと、store_test_results は CircleCI に最適化されたテストの結果が表示されるようになりますが store_artifacts の場合はその言語のライブラリの出力結果を見ることができるようになるので確認できる内容は全然、違うものになっています。 Ruby の場合だと SimpleCov を使うと思いますが、こちらはコードのカバレッジ結果を確認できますが、store_test_results はカバレッジ結果は見れませんが、それぞれのテストの実行時間を簡単に確認できるようになり、さらにテストの並列実行に必要なタイミングデータも作成してくれるので全く違う用途で使うことになります。
言語ごとそれに適した orb があるのでそれを活用していくことをオススメします。
自前で実装するよりも config.yml の行数が圧倒的に減り、見やすくなります。 キャッシュや並列実行でも触れましたが、orb の実装ではほぼ キャッシュや並列実行が簡単にできるように実装されているケースが多い のでわざわざ自分で実装せずに circleci が公開している orb に寄せて管理は circleci に任せると良いでしょう。
orb が登場してから使用してきましたが、私の間隔だとバージョンアップがよくされているように感じます。なので半年前に実装した orb と中身が結構違うケースがあります。 orb のバージョンがアップすると、 command や job の名前が変わったりする のでバージョンアップ時には注意が必要です。
またよくわからず使用することはオススメしません。 基本的に orb に寄せるで良いと思っていますが、ちゃんと使用する command, job の処理の中身を確認 して自分のプロダクトにあった処理になっているか確かめてから使用するとよいと思います。
orb のドキュメントページには必ず example のコードが記載されています。なのでその example のコードをよしなに変更にして使うようにしましょう。
CircleCI で使用する環境変数の Tips を紹介していきます。
CircleCI の設定で Organization で環境変数を扱う場合は、Contexts を使用して共通で管理できるようにしましょう。 slack 通知で必要な アクセストークンやチャンネルIDなどを設定するとよいと思います。
workflow で設定する job に対して contexts を使用するように指定します。context: slack
の感じで指定することで環境変数を読み込むことができるようになります。
workflows:
main:
jobs:
- setup
- lint:
requires:
- setup
- slack/on-hold:
name: slack_on_hold
channel: $SLACK_CHANNEL
# ここで contexts を設定しています
context: slack
custom: |
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "ON HOLD - Awaiting Approval :raised_hand:",
"emoji": true
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Project*:\n$CIRCLE_PROJECT_REPONAME"
},
{
"type": "mrkdwn",
"text": "*Branch*:\n$CIRCLE_BRANCH"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Workflow"
},
"url": "https://circleci.com/workflow-run/${CIRCLE_WORKFLOW_ID}"
}
]
}
]
}
requires:
- lint
設定画面は以下のような感じになっています。
Contexts 画面
Contexts 詳細画面
.env
ファイルなどの複数行の環境変数を一括で1つの環境変数として使用し、ビルド時にその環境変数を .env
ファイルなどに展開して使用する方法です。
bash64 コマンドを使用して .env ファイルの値を encode します。
$ cat .env | base64
qwertyuiopasdfghjklzxcvbnm
出力された値を環境変数に設定します。
今度は逆で環境変数の値を decode しその値から .env ファイルを生成します。
commands:
setup_env_file:
steps:
- run:
name: Create .env file
command: |
echo $MULTIPLE_ENV_VALUE | base64 --decode > ./.env
こちらの方法は CircleCI実践入門──CI/CDがもたらす開発速度と品質の両立 の書籍に紹介されていた方法になります。 こちらの書籍は CircleCI について網羅的に書かれているのですごくオススメです!
デプロイが完了したり、デプロイの準備が整ったら slack に通知されて欲しいかと思います。 ここでは slack に CircleCI の通知を行う方法を記載していきます。
公式が slack の orb を提供してくれているのでこちらを使用して実装します。
slack の orb の github のページに Setup 方法が記載されているのでこれの通りに slack に設定を行い、CircleCI にも環境変数の設定を行います。
Wiki に書かれている通り、以下の手順をそれぞれなぞって設定します。
- Create a Slack App
- Permissions
- Install and Receive Token
- Create a Context on CircleCI
チャンネルを右クリックしリンクコピーすると、https://xxxxxxxxx.slack.com/archives/XXXXXXXXXXXX
のような形でコピーされるのでその末尾の XXXXXXXXXXXX
がチャンネルIDになります。
設定さえ、ちゃんできていれば slack の orb に載っている only_notify_on_branch
をそのまま使用することで通知が可能になります。
version: '2.1'
orbs:
slack: circleci/slack@4.1
jobs:
build:
machine: true
steps:
- run: some build command
- slack/notify:
branch_pattern: main
event: fail
template: basic_fail_1
- slack/notify:
branch_pattern: production
event: fail
mentions: '<@U8XXXXXXX>, @UserName'
template: basic_fail_1
workflows:
deploy_and_notify:
jobs:
- build:
context: slack-secrets
- deploy:
filters:
branches:
only:
- production
requires:
- build
デプロイ前に承認処理を追加し、Approve
ボタンをクリックしないと後続の job が実行されない仕組みを実装しましょう。
この処理を挟むことでデプロイ内容を予めチェックし問題なければ、そのままデプロイすることが可能になります。いわゆる安全装置です。
承認処理 job 押下前
承認処理 job 押下後
こちらの処理も slack の orb に載っている on_hold_notification
を使用することで実装することが可能です。
version: '2.1'
orbs:
slack: circleci/slack@4.1
workflows:
on-hold-example:
jobs:
- my_test_job
- slack/on-hold:
context: slack-secrets
requires:
- my_test_job
- pause_workflow:
requires:
- my_test_job
- slack/on-hold
type: approval
- my_deploy_job:
requires:
- pause_workflow
通知処理と承認処理の両方を実装することができたら、後はお好みでカスタマイズしていきます。 slack 通知する内容は テンプレート が用意してあってそれを使用して通知してします。
自分でカスタマイズする場合は custom
パラメータを適宜変更してそれを実装するようにします。
- slack/notify:
event: always
custom: |
{
"blocks": [
{
"type": "section",
"fields": [
{
"type": "plain_text",
"text": "*This is a text notification*",
"emoji": true
}
]
}
]
}
細かい表示の変更をする時は slack の Block Kit Builder を使用して自分が表示して欲しい形式を作成してから config.yml に落とし込むのが良いでしょう。
CircleCI が持っている定義済みの環境変数を使用することでより slack の通知を使いやすくすることができます。
例えば slack 通知に job の URL へ遷移するボタンは CIRCLE_BUILD_URL
という環境変数を使用し以下のように設定すると使えるようになります。
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Job"
},
"url": "${CIRCLE_BUILD_URL}"
}
]
}
プルリクエストを作成したユーザー名は CIRCLE_PR_USERNAME
という環境変数で取得できたりします。
お好みの環境変数を使用して自分好みの slack 通知を作成すると良いでしょう。
私が3年ぐらい、業務や個人で CircleCI を使用し溜めてきた Tips やいつも必ず設計することや効率的な開発方法について述べてみました。 皆さんのオススメの開発方法などありましたら教えてください!