複数チームが開発するモノリシックなサービスにおける、Rubyのバージョンアッププロジェクトの進め方とこのプロジェクトから学んだこと

こんにちは、アンドパッドSWEの あかりです。

今年もやってきました、花粉症の季節ですね😇 去年、「来年こそはこの時期だけ沖縄に避難する!」と固く誓っていたですが、子供が産まれてそんなことが言ってられなくなリました。嬉しい悲鳴です。
ですので、家に引きこもることにした、その成果がこちらの記事になります!

1. 概要

最近、アンドパッドで最も古くから稼働している施工管理サービスのRubyを2.7から3.0にバージョンアップしました。この記事では、バージョンアッププロジェクトの進め方と今回のプロジェクトを通して学んだことを説明します。

2. Rubyバージョンアッププロジェクトの始動

複数のチームが開発を行うモノリスでは、Ruby・Rails・各種gemやライブラリのアップデートを誰が責務を持って行うのか、宙に浮きがちな問題です。施工管理サービスも歴史が長く、複数のチームが開発を行うモノリシックなサービスで、例に漏れない状況でした1。しかし、誇れることではないのですが、Ruby 2.7が2023年3月31日にEOLを迎えてしまうことによるバージョンアップ熱の高まりがありました。また、Ruby 3.0で施工管理サービスのRSpecを全て成功させるようにr7kamuraさんが精力的に修正を進めてくださったこともあり、今回のRubyバージョンアップの狼煙が上がりました。そして、施工管理サービスのRubyをバージョンアップするためのメンバーがアサインされ、プロジェクトがスタートしました。

3. プロジェクトの進め方

このプロジェクトはおおよそ以下の4ステップで進行しました。

  1. 全チームに動作確認の工数確保を依頼し、動作検証期間のスケジュールを確保する。
  2. 動作検証期間の開始時に、本番環境以外の全ての環境でRubyをバージョンアップし、全チームに動作検証を依頼する。
  3. 動作検証中にRubyバージョンアップ起因の不具合を全て洗い出し、これを修正する。
  4. Rubyバージョンアップ起因の不具合が全て修正され、全チームの動作検証が完了したことを確認して、本番環境のRubyをバージョンアップする。

4. 動作検証期間中のCI/CD

動作検証期間中にCI/CDをどのように設定していたのか気になる方も多そうなので、これについて説明します。

4-1. CD

動作検証期間中のCDは、以下のように設定していました。

  1. Ruby 2.7用のGemfile.lockとは別に、Ruby 3.0用のGemfile.lockをGitリポジトリに載せて並行運用。イメージのビルド時に、使用するGemfile.lockを切り替える。
  2. 全ての開発環境(ローカルを含む)では、Ruby 3.0をデフォルトでイメージビルド & デプロイする。その一方で、本番環境ではRuby 2.7をデフォルトでイメージビルド & デプロイする。

上記のことが可能だったのは、施工管理サービスが全てコンテナで動作する2ようになっていた恩恵でした3。 また、以下のおかげでこれを実現するのも比較的容易でした。

  • 引数として受け取ったRubyのバージョンでイメージをビルドするようにDockerfileが記述されていた。
  • イメージのビルドを行っているCodeBuildも環境変数としてRubyのバージョンを受け取るように実装されていた。

これを図にすると、以下のようになります。

つまり、開発環境ではRubyバージョンを3.0としてCodeBuildの環境変数に設定し、本番環境ではRubyバージョンを2.7としてCodeBuildの環境変数に設定すれば良いだけでした😁

4-2. CI

また、動作検証期間中のCIは、以下のように設定していました。

  1. RSpecは本番環境のRubyバージョンである2.7とバージョンアップのターゲットである3.0とで並列実行させる。
  2. RuboCopは本番環境のRubyバージョンである2.7をターゲットとして動作させる。
  3. gemfile-diffというGitHub Actionsのカスタムアクションを利用して、Ruby 2.7用のGemfile.lockとRuby 3.0用のGemfile.lockとで、特定のgem以外に差分がないことを確認する。

