CircleCIを2.0から2.1にバージョンアップした
CircleCIのver2.1から新機能が追加されました。
新機能が追加されたことによって今までの同じような記述を何度も繰り返さればならなかった箇所をスッキリさせることで、CircleCIを触ったことない人でもざっくりと何をやっているのかわかりやすい設定ファイルを作成できます。
新しく追加された機能と、バージョンアップの取り組みから学んだことを紹介します。
新機能
まず、はじめに新しく追加された機能について紹介します。
変更点はCircleCI 2.1 Config Overview に記載されています。
commands
steps配下の設定を集約したもので、steps内で呼び出すことによって再利用できます。
version: 2.1 commands: my-command: steps: - run: echo "a command is a collection of steps" - run: echo "this command has two steps" jobs: my-job: steps: - my-command
executors
定義した実行環境の再利用を可能にする機能です。例えば、docker
やenvironment
のキーなどです。
version: 2.1 executors: my-executor: docker: - image: python my-other-executor: docker: - image: ruby jobs: my-job: executor: my-executor steps: - run: echo "i'm using my-executor" my-job2: executor: my-other-executor steps: - run: echo "i'm using my-other-executor"
jobs
jobsは、stepの集まりです。2.1からの変更点は、後述するparameters
キーを設定できるようになったことです。
また、以前のjobsではworkflow
内で同一の名前をつけることができませんでした。この問題は、version2.1から解決しました。
version: 2.1 workflows: build: jobs: - loadsay # This doesn't need an explicit name as it has no downstream dependencies - sayhello: saywhat: Everyone requires: - loadsay # This needs an explicit name for saygoodbye to require it as a job dependency - sayhello: name: SayHelloChad saywhat: Chad # Uses explicitly defined "sayhello" - saygoodbye: requires: - SayHelloChad
parameters
command
、executors
、jobs
のサブキーです。パラメータを設定することで、似たような処理だが一部異なる箇所をまとめる時に利用します。現在は、以下のパラメータが設定できます。詳しい利用方法は、公式ドキュメントが一番わかりやすいです。
- string
- boolean
- integer
- enum
- executor
- steps
- environment variable name
orbs
command
、executors
、jobs
の集合体です。Docker-Hub、RubyのGemのように優秀なエンジニアが作成した最強のconfigファイルのパラメータを変更して使うイメージです。個人的には、専門分野でないならエコシステムが確立した際に、利用させて頂くのがよいのではないかと思います。
Orbsは、Explore Orbsで探すことができます。CircleCIの設定ファイルの書き方を勉強するための素材としても価値がとても高いので、CircleCIを始めたばかりの初心者にもオススメです。
私の場合だと、@sue445さんが作成したsue445/ruby-orbsを見て、多くのことを学びました。
以上が、新しく追加された機能です。
バージョンアップ時の注意点
CircleCIのGitHubにversion2.1の注意事項が記載されています。
以下、ざっくりとした日本語の要約を示しますが詳細はIMPORTANT: 2.1 Configuration Caveatsを確認してください。
config.yml
の一番最初に、version: 2.1
と記載するパラメータが導入されたことによって
<< parameters.foo >>
などを字句解析する際に問題となるので、<<
という記号を使っていた場合、prefixとしてバックスラッシュ\
をつける必要があります。shell
とsetup_docker_engine
キーが使えなくなりました。それぞれ、run
とsetup_remote_docker
に書き換えが必要です。CLIによるローカルビルドをサポートしていません。
circleci config process
コマンドでファイルを書き出した後に、buildする必要があります。
circleci config process .circleci/config.yml > process.yml ## 変換と書き出し circleci local execute -c process.yml --job *your_job_name*
バージョンアップで学んだこと
簡易的な文法のチェックは、circleci config validate
で行います。
executors
やcommands
を利用したとしても、冗長な記述がなくなるだけなので設定ファイルの構造自体に変化が起きてはいけません。そこで、コミットする前にcircleci config process .circleci/config.yml
で書き出したファイルと元々のファイルをDiffcheckerなどを使って比較して差分がないことを確かめました。
バージョンアップ後のCircleCI設定ファイルでは、Parameter
を利用しませんでした。Parameter
を駆使したPullRequestを出したのですが、読みにくいと指摘を受けました。感覚値ですが、一つの処理に対して2つ以上Parameter
を使っていた場合は、読みにくいので一つで表現できないなら利用する必要はないと思います。
少ない行数の設定ファイルだと、逆に行数が増えることがあるかもしれません。しかし、読みやすさは上がるので取り組む余地はあると思います。
CircleCI設定ファイル
ENVやRubyなどのバージョン情報は、適当な値に置き換えているのは、ご了承下さい。
バージョンアップしたことによる変更した主な場所は、jobs配下のsteps実行の部分です。特に、ほぼ同じような設定であるrubucop
とbrakeman
が見やすくなっていることがわかります。RSpecにおいても、注目すべき箇所を判断しやすくなっています。
変更前
jobs: brakeman: docker: - image: circleci/ruby:version steps: - checkout - restore_cache: keys: - rubygems-dependencies-{{ checksum "Gemfile.lock" }} - rubygems-dependencies- - run: name: Install Ruby Dependencies command: | bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3 - save_cache: paths: - vendor/bundle key: rubygems-dependencies-{{ checksum "Gemfile.lock" }} - run: name: Run Brakeman command: | bundle exec brakeman rubocop: docker: - image: circleci/ruby:version steps: - checkout - restore_cache: keys: - rubygems-dependencies-{{ checksum "Gemfile.lock" }} - rubygems-dependencies- - run: name: Install Ruby Dependencies command: | bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3 - save_cache: paths: - vendor/bundle key: rubygems-dependencies-{{ checksum "Gemfile.lock" }} - run: name: Run rubocop (new and modified files) command: | bundle exec rubocop --parallel rspec: docker: - image: circleci/ruby:version-stretch-node-browsers environment: DATABASE_HOSTNAME: DATABASE_USERNAME: "" DATABASE_PASSWORD: "" DATABASE_NAME_FOR_TEST: "" REDIS_PORT_6379_TCP_ADDR: "" REDIS_PORT_6379_TCP_PORT: "" HOST: "" - image: circleci/mysql:version environment: - MYSQL_ROOT_PASSWORD= command: mysqld hoge hoge - image: redis:version-alpine parallelism: 2 steps: - checkout - run: name: Set Timezone command: sudo /bin/cp -f /usr/share/zoneinfo/Asia/Tokyo /etc/localtime - restore_cache: keys: - rubygems-dependencies-{{ checksum "Gemfile.lock" }} - rubygems-dependencies- - restore_cache: keys: - v1-npm-packages-{{ checksum "yarn.lock" }} - v1-npm-packages-{{ checksum "package.json" }} - v1-npm-packages- - run: name: Install Ruby Dependencies command: | bundle check --path=vendor/bundle || bundle install --force --path=vendor/bundle --jobs=4 --retry=3 - run: name: Install Node Dependencies command: | npm install - save_cache: paths: - vendor/bundle key: rubygems-dependencies-{{ checksum "Gemfile.lock" }} - save_cache: paths: - ./node_modules key: v1-npm-packages-{{ checksum "yarn.lock" }} - save_cache: paths: - ./node_modules key: v1-npm-packages-{{ checksum "package.json" }} - run: name: Create DB command: | bundle exec rake db:create db:schema:load --trace - run: name: Run RSpec command: | TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) bundle exec rspec -b --format Fuubar --format RspecJunitFormatter --out /tmp/test-results/rspec.xml -- ${TESTFILES} no_output_timout: 50m - store_artifacts: path: coverage destination: coverage - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results workflows: version: 2.0 rubocop-and-rspec-brakeman: jobs: - rubocop - brakeman - rspec
変更後
version: 2.1 executors: default: docker: - image: circleci/ruby:verison environment: TZ: "Asia/Tokyo" extend: docker: - image: circleci/ruby:version-stretch-node-browsers environment: TZ: "Asia/Tokyo" DATABASE_HOSTNAME: DATABASE_USERNAME: "" DATABASE_PASSWORD: "" DATABASE_NAME_FOR_TEST: "" REDIS_PORT_6379_TCP_ADDR: "" REDIS_PORT_6379_TCP_PORT: "" HOST: "" - image: circleci/mysql:version environment: MYSQL_ROOT_PASSWORD: "" TZ: "Asia/Tokyo" command: mysqld hoge hoge - image: redis:version-alpine environment: TZ: "Asia/Tokyo" commands: restore_gems: steps: - restore_cache: keys: - rubygems-dependencies-{{ checksum "Gemfile.lock" }} - rubygems-dependencies- restore-npm-packages: steps: - restore_cache: keys: - v1-npm-packages-{{ checksum "yarn.lock" }} - v1-npm-packages-{{ checksum "package.json" }} - v1-npm-packages- install_gems: steps: - run: name: Install Ruby Dependencies command: | bundle check --path=vendor/bundle || bundle install --clean --force --path=vendor/bundle --jobs=4 --retry=3 install_npm-packages: steps: - run: name: Install Node Dependencies command: | npm install save_gems: steps: - save_cache: paths: - vendor/bundle key: rubygems-dependencies-{{ checksum "Gemfile.lock" }} save_yarn: steps: - save_cache: paths: - ./node_modules key: v1-npm-packages-{{ checksum "yarn.lock" }} save_npm-packages: steps: - save_cache: paths: - ./node_modules key: v1-npm-packages-{{ checksum "package.json" }} jobs: brakeman: executor: default steps: - checkout - restore_gems - install_gems - save_gems - run: name: Run Brakeman command: | bundle exec brakeman rubocop: executor: default steps: - checkout - restore_gems - install_gems - save_gems - run: name: Run rubocop (new and modified files) command: | bundle exec rubocop --parallel rspec: executor: extend parallelism: 2 steps: - checkout - restore_gems - restore-npm-packages - install_gems - install_npm-packages - save_gems - save_yarn - save_npm-packages - run: name: Create DB command: | bundle exec rake db:create db:schema:load --trace - run: name: Run RSpec command: | TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) bundle exec rspec -b --format Fuubar --format RspecJunitFormatter --out /tmp/test-results/rspec.xml -- ${TESTFILES} no_output_timout: 50m - store_artifacts: path: coverage destination: coverage - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results workflows: version: 2.1 rubocop-and-rspec-brakeman: jobs: - rubocop - brakeman - rspec
感想
CircleCIの設定ファイルを変更するとRSpecのテスト実行時間を短くすることができるかもという情報があり、CircleCIに慣れるためバージョンアップに取り組みました。RSpecのテストをFeature spec
とそれ以外に分けてworkflows
に新しく登録することで早くテストを終わらせることができるのではないかと考えているので、試してみたいと思います。
CircleCI内だけで完結できるテスト高速化の知見がありましたら、Twitterやコメントをしてくれると嬉しいです。
command: | TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) bundle exec rspec -b --format Fuubar --format RspecJunitFormatter --out /tmp/test-results/rspec.xml -- ${TESTFILES}
オススメのリンク
公式ドキュメントを読まずにざっと内容を把握した人向け
CircleCI 2.1 の新機能を使って冗長な config.yml をすっきりさせよう!
CircleCIの公式が出している日本語の動画
CircleCIのDiscussページ
Useful tips and best practices when migrating to CircleCI 2.0