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.1 orbs: 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: 4 requires: - rails/assets
ジョブとコマンドを組み合わせる
Orbの提供するrb-deps
ジョブとbundle-install
コマンドを組み合わせると、RuboCopを実行するジョブなども短く書けます。
version: 2.1 orbs: 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っぽい仕事をしていない