この記事は Classi Advent Calendar 2019 9 日目の記事になります。フロントエンドエンジニアの @kasaharu です。今年はフロントエンドエンジニア以外にも読んでほしい CircleCI についての記事になります。
CircleCI 使ってますか?設定ファイルの変更していますか?もしかしたら Yaml ファイルが大きく、読むのを諦めて雰囲気で修正している人もいるかもしれません。
CI の設定ファイルは毎日見るものではないので、たまに見たときにできるだけ時間を取られないように書き方をしたいものです。
今回は default の config.yml からできるだけメンテナンスしやすいような config.yml を作っていく手順を説明します。
はじめに
アプリケーションがフロントエンド前提で書いてあるが他の言語でも考え方は同じはず!
最終的な Yaml ファイルだけ見たい場合は一番下を見ればいいよ
どこから書くか?
あまり config を最初から書く機会はないかもしれないが、もし書くことになったらまずは CircleCI 上でプロジェクトの追加をするときに雛形として表示される Sample.yml をコピーする。
例えば "Set Up Project" の画面で Node.js を選択すると下記のような Sample が表示される。
version: 2
jobs:
build:
docker:
- image: circleci/node:7.10
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: yarn install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run: yarn test
Sample なので設定はシンプルだが、以下の状態になっている
- CircleCI のバージョンは 2
- 指定された job は
build
の一つ - Docker コンテナは Node.js 7.10 の入ったイメージを使う
- working_directory は以降の step を実行するディレクトリで最初は
~/repo
が指定されている(デフォルトは~/project
) - build job は以下の step で実行される
- ソースコードを working_directory にチェックアウト
- キャッシュの restore(4. で保存するキャッシュを restore)
- パッケージインストール
- インストール済みのパッケージをキャッシュ
自分のプロジェクトでやりたいことを整理
Sample では test
の実行のみであるが、昨今のフロントエンドは build をしたり lint をかけたり、もちろん test もあるので CI にやってほしいことを整理する。
この記事では lint, build, test, e2e を CI で実行することを目指す。
config.yml をカスタマイズ
使用する Docker image を変更
まずは Node.js のバージョンを上げたいので image を変更する。
circleci/node のバージョンは Docker Hub で探せる。ここでは v12.13 に変更する。
jobs:
build:
docker:
- - image: circleci/node:7.10
+ - image: circleci/node:12.13-stretch-browsers
working_directory: ~/repo
steps:
- checkout
node_modules の cache の key を変更
インストールされるパッケージは package.json
が変わらなくても lock file が変わると変更されるので cache の key も lock file の checksum に変更しておく。
- checkout
- restore_cache:
keys:
- - v1-dependencies-{{ checksum "package.json" }}
+ - v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-
- run: yarn install
- save_cache:
paths:
- node_modules
- key: v1-dependencies-{{ checksum "package.json" }}
+ key: v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn test
test 以外の step を追加
ここで test 以外の lint, build, e2e も CI に追加する
paths:
- node_modules
key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - run: yarn lint
+ - run: yarn build
- run: yarn test
+ - run: yarn e2e
job の分割と Workflow を使った並列実行
この時点で build という job の中で lint, build, test, e2e の 4 つのことをやるように設定した。
開発が進み build や test の step に時間がかかるほど CI の時間も遅くなっていくし、仮に job が失敗した場合一つの job でいろんなことをやっていると何に失敗したのかわかりにくくなってしまう。そのため役割に応じて job の分割をしておく。
また Workflow を使った並列実行も可能なので合わせて設定する。
job の分割
ここでは単純に 4 つの job を用意するだけ。
version: 2
jobs:
- build:
+ lint:
docker:
- image: circleci/node:12.13-stretch-browsers
working_directory: ~/repo
@@ -16,6 +16,51 @@ jobs:
- node_modules
key: v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn lint
+ build:
+ docker:
+ - image: circleci/node:12.13-stretch-browsers
+ working_directory: ~/repo
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "yarn.lock" }}
+ - v1-dependencies-
+ - run: yarn install
+ - save_cache:
+ paths:
+ - node_modules
+ key: v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn build
+ test:
+ docker:
+ - image: circleci/node:12.13-stretch-browsers
+ working_directory: ~/repo
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "yarn.lock" }}
+ - v1-dependencies-
+ - run: yarn install
+ - save_cache:
+ paths:
+ - node_modules
+ key: v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn test
+ e2e:
+ docker:
+ - image: circleci/node:12.13-stretch-browsers
+ working_directory: ~/repo
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "yarn.lock" }}
+ - v1-dependencies-
+ - run: yarn install
+ - save_cache:
+ paths:
+ - node_modules
+ key: v1-dependencies-{{ checksum "yarn.lock" }}
- run: yarn e2e
Workflow を使った並列実行
job を分けたので、各 job をどのような順番で実行するかを決める。今回は lint の job を最初に実行し lint job 終了後に build, test, e2e を並列で実行するようにする。
version: 2
+
+workflows:
+ version: 2
+ all-check:
+ jobs:
+ - lint
+ - build:
+ requires:
+ - lint
+ - test:
+ requires:
+ - lint
+ - e2e:
+ requires:
+ - lint
+
jobs:
lint:
docker:
アンカーとエイリアスを使った cache 処理の一元管理
それぞれチェックしたい関心事に job をわけることができた。しかし、すべての job で cache の restore と save が必要でかつインデントも多くファイルの可読性を下げている。
Yaml にはアンカー(&
)とエイリアス(*
)の機能がある。これを使用し cache の save, restore 処理を一箇所にまとめる。
アンカーの定義
version: 2
+references:
+ save_node_modules: &save_node_modules
+ save_cache:
+ paths:
+ - node_modules
+ key: v1-dependencies-{{ checksum "yarn.lock" }}
+ restore_node_modules: &restore_node_modules
+ restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "yarn.lock" }}
+ - v1-dependencies-
+
workflows:
version: 2
all-check:
エイリアスによる参照
@@ -34,15 +34,9 @@ jobs:
working_directory: ~/repo
steps:
- checkout
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
- - v1-dependencies-
+ - *restore_node_modules
- run: yarn install
- - save_cache:
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - *save_node_modules
- run: yarn lint
build:
docker:
@@ -50,15 +44,9 @@ jobs:
working_directory: ~/repo
steps:
- checkout
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
- - v1-dependencies-
+ - *restore_node_modules
- run: yarn install
- - save_cache:
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - *save_node_modules
- run: yarn build
test:
docker:
@@ -66,15 +54,9 @@ jobs:
working_directory: ~/repo
steps:
- checkout
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
- - v1-dependencies-
+ - *restore_node_modules
- run: yarn install
- - save_cache:
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - *save_node_modules
- run: yarn test
e2e:
docker:
@@ -82,13 +64,7 @@ jobs:
working_directory: ~/repo
steps:
- checkout
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
- - v1-dependencies-
+ - *restore_node_modules
- run: yarn install
- - save_cache:
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - *save_node_modules
- run: yarn e2e
ついでに cache の key も typo 防止のためにアンカーで定義しておく。
@@ -1,15 +1,16 @@
version: 2
references:
+ cache_key: &cache_key v1-dependencies-{{ checksum "yarn.lock" }}
save_node_modules: &save_node_modules
save_cache:
paths:
- node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ key: *cache_key
restore_node_modules: &restore_node_modules
restore_cache:
keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
+ - *cache_key
- v1-dependencies-
workflows:
冗長なパッケージインストールの廃止
パッケージインストールは 1 Workflow で 1 回やればいいので冗長な step を削除する。
本来はパッケージインストールだけでひとつの job にしてもいいが今回は lint job と一緒にすることにする。
@@ -17,19 +17,19 @@ workflows:
version: 2
all-check:
jobs:
- - lint
+ - package-install-and-lint
- build:
requires:
- - lint
+ - package-install-and-lint
- test:
requires:
- - lint
+ - package-install-and-lint
- e2e:
requires:
- - lint
+ - package-install-and-lint
jobs:
- lint:
+ package-install-and-lint:
docker:
- image: circleci/node:12.13-stretch-browsers
working_directory: ~/repo
@@ -46,7 +46,6 @@ jobs:
steps:
- checkout
- *restore_node_modules
- - run: yarn install
- *save_node_modules
- run: yarn build
test:
@@ -56,7 +55,6 @@ jobs:
steps:
- checkout
- *restore_node_modules
- - run: yarn install
- *save_node_modules
- run: yarn test
e2e:
@@ -66,6 +64,5 @@ jobs:
steps:
- checkout
- *restore_node_modules
- - run: yarn install
- *save_node_modules
- run: yarn e2e
Artifact による job 成果物の保存
job の中には成果物が出力される場合があり、それを保存しておきたいことも出てくる。その場合 Artifact 機能が有用である。
例として Artifact 機能を使って test job 実行後に生成されるコードカバレッジのレポートを保存する。
@@ -57,6 +57,8 @@ jobs:
- *restore_node_modules
- *save_node_modules
- run: yarn test
+ - store_artifacts:
+ path: coverage
e2e:
docker:
- image: circleci/node:12.13-stretch-browsers
Artifact については https://qiita.com/kasaharu/items/0fa892a2100a413e6145 にも書いている。
Executor を定義して実行環境の一元管理
だいぶスッキリかけてきたが、もう一箇所だけメンテンス性に欠ける部分がある。各 job で使用する Docker image の設定部分である。
このままだと例えば Node.js のバージョンを上げる必要が出たときにある job だけ変更を忘れて使う image が揃わなくなる、という問題が起こる可能性がある。
そこで Executor を定義して実行環境の一元管理する。この機能を使うには version 2.1 が必要なので version も上げておく。
Executor の定義
@@ -1,4 +1,9 @@
-version: 2
+version: 2.1
+
+executors:
+ frontend-executor:
+ docker:
+ - image: circleci/node:12.13-stretch-browsers
references:
cache_key: &cache_key v1-dependencies-{{ checksum "yarn.lock" }}
各 job で Executor を使用
@@ -35,8 +35,7 @@ workflows:
jobs:
package-install-and-lint:
- docker:
- - image: circleci/node:12.13-stretch-browsers
+ executor: frontend-executor
working_directory: ~/repo
steps:
- checkout
@@ -45,8 +44,7 @@ jobs:
- *save_node_modules
- run: yarn lint
build:
- docker:
- - image: circleci/node:12.13-stretch-browsers
+ executor: frontend-executor
working_directory: ~/repo
steps:
- checkout
@@ -54,8 +52,7 @@ jobs:
- *save_node_modules
- run: yarn build
test:
- docker:
- - image: circleci/node:12.13-stretch-browsers
+ executor: frontend-executor
working_directory: ~/repo
steps:
- checkout
@@ -65,8 +62,7 @@ jobs:
- store_artifacts:
path: coverage
e2e:
- docker:
- - image: circleci/node:12.13-stretch-browsers
+ executor: frontend-executor
working_directory: ~/repo
steps:
- checkout
working_directory の指定をやめる
これはおまけだが working_directory の名前にこだわりがなければここは指定する必要がない。時間が経ったときにこの設定がなんだったか思い出すくらいなら、指定しないほうがいいので消しておく。
削除のタイミングによっては working_directory の変更で cache が期待通り restore できなくなるので、その場合は cache の key を (v2-
などに) 変更する。
@@ -36,7 +36,6 @@ workflows:
jobs:
package-install-and-lint:
executor: frontend-executor
- working_directory: ~/repo
steps:
- checkout
- *restore_node_modules
@@ -45,7 +44,6 @@ jobs:
- run: yarn lint
build:
executor: frontend-executor
- working_directory: ~/repo
steps:
- checkout
- *restore_node_modules
@@ -53,7 +51,6 @@ jobs:
- run: yarn build
test:
executor: frontend-executor
- working_directory: ~/repo
steps:
- checkout
- *restore_node_modules
@@ -63,7 +60,6 @@ jobs:
path: coverage
e2e:
executor: frontend-executor
- working_directory: ~/repo
steps:
- checkout
- *restore_node_modules
まとめ
各 job に書いてあることが 4 ~ 5 個の step だけになった。
実行環境は executors に step の詳細は references に持っていったりしたので、各 job の見通しはかなりよくなったはず。
最終的な config.yml は下記。
version: 2.1
executors:
frontend-executor:
docker:
- image: circleci/node:12.13-stretch-browsers
references:
cache_key: &cache_key v1-dependencies-{{ checksum "yarn.lock" }}
save_node_modules: &save_node_modules
save_cache:
paths:
- node_modules
key: *cache_key
restore_node_modules: &restore_node_modules
restore_cache:
keys:
- *cache_key
- v1-dependencies-
workflows:
version: 2
all-check:
jobs:
- package-install-and-lint
- build:
requires:
- package-install-and-lint
- test:
requires:
- package-install-and-lint
- e2e:
requires:
- package-install-and-lint
jobs:
package-install-and-lint:
executor: frontend-executor
steps:
- checkout
- *restore_node_modules
- run: yarn install
- *save_node_modules
- run: yarn lint
build:
executor: frontend-executor
steps:
- checkout
- *restore_node_modules
- *save_node_modules
- run: yarn build
test:
executor: frontend-executor
steps:
- checkout
- *restore_node_modules
- *save_node_modules
- run: yarn test
- store_artifacts:
path: coverage
e2e:
executor: frontend-executor
steps:
- checkout
- *restore_node_modules
- *save_node_modules
- run: yarn e2e
まとめ
かなりすっきりした config.yml が書けたのではないかと思います。
例えば時間が経って format check の job を追加したいとなっても簡単に追加できるはずです!
Let's CI !!!
明日は @fusho-takahashi さんです。お楽しみに〜