CircleCI Orbsの美味しいところだけを使おう 〜例えばrestore_cacheを撲滅する〜
はじめに
CircleCI 2.1になってOrbsという新機能が登場しましたが、みなさん活用しているでしょうか? 先駆者の方々がOrbsについてのブログを早速書いてくれていますが、その多くは自分でOrbsを作る記事でした。
Orbsは自分で作成するだけでなく、CircleCI公式や誰かが作成して公開したOrbsも使うことが可能ですが、そのようなサンプルコードや解説記事がまだまだ少ない状況です。実際に自分がCircleCI公式のOrbsを使用しようとしたときにサンプルが少なくて困ったので、自分でOrbsを作らなくても使うだけで十分という人向けに解説をします。
Before & After
まずはサンプルを紹介します。BabelかTypeScriptでnpm run build
を実行するというjs開発では日常的に見られるconfig.ymlです。
version: 2 jobs: build: docker: - image: circleci/node:latest working_directory: ~/repo steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "package.json" }} - v1-dependencies- - run: npm install - run: name: build command: npm run build - save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }} workflows: version: 2 build: jobs: - build
これが、Orbsを使うとこのように書けます。
version: 2.1 orbs: # https://circleci.com/orbs/registry/orb/circleci/node node: circleci/node@0.0.8 # circleci/nodeのOrbsを`node`という名前で参照する jobs: build: executor: # nodeのOrbsによってimage: 'circleci/node:latest'と同じになる name: node/node tag: "latest" working_directory: ~/repo steps: - checkout - node/with-cache: # デフォルトは~/project/node_modulesをキャッシュするのでworking_directoryを変更した場合はそれに合わせる dir: ~/repo/node_modules cache-version: v1 steps: - run: npm install - run: name: build command: npm run build workflows: version: 2 build: jobs: - build
Orbsによってrestore_cache
, save_cache
が隠蔽されているのが分かるでしょうか。
キャッシュ周りについてはCircleCIのサンプルコードをコピペしてるだけの人が大半ではないでしょうか。本当は暗黙的にキャッシュして欲しいぐらいですが、CircleCI 2.0からはキャッシュコントロールも含めて全てユーザが面倒を見る仕様となったため、みんな面倒くさいと思いつつ毎回コピペするしかありませんでした。
Orbsは再利用可能なパーツを提供できる仕組みであり、自分で作らずとも誰かが作ったOrbsを使わせてもらうことでコピペコードを減らすことができます。
Orbsを知る
npm install
のキャッシュ周りだけをOrbsで解決する方法を検索して記事に辿り着いた方は先程のサンプルコードだけで十分かもしれません。
ここからはOrbsを使うために知っておくべき要素を解説します。
OrbsはCommands, Executors, Jobsで構成されています。3つを全て含む必要はなく、Commandsだけで構成されているOrbsもたくさんあります。では実際にドキュメントを見ながらそれぞれについて理解していきましょう。
https://circleci.com/docs/2.0/using-orbs/
Commands
Commandはstepsの集まりです。そしてstepsはrun
とかsave_cache
などのことです。
workflowsで複数のjobに分割していると同じコマンドのコピペだらけになることが多いですが、同じコマンドを繰り返している場合は一連の流れがCommandとして登録されているOrbsを使うことでコピペコードを撲滅できます。
Orbsの中にはパラメータを受け取り、挙動を変えることができるものも存在します。例えば、先程のサンプルのwith-cache
ではdir
, cache-version
, steps
がパラメータです。
ちなみにCommandの中にstepsを入れ子にすることも可能で、with-cache
はそれをうまく活用したものとなっています。詳しくは後述します。
Jobs
CommandとExecutorが一緒になったものがJobです。実はこれは見慣れたものであり、2.0から意識せずとも定義しているjobsの中で実行環境のdockerイメージとstepsを書いているはずです。
workflowsでjobsに定義したものを使用しているのと同じようにして、Orbsで定義されたjobsもまたworkflowsの中で使うことが可能です。
Executors
見慣れない単語ですがこれも実は新しい概念というわけではありません。今までjobsの中でdockerイメージを指定していたものがExecutorです。
個人的にはExecutorは実はOrbsの中で結構重要なものではないかと思います。基本的コピペコードを撲滅するためにOrbsを使うことが多いと思いますが、Orbsの中で定義されているCommandを実行できるかどうかは実行環境に依存しています。
例えば、npm
はnodejsが動く環境でしか使えませんし、bundler
はrubyが動く環境でしか使えません。npm
やbundler
であればDocker Hubから公式のイメージを探せばOKでしょうが、例としてHerokuに使用するheroku
コマンドを扱うOrbsを見つけたとして、その実行環境にはどのdockerイメージを使うべきでしょうか?
このように、実はCommandだけでは片手落ちなのです。Executorが用意されているOrbsでは作者がCommandの実行に必要な環境を指定しているはずですので、Orbsが提供しているExecutorを指定すれば実行環境について考える必要はなくなるでしょう。
また、Orbsが対応していればExecutorにもパラメータを渡すことが可能です。パラメータをdockerイメージのタグ指定に使うことで、言語やOSのバージョンをある程度コントロール可能にしているものが多いようです。
実際に外部製のOrbsのドキュメントを読んでみよう
では実際にcircleci/node使い方をドキュメントから読み解いてみましょう。
Commands
執筆時点でのバージョン0.0.8ではCommandsが3つありますが、サンプルでも使用したwith-cache
を例に解説します。
まず、steps
がREQUIREDになっているため、このパラメータは必須であることが分かります。そして、DESCRIPTIONによると、Nodeのキャッシュを使いながら実行するステップを指定するようです。
次にdir
, cache-key
, cache-version
を見ると、デフォルト値がそれぞれ以下のようになっています。
PARAMETER | DEFAULT |
---|---|
dir | ~/project/node_modules |
cache-key | package.json |
cache-version | v1 |
ちなみに、Orbsを使う前のconfig.ymlではsave_cache
ステップはこのようにしていましたね。これを見ると、これらのパラメータはおそらくsave_cache
のステップで使用する変数をコントロールするものだと推測ができます。
- save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }}
では本当に想定した挙動かどうかを確認します。"Show Command Source"と書かれているリンクをクリックするとOrbsの実際のyamlが表示されるので見てみましょう。
まず、細かいパラメータを省略すると全体の流れはこのようになっています。
steps: - when: condition: << parameters.use-strict-cache >> steps: - restore_cache: - unless: condition: << parameters.use-strict-cache >> steps: - restore_cache: - steps: << parameters.steps >> - save_cache:
use-strict-cache
のwhen-unlessを一旦横においておくと、restore_cache
-> 任意のsteps -> save_cache
という流れになっていることが分かります。つまり、steps
パラメータにキャッシュ周り以外のnpm install
やnpm run build
といったstepを渡せばいい感じにキャッシュを扱ってくれそうな感じがします。
次にsave_cache
の定義を見てみましょう。keysの文字列を読み解くと、cache-version
, ブランチ, cache-key
のチェックサムの3つの組み合わせでキャッシュを判定し、dir
のディレクトリをキャッシュ対象とすることが分かります。
- save_cache: key: >- node-deps-<< parameters.cache-version >>-{{ .Branch }}-{{ checksum "<< parameters.cache-key >>" }} paths: - << parameters.dir >>
どうでしょうか、ざっと見ただけでもwith-cache
がどのような挙動をするのか大体つかめたのではないかと思います。
Executors
バージョン0.0.8にはExecutorが4種類あります。おそらくdefault
が標準として用意されているものなのでこちらを解説といきたいところですが、実際にコードを見てみるとnode
と全く同一なのでnode
を例にします。
"Show Executor Source"をクリックすると以下の実際のコードが表示されます。非常にシンプルになっており、dockerイメージにはcircleci/node
を使用し、タグの指定がtag
パラメータで可能なことが分かります。
デフォルト値はlatest
です。つまり、自分のconfig.yml上でこのOrbsのExecutorを使用すると、実際にはcircleci/nodeのdockerイメージが使用されるということが分かります。
parameters: tag: type: string description: Pick a specific circleci/node image variant. default: latest docker: - image: 'circleci/node:<< parameters.tag >>
ここまでドキュメントから読み解いた挙動を元に、再び最初に示したOrbsを使用したサンプルコードを見直してみてください。Orbsの概念、そして使い方もそれほど難しいものではないことが分かるかと思います。
玉石混交のOrbsの中から玉を探す方法
ここまでの解説によって自分でOrbsを作らずとも、外部製のOrbsを活用する方法が理解できたと思います。
次は実際に必要なOrbsを探すことになるのですが、今はOrbsが公開されたばかりということもあって少し見ただけでも以下のように玉石混交の状況です。
- CircleCI謹製
- かなり本気で作成したコミュニティ製
- とりあえずやってみましたレベル
自分が使いたいコマンドで検索して候補が複数存在した場合、あくまで私見ですが以下の点で優れているものを選ぶと良いと思います。
指定可能なパラメータが多いもの
パラメータが多い = 挙動を細かくコントロールできるということ。
自分が使いたいコマンドのオプションをコントロールできるパラメータが存在しない場合、Orbsを使えないことになってしまいます。最初から望む細かい挙動などがあるのならばちゃんとOrbsのコードを確認しておきましょう。
例えば、with-cache
はパラメータの数が豊富だったのでキャッシュ周りで困ることはなさそうですね。
Orbsを利用することでコード量の減少が見込めるもの
例として紹介したwith-cache
はCommandにstepsを渡せるという仕様をうまく活用することでrestore_cache
, save_cache
のコードを減らしています。さらに、キャッシュという本来あまり気にしたくはない工程を隠蔽してくれる点で非常に優れていると思います。
他には、パラメータのデフォルト値として妥当なものが設定されているかどうかも重要でしょう。パラメータの数は多いほうが良いと先述しましたが、デフォルト値が微妙だと自分で全て指定することになってしまいます。
executorとなるdockerイメージのタグを柔軟に選択可能なもの
例えば、executorで指定されたdockerイメージがcircleci/node:10
とタグが固定されてしまっていた場合、どうなるでしょうか?
その場合、新しいNode.jsのバージョンが将来公開されたとしても、circleci/node:11
を指定できないため古いNode.jsを使用し続けるしかなくなってしまいます。
そのOrbsのバージョンが上がることでExecutorsが更新されて新しいNode.jsに対応する可能性もありますが、その場合はExecutors以外にJobsやCommandsに非互換で破壊的な変更が入る可能性も有り得るでしょう。
Executorのパラメータを使ってある程度柔軟にdockerイメージを指定できるOrbsであれば、Orbsのバージョンを固定してもパラメータを更新するだけで新しいdockerイメージを使うことができます。
まとめ
CircleCI 2.1から登場したOrbsはまだサンプルコードもあまりない状況です。そのような状況ですのでドキュメントを見るだけで使い方を読み解けるようにOrbsの使い方を解説しました。
この記事でOrbsの概念とドキュメントの読み方は理解できたと思いますので、ぜひOrbsのドキュメントやコードを読み解きながら活用してみてください。