11月に入社したCTO室SREの@sinsokuです。
主にやっていることはRailsアプリのレビューや開発環境の改善です。*1
- 社内のRailsアプリを横断して浅くレビューする(8つくらい)
- MedPeerの開発環境の改善
docker-compose up
で30個のコンテナが起動するのを減らす
- SwitchPointからActiveRecord v6への移行
- CircleCIの実行時間の短縮、稀に落ちるテストの修正
- その他の細かい改善
このうち、CircleCIについて知見が溜まったので技術ブログで紹介します。
CircleCIで気をつける点
CircleCIの実行時間を短くするにはいくつかコツがあります。
- gemとnpmをできるだけキャッシュする
- RSpecを並列で実行する前に
assets:precompile
を実行しておく - 各ジョブで必要なgem(もしくはnpm)だけをキャッシュから復元する
- 例: JavaScriptのテストはnpmのキャッシュだけ復元する
ワークフローを図にするとこんな感じです。
キャッシュの仕組みについて
詳しく知りたい人はCircleCIのページを読んで頂くとして、ここでは一番重要な 部分キャッシュについて説明します。
CircleCIのキャッシュキーには複数のキーを指定することができます。
- restore_cache:keys: # 「OSとCPUの種類」「ブランチ名」「Gemfile.lock の checksum」でキャッシュを探す- gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }} # 上のキーで見つからない場合、「OSとCPUの種類」「ブランチ名」でキャッシュを探す- gem-cache-v1-{{ arch }}-{{ .Branch }} # それでも見つからない場合、 `gem-cache-v1` で始まる最新のキャッシュを探す- gem-cache-v1
キャッシュキーを複数指定すると上から順番にキャッシュを探します。
これによりGemfile.lockが変わっても、 bundle-installで全てのgemをインストールしないで済むようになります。
キャッシュの肥大化
部分キャッシュを使っているとgemが増え続け、キャッシュのリストアに時間がかかる問題が起きます。bundle install --clean
すれば良いのですが、少しずつ遅くなるため気づき辛い問題です。
参考: bundle install には --clean を指定する (特に Circle CI では) | Born Too Late
ちなみにYarnは自動的に不要なnpmを消してくれます。🐈
Rails Orb
上記の点を気にしながら、社内のRailsアプリで横断的に対応するのは大変なので、誰でも良い感じに設定できるOrbを作りました。
実際に社内のいくつかのRailsプロジェクトに導入しています。
使い方
Orbの提供するジョブやコマンドの詳細はOrb registryを参考にしてください。
また、Rails Orbを使う前にCircleCIの設定画面で uncertified orbsを許可する必要があります。
gemやnpmのキャッシュについて
Gitリポジトリのデフォルトブランチをキャッシュキーに含めることでキャッシュヒット率を上げています。
- restore_cache:keys:- << parameters.key >>-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }} - << parameters.key >>-{{ arch }}-{{ .Branch }} - << parameters.key >>-{{ arch }}-{{ checksum ".git/refs/remotes/origin/HEAD" }} - << parameters.key >>-{{ arch }}
一般的なジョブを提供
CircleCIの設定を簡単にできるように、以下4つのジョブを提供しています。
- rb-deps:
bundle install
を実行する - js-deps:
yarn install
を実行する - assets:
assets:precompile
を実行する - rspec: RSpecでテストを並列に実行する
新規案件で rails new
した直後なら以下の設定で良い感じにCircleCIが動きます。
version:2.1orbs:rails: medpeer/rails@x.y.z executors:ruby:docker:- image:&docker_ruby circleci/ruby:2.7.0-node-browsers ruby_with_db:docker:- image:*docker_ruby- image: circleci/postgres:12.1-alpine-ram environment:DATABASE_URL:'postgres://postgres:postgres@127.0.0.1:5432'workflows:rspec:jobs:- rails/rb-deps:executor: ruby - rails/js-deps:executor: ruby - rails/assets:executor: ruby requires:- rails/rb-deps - rails/js-deps - rails/rspec:executor: ruby_with_db db-port:'5432'parallelism:4requires:- rails/assets
ジョブとコマンドを組み合わせる
Orbの提供するrb-deps
ジョブとbundle-install
コマンドを組み合わせると、RuboCopを実行するジョブなども短く書けます。
version:2.1orbs:rails: medpeer/rails@x.y.z executors:ruby:docker:- image: circleci/ruby:2.7.0-node-browsers jobs:rubocop:executor: ruby steps:- checkout # Restore gems from cache- rails/bundle-install:restore_only:true- run: bundle exec rubocop --parallel workflows:rspec:jobs:- rails/rb-deps:executor: ruby - rubocop:requires:- rails/rb-deps
まとめ
Rails Orbを使うと .circleci/config.yml
の記述をかなり減らせるかなと思います。
各社でCircleCIのYAML職人をしている方、ぜひRails Orbを試してみてください。
(☝︎ ՞ਊ ՞)☝︎是非読者になってください
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら
https://medpeer.co.jp/recruit/workplace/development.html
*1:SRE所属だけど、あまりSREっぽい仕事をしていない