メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

CircleCIのYAMLを短く書けるRails Orbを作りました

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のキャッシュだけ復元する

ワークフローを図にするとこんな感じです。

f:id:sinsoku:20200207111113p:plain
CircleCIのジョブのワークフロー

キャッシュの仕組みについて

詳しく知りたい人は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を作りました。

github.com

実際に社内のいくつかのRailsプロジェクトに導入しています。

使い方

Orbの提供するジョブやコマンドの詳細はOrb registryを参考にしてください。

また、Rails Orbを使う前にCircleCIの設定画面で uncertified orbs を許可する必要があります。

f:id:sinsoku:20200212184310p:plain

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っぽい仕事をしていない