広告技術部のtoshimaruです。この記事はGunosy Advent Calendar 2018、10日目の記事です。昨日の記事はふそやんさんのサーバーレスプッシュ管理画面のコンセプト【グノスポ連載第六回】でした。
昨年2017年にCircleCI 2.0にCircleCIがアップグレードされたことは記憶に新しいかと思いますが、今年2018年末にCircleCI 2.1のアップデートが降ってきました。今日はこのCircleCI 2.0+2.1の機能をフル活用して弊社のRuby on RailsアプリケーションのCI/CDワークフローを改善させた話を書いてみます。
TL;DR
- CircleCIの
executors
,commands
の定義を利用してCircleCIのconfigをDRYに書けるようになったよ - CircleCI workflowを使って自動レビューやデプロイのワークフローを整備したよ
- Circle CI Orbsを使ってレポジトリを跨ぐCircleCIの処理を共通化できるようになったよ
前提事項
- CircleCI 2.1およびCircleCI Orbsを使うための設定を済ませてあること
なおCircleCI 2.1, CircleCI Orb導入にあたっては下記の記事を参考にさせていただきました。
CircleCI Orbs 入門 | tsub's blog
改善前: 素のCircleCI 2.0
あくまで擬似コードですが、改善前の .circleci/config.yml
は下記のようなイメージです。1つのbuild stepの中に一連の処理内容が上から順に並べてあるだけでした。
version: 2 jobs: build: docker: - image: circleci/ruby steps: - checkout - run: bundle install - run: bundle exec rspec - deploy: name: Deploy to Staging command: "deploy operation"
改善ステップ
上記のCircleCIの設定をCircleCI 2.0/2.1の機能をフル活用して改善させていきたいと思います。
改善第1弾: executors
, commands
を使ってみる
CircleCI 2.1からはexecutors, commandsがyamlに定義できるようになりました。それぞれ下記の定義が可能です。
executors
: 実行環境の定義が可能(dockerイメージや環境変数の定義) → これにより複数のジョブを跨ぐ実行環境を共通化できるようになりましたcommands
: 一連の処理をコマンドとして定義可能。→ 関数切り出しのようなイメージ。これによりジョブを跨ぐ共通部分の処理をコマンドとして切り出せるようになりました
ではこれで上記のyamlをリファクタリングしてみましょう。
version: 2.1 executors: default: docker: - image: circleci/ruby commands: bundle_install: steps: # 実際はここにCircleCIのキャッシュの処理も入る - run: bundle install --path vendor/bundle jobs: build: executor: default steps: - checkout - bundle_install - run: bundle exec rspec - deploy: name: Deploy to Staging command: "deploy operation"
いい感じですね。これによりさらなるワークフロー改善のためのお膳立てが整いました。どんどん改善していきましょう。
改善第2弾: workflowsを使ってみる
次にworkflowsを使ってデプロイ周りの処理をリファクタリングしていきます(ちなみにworkflowはCircleCI 2.0から使える機能です)。
以降のyamlから既に引用してあるyamlと同じ記述は ...
として省略しています。
version: 2.1 executors: default: ... commands: bundle_install: ... jobs: rspec: executor: default steps: - checkout - bundle_install - run: bundle exec rspec deploy_staging: executor: default steps: - run: name: Deploy to Staging command: "=== deploy operation ===" workflows: test: jobs: - rspec - deploy_staging: requires: - rspec context: deploy filters: branches: only: develop
ポイントは下記の通り。
develop
ブランチのビルド時のみdeployコマンドを走らせるのでそのようなfilterを適応- deployはrspecのテストがうまくいったときのみ実行(
requires: rspec
) context
の機能を使い、デプロイのために必要な環境変数はオーガナイゼーショングローバルで管理可能なように切り出し
結果として、下記のようなRSpec→Deploymentというワークフローが仕上がりました。
さぁワークフローっぽくなってきましたね。まだ改善できそうなのでさらに改善させていきます。
改善第3弾: rubocop自動レビューをワークフローに導入(with reviewdog)
私たちのプロジェクトではrubocopを導入しています。rubocopは途中から導入したためrubocopルールを強制すると大変なrubocop diffが出てしまうので今まで各個人の努力でrubocopルールを遵守する・レビューで気づいたrubocopルール違反があれば指摘するという人間による運用を続けていました。
しかしrubocop導入が一年ほどたちrubocopルール遵守の意識もチームに根付いてきたので、このワークフロー整備がよいきっかけだと思い自動レビューの仕組みをワークフローに組み入れてみることにしました。
自動レビューの仕組みはいくつかありますが今回はhaya14busa/reviewdogを使わせてもらうことにしました。なおrubocop+reviewdogを用いた自動レビューの仕組みは私の個人ブログにまとめてありますので気になる方はこちらを参照してみてください。
改善した結果仕上がったconfig.yml
はこちら。
version: 2.1 executors: default: ... commands: bundle_install: ... jobs: rubocop: executor: default steps: - checkout - bundle_install - run: name: Install reviewdog command: | curl -fSL https://github.com/haya14busa/reviewdog/releases/download/$REVIEWDOG_VERSION/reviewdog_linux_amd64 -o reviewdog && chmod +x ./reviewdog - run: bundle exec rubocop | ./reviewdog -f=rubocop -reporter=github-pr-review rspec: ... deploy_staging: ... workflows: test: jobs: - rubocop: context: reviewdog - rspec: requires: - rubocop - deploy_staging: context: deploy requires: - rspec filters: branches: only: develop
どんなワークフローになったかみてみましょう。
ポイントは下記です。
- rubocopの修正を強要するためrubocopが成功したときのみ後続の処理を走らせる
- reviewdog はGitHub Pull Requestにコメントする権限を持っている必要があるのでTokenはreviewdog contextで切り出し
しかしまだ終わりではありません。さらに改善させていきましょう。
改善第4弾: CircleCI Orbsでプロジェクト間の処理を共通化
次はCircleCI Orbsを使ってプロジェクト間の処理を共通化していきましょう。
本日Orbsをリリースしました🥳OrbsはWorkflow以来のメジャーアップデートです。OrbsはCircleCIの設定をパッケージ化する仕組みで、誰でも自分のOrbsを公開できます。これはCI/CDサービスとしては初の試みで、今後はユーザーやパートナーを巻き込んでCircleCIのエコシステムを構築することができます。
— CircleCI Japan (@CircleCIJapan) 2018年11月7日
どこが共通化できそうか考えた結果、下記の2つができそうということでCircleCI Orb化しました。
- toshimaru/bundle-install: Ruby/Ruby on Railsプロジェクトであれば大体やるであろうbundle install をOrb化1
- toshimaru/aws-opsworks-deploy: Gunosyで使っているOpsWorksデプロイのインストラクションをOrb化
ちなみにCircleCI Orbの公開手順も私のブログにまとめてありますので、興味がある方はどうぞ。
仕上がった config.yml
はこちら。
version: 2.1 orbs: bundle-install: toshimaru/bundle-install@0.1.1 aws-opsworks-deploy: toshimaru/aws-opsworks-deploy@0.0.1 executors: default: ... jobs: rubocop: executor: default steps: - checkout - bundle-install/bundle-install ... rspec: executor: default steps: - checkout - bundle-install/bundle-install ... workflows: test: jobs: - rubocop: context: reviewdog - rspec: context: deploy requires: - rubocop - aws-opsworks-deploy/deploy: name: "deploy-to-staging" requires: - rspec context: deploy aws-region: ap-northeast-1 stack-id: STACK_ID app-id: APP_ID command: '{"Name": "deploy", "Args":{"migrate":["true"]}}' filters: branches: only: develop
これで完成!…と言いたいところですがもう一声!production環境のdeployもワークフローに組み込んでみましょう。
改善第5弾: Production Deployを人間の手をかまして行う
上述した設定の通り、stagingのデプロイはCircleCIより自動で行っていましたが、productionの自動デプロイは本プロジェクトでは行っておりませんでした。これは本番のデプロイはリリースタイミングの調整が必要だったりDBマイグレーションを気をつけて行う必要であったりすることが主な理由です。
しかし人の手をかますようにしたらどうでしょうか。幸い、CircleCIにはapproveの機構があります。これを使って手動のApproveのステップを挟んで安全なProductionデプロイを実現しましょう2。
version: 2.1 orbs: bundle-install: toshimaru/bundle-install@0.1.1 aws-opsworks-deploy: toshimaru/aws-opsworks-deploy@0.0.1 slack: circleci/slack@0.1.10 executors: default: ... jobs: rubocop: ... rspec: ... send-approval-link: executor: default steps: - slack/notify: message: | Please check and approve Job to deploy. https://circleci.com/workflow-run/${CIRCLE_WORKFLOW_ID} workflows: test: jobs: - rubocop: ... - rspec: ... - aws-opsworks-deploy/deploy: ... - pending-approval: type: approval requires: - rspec filters: branches: only: master - send-approval-link: requires: - rspec filters: branches: only: master - aws-opsworks-deploy/deploy: name: "deploy-to-production" requires: - pending-approval context: deploy aws-region: ap-northeast-1 stack-id: STACK_ID app-id: APP_ID filters: branches: only: master
結果できたフローはこちらになります。
ポイントは下記です。
- masterビルド成功時に
pending-approval
の処理を挟み込む - もし
pending-approval
がApproveされれば後続のProduction Deployのステップへと進む pending-approval
時にSlackでapproveのためのリンクを送付
これにて今回のワークフロー改善は終了です!!!
最後に
以上、ステップバイステップでCI/CDワークフローの改善の歩みを紹介してきました(実際はこれを一ヶ月半くらいかけてちょこちょこと進めてきていました)。たかがCI/CDワークフロー改善、されどCI/CDワークフロー改善。このような改善を積み重ねることが開発者のDeveloper Experienceの向上に繋がります。
CircleCI 2.1とCircle Orbsを組み合わせることでCI/CD改善の幅は圧倒的に広がったと思っています。みなさんの開発の現場でもワークフロー改善にチャレンジしてみてはいかがでしょうか?
-
sue445さんが作成されたsue445/ruby-orbsの
bundle-install
コマンドが細かなオプションを設定できるリッチなbundle installコマンドに仕上がっているので、必要に応じてそちらも使用してみてください↩ - ただ本設定は各ビルド毎にデプロイリソースを作成するようなワークフローにおいては、デプロイ事故が発生する可能性があるので気をつけたほうがよいです。事故の例としては例えば、誤って過去のワークフローのApproveをしてしまい1週間前のコードベースがデプロイされるなどが考えられます。↩