まず、1についてですが、検証期間中も本番環境はRuby 2.7で動作していたため、必ずRuby 2.7で動くコードが本番に適用される必要がありました。また、検証期間中に、Ruby 3.0へアップグレードすると動かなくなるコードをマージして欲しくありません。ですので、RSpecについては、Ruby 2.7と3.0とを並列実行させるようにしました。

次に、2についてですが、RSpecと同様の理由でRuboCopもRuby 2.7と3.0とで並列実行させることも考えられました。しかし、RuboCopについては並列実行のメリットが少なかったため、Ruby 2.7だけで動作させるように設定しました。なぜならRuby 2.7では違反にならず、3.0では違反になる記述を追加される可能性が低かったからです。

そして、3についてですが、Rubyバージョンアップの検証期間中も、その他gemのアップデートを通常通りに行いたいという要求がありました。ですので、Ruby 2.7用に用意したGemfile.lockとRuby 3.0用に用意したGemfile.lockとに意図しない差分が発生しないよう、CIで監視しました4

5. プロジェクトの振り返り

このプロジェクトは特段の問題なく進行し、本番環境のRubyバージョンアップが完了しました。ですが、執筆時点(2023/02/14)ではRuby 3.2がリリースされており、この勢いで最新バージョンまでアップグレードしたい意気込みです。また、今回のプロジェクトが問題なく終えられたのは、各チームの開発者の察しや機転によるところも大きかったと思います。なので、今後は人に依存しない形でしっかりプロジェクトを進行させられるようノウハウを蓄積していきたい所存です。ですので、またすぐに始まるであろうRuby 3.2へのバージョンアッププロジェクトに備えて、ここでは今回のプロジェクトの改善点を列挙しておきます。

  • 動作検証の期限とコードフリーズ日とには少し期間を空けておき、検証期限日に要修正箇所が発見されたとしても、その修正を反映するための余白期間を設けておく。
  • 動作確認依頼のリマインドがスルーされないように、@hereメンションでリマインドを送るのではなく、チーム単位で代表者を指名してリマインドを送るようにする。
  • 動作検証をしていただいている各チームへの依頼事項は課題発見のみで、発見された課題解決はRubyバージョンアップチームが担う旨を明確にしておく。
  • オーナーが曖昧なAPIや非同期ジョブをリストアップしておき、今回のような広域の動作検証で抜け漏れが発生しないようにする。
  • 開発者の混乱を招かないよう、動作検証期間中は検証可能なデプロイ環境の提供方法を一貫しておく。

6. まとめ

この記事では、複数チームが開発を行うモノリシックなサービスのRubyバージョンを2.7から3.0へバージョンアップしたプロジェクトについて、プロジェクトの進め方とそこから学んだことについて説明しました。執筆時点(2023/02/14)での最新のRubyバージョンは3.2なので、このプロジェクトで得たノウハウを活かして、勢いそのまま最新バージョンまでアップグレードしたいと考えています。あ、そうそう、Ruby 3.2からはYJITが実用段階になっているので、その際はパフォーマンス改善も絡めてご報告させていただければと思います^^

最後に

アンドパッドでは、様々な技術要素を活用して開発に取り組んでいます。 一緒に開発、挑戦していただける方を大募集しておりますので、興味のある方は下記をご覧ください!

engineer.andpad.co.jp

hrmos.co


  1. 最近、チーム横断的な改善プロジェクトが立ち上がり、仕組みを改善しています。
  2. 一部、開発者の作業用サーバがコンテナ化されていないので、絶賛改善中です。
  3. コンテナ化した経緯については、ANDPAD本体サービスをEKSに移行しました施工管理サービスの定期実行ジョブ処理基盤をEC2からコンテナへ移行しましたをご参照ください。
  4. r7kamuraさんが過去のテックブログで説明しているので、詳細はそちらをご参照ください。