はじめに
Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。
一般的にフレームワークはバージョン毎にEOL (End of Life)が定められています。EOLとは製品のサポートが終了する年月日のことです。EOLを過ぎた製品に不具合やセキュリティホールが見つかったとしても修正されることはありません。
RailsのEOLは以下のサイトなどで確認できます。
例えば、Rails 6.0のEOLは2023年6月1日なので、仮に2023年6月1日以降に致命的なセキュリティホールが見つかったとしても、そのセキュリティホールが修正されることはありません。セキュリティホールが存在するフレームワークのバージョンを使い続けているWebシステムは、悪意のあるユーザーによる攻撃のターゲットとなる恐れがあります。
先般、仕事で使っているRailsのアップグレード作業(Rails 5.2系 → Rails 6.0系)を無事完了させることができました。アップグレード作業は基本的に私一人で行いました。幸いなことに、アップグレード作業が完了してから現在まで、アップグレード作業による問題は一件も発生していません。しかし、アップグレード作業を振り返ったときに「こうしておけば良かった」などいくつか反省点があるので、それらを踏まえて記事にまとめたいと思います。
参考ドキュメント
Railsのアップグレード作業を行うにあたって、事前に目を通しておくべきドキュメントを記載します。
Railsアップグレードガイド
RailsガイドのRailsアップグレードガイドは必読です。アップグレード作業に着手しはじめる前に全体像を俯瞰して把握しておいてください。また、可能ならこの時点でアップグレード作業のタスクを洗い出しておき、社内ナレッジベースのような場所にドキュメントを残しておくといいでしょう。このドキュメントはアップグレード作業が完了するまで随時アップデートしていってください。
Railsアップグレードガイドはアップグレード作業中にも度々お世話になるので、アップグレード作業が完了するまで常に開いておくといいでしょう。
リリースノート
Railsガイドのリリースノートも事前に一通り目を通しておくといいでしょう。Railsアップグレードガイドよりも詳細な変更点が記載されています。
Railsアップグレードのまとめ記事
RailsのEOLが近づいてくると、Railsアップグレード作業のまとめ記事が続々と公開されてきます。Railsは国内の大手Webシステムで使われていることも多いため、検索すると大手Webシステムの技術ブログによるまとめ記事が見つかることが多いです。
大手になるほどアップグレード作業を行う時期が早い傾向にあります。上記の記事によると、freee会計では2021年12月にRails 5系からRails 6系へのアップグレード作業を行ったとのことです。Rails 5.2のEOLは2022年6月1日なので、半年も前にアップグレード作業を完了していることになります。
まとめ記事で扱っているアップグレードの対象バージョンと、今回のアップグレード作業の対象バージョンが一致していることが理想ですが、そうでなくても参考になることが多いので、この手のまとめ記事には目を通しておくといいでしょう(できれば複数)。
Rubyのバージョンアップ
Railsの動作が保証されているRubyバージョンは以下の通りです。
- Rails 7: Ruby 2.7.0以降が必須
- Rails 6: Ruby 2.5.0以降が必須
- Rails 5: Ruby 2.2.2以降が必須
Railsのアップグレード作業を行う前にRubyのバージョンアップ作業を行います。Railsのアップグレード作業とRubyのバージョナップ作業はそれぞれ独立して行います。
今回はRubyのバージョンアップ作業(2.5.7 → 2.7.6)を行っていたので、バージョンアップ後のRubyを以下の手順でRailsアップグレード作業の作業ブランチに反映しました。
# rbenvのバージョン確認
% rbenv --version
# インストール可能な安定バージョン
% rbenv install --list
# Rubyのインストール
% rbenv install 2.7.6
# `git switch branch`すると.ruby-version記載のバージョンに
# 自動で切り替わるので以下を実行する必要はない
% rbenv local 2.7.6
# Rubyのバージョン確認
% rbenv version
% ruby --version
# バージョンごとにGemをインストール
% gem update -system
% gem install bundler
% bundle --version
% bundle install
# rails server再起動
% rails server
Rubyのバージョンアップ:余談①
bundle install --path vendor/bundle
を実行するとvendor/bundle/ruby/x.x.x/gems
配下にGemがインストールされます。Railsはrails serverの起動時に使用するGemを読み込むので、rails serverを起動したままRubyのバージョンを切り替えると、意図しないGemを使用することになるので注意。Rubyのバージョンを切り替えた際は忘れずにrails serverの再起動を行ってください。
# Rubyのバージョン確認
% rbenv version
2.5.7
# rails server起動
% rails server
# Rubyのバージョン切替
% rbenv local 2.7.6
# Rubyのバージョン確認
% rbenv version
2.7.6
# => Rubyのバージョンは2.7.6だが
# vendor/bundle/ruby/2.5.0/gemsのGemを使い続ける
# rails server再起動
% rails server
# => vendor/bundle/ruby/2.7.0/gemsのGemが読み込まれる
Rubyのバージョンアップ:余談②
GemのインストールはRubyのマイナーバージョン毎に分かれています。例えば、Ruby 2.7.5とRuby 2.7.6はともにvendor/bunldle/ruby/2.7.0/gems
配下にGemがインストールされます。このとき、Rubyのパッチバージョン以下の切り替えする場合でも、Gemはインストールし直す必要があります。
# Rubyのバージョン確認
% rbenv version
2.7.5
# Ruby 2.7.6へバージョンアップ
% rbenv install 2.7.6
% rbenv local 2.7.6
% rbenv version
2.7.6
# Railsコンソール実行
% rails console
# => エラーが発生する場合がある
# 2.7.0のGemを削除
% rm -rf vendor/bundle/ruby/2.7.0
# Gemをインストールし直す
% gem install bundler
% bundle install
# Railsコンソール実行
% rails console
Gemのバージョンアップ(Rails除く)
Railsを除くGemのバージョンアップを行います。アップグレード後のRailsバージョンと互換性のないGemがあるかもしれないので、基本的にすべてのGemを最新バージョンにします。
ただし、Gemをバージョンアップしたことによりエラーが表示されたり、大幅なコード修正が必要になりそうなときは無理にバージョンアップはせず、現状維持としました。
Gemのバージョンアップ方針
Gemのバージョンアップ方針は以下が考えられます。下にいくほど影響が局所化されるので、より安全にバージョンアップできるでしょう。
- すべてのGemを一気にバージョンアップ
- グループ毎にGemをバージョンアップ
- Gemを一つひとつバージョンアップ
今回は「グループ毎にGemをバージョンアップ」していくことにしました。Gemの数が多かったのである程度まとまった単位でバージョンアップしたかったのと、グループ単位であればそれほど影響は大きくならなさそうだと判断しました。
Gemのバージョンアップ手順
Gemfile
に記載されているGemでバージョン指定してあるものはすべて外します。その後、以下のコマンドを実行してGemのバージョンアップを行います。
% bundle update [--group group-name [group-name]]
group-name
にはdevelopment
やproduction
などのグループ名を指定します。半角スペースを挟んで複数指定することもできます。
Gemのバージョンアップができたら、VCSにpushし自動テストを実行します。自動テストがすべてパスするまでコードを修正しpushします。
Railsのアップグレード
Railsのアップグレード方針
今回は以下の順番でRailsのアップグレード作業を行いました。
- Rails 5.2系を最新バージョンまでアップグレード
- Rails 6.0系の最新バージョンにアップグレード
まず、「5.2.5 → 5.2.6.3 → 5.2.7」というようにRails 5.2系の最新バージョン(当時)まで順番にアップグレードしていきました。しかし、ここまで段階を踏む必要はなかったように思います。パッチバージョン程度であれば一気にRails 5.2系の最新バージョンまでアップグレードしてしまっても良かったかもしれません。
Rails 5.2系の最新バージョンまでアップグレードできたら、満を持してRails 6.0系の最新バージョンにアップグレードしました。後方互換性のない変更が含まれるメジャーバージョンアップなので、大量のエラーが出ることを覚悟して臨みましょう。
Rails 5.2系からRails 6.1系へアップグレードすることが予定されている場合でも、必ずRails 5.2系からRails 6.0系へのアップグレード作業を行い、その後にRails 6.0系からRails 6.1系へのアップグレード作業を行ってください。基本的にマイナーバージョン以上が上がっていればアップグレード作業を行う必要があります。
Railsの全バージョンは以下のサイトで確認できます。
Railsのアップグレード手順
Gemfile
に記載されているRailsのバージョン指定を書き換えて以下のコマンドを実行します。
% bundle update rails
Railsのアップグレードができたら以下のコマンドを実行してアップグレードタスクを行います。アップグレードタスクを行うことで、新しいバージョンでのファイル作成や既存ファイルの変更を対話形式で行うことができます。
% rails app:update
次に、load_defaults
のバージョン指定を変更します。
config/application.rb
config.load_defaults 6.0
ただし、パッチバージョン以下のアップグレードであればload_defaults
を変更する必要はありません(むしろパッチバージョン以下を指定するとエラーになります)。
config.load_defaults 5.2.6.3
# => SyntaxError: /path/to/my_app/config/application.rb:14: unexpected fraction part after numeric literal
# ... config.load_defaults 5.2.6.3
# ... ^~~~
マイナーバージョン以上のアップグレードを行いアップグレードタスクを実行すると、config/initializers/
ディレクトリ配下にnew_frameworks_defaults_X_Y.rb
というファイルが作成されます。これは、アプリケーションを新しいデフォルト設定に1つずつアップグレードできるようにするための設定ファイルです。このファイルに記載されている設定のコメントアウトを外すことで、新しいデフォルト設定を個別に有効にしていくことができます。
Railsのアップグレードを行いアップグレードタスクも行ったら、、VCSにpushし自動テストを実行します。自動テストがすべてパスするまでコードを修正しpushします。
また、rails consoleやrails serverを実行して非推奨警告などが出ていないかも確認します。画面上での動作確認はすべてのアップグレード作業が完了してからでも問題ないでしょう。
テスト
すべてのアップグレード作業が完了したら手動テストを行います。ある程度のテストカバレッジは自動テストで担保されていますが、自動テストでは担保されていない箇所や特に重要な機能などは人の手でテストしたほうがいいでしょう。
テスト方針
今回、Railsのアップグレード作業を行ったWebシステムはECサイトです。一般の消費者が商品の閲覧や購入をするECサイトの他に、ユーザー管理や商品管理を行うための管理画面があります。特に重要な機能の選定や時間的な制約を勘案し、以下のようなテスト方針を策定しました。
- 一般の消費者が利用するECサイトはQAで実施
- すべての機能を網羅的に確認
- 全画面でレイアウト崩れがないことを確認
- 商品購入機能(最重要機能)の確認
- OAuth連携(Gemバージョンアップの影響あり)の確認
- 管理画面とバッチ処理は自社が実施
- 基本的なCRUD処理以外の機能
- バージョンをまたぐ商品購入機能の確認
QAによるテスト
今回のRailsアップグレード作業を行っているときにちょうどQAの導入が決まったため、QA利用の第一弾として、一般の消費者が利用するECサイトのテストをお願いしました。管理画面はやや複雑な構成のため、テストを実施してもらうメリットよりも、QAにイチから説明する労力のほうがデメリットだと判断しました。
QAが行ったテストでは、Railsアップグレードが原因と思われる不具合はありませんでした。不具合がないという確認が取れ、安心できたのは大きかったです。
自社によるテスト
管理画面とバッチ処理のテストは自社で行いました。基本的なCRUD処理は自動テストで担保されているはずなので、それ以外の機能やGemバージョンアップの影響がありそうな機能に絞ってテストを行いました。
バージョンをまたぐテスト
Rails 5.2系とRails 6.0系ではデフォルトのCSRF Tokenのフォーマットが異なるため、Railsアップグレードによりフォーマットが変更されないように以下の設定を行いました。
config/application.rb
config.action_controller.urlsafe_csrf_tokens = true
仮に上記の設定を行わないままRailsアップグレードを行ってしまうと、アップグレードの前後でCSRF Tokenのフォーマットが異なることになり、エラーが発生してしまいます。
そのため、最も重要な機能である商品購入機能においてバージョンをまたぐテストを行いました。
- 購入確定の直前まで進める
- 新バージョンをリリース
- 購入確定する
上記のテストを行い、正常に商品が購入できることを確認しました。
RailsのCSRF Tokenに起因する問題について詳しくは以下のGitHub Issueを参照してください。
振り返り
良かった点
ドキュメントの作成
今回のRailsアップグレード作業で行ったことはすべて社内のナレッジベースにドキュメントを残しながら進めました。Railsアップグレード作業で具体的にどういうことを行ったかを書いて残しておくと様々なメリットがあります。
- 抜け漏れに気づきやすい
- 振り返りがしやすい
- コードレビューがしやすい
- 社内の知的財産になる
レビュアーいわく「大作」だというRailsアップグレード作業のドキュメントがあることで、コードレビューが非常にしやすかったと言ってもらえました。後述する「全体構成図の作成」もそうですが、第三者にも見てもらうことを意識しながらドキュメントを書くといいでしょう。私はこの技術ブログで書くことには慣れているので、同じノリで書いていました。
全体構成図の作成
今回のRailsアップグレード作業は基本的に私一人で行ったわけですが、実はアップグレード作業を全面的に任せられることになったのは、私が案件に参画してからわずか3ヶ月ほどしか経っていないときでした。そのため、まだWebシステムの全体像を把握しきれていなく、Railsアップグレードの影響を正確に図れないという懸念がありました。
そこで、まずはWebシステムの全体構成図を作成することにしました。ローンチから7年が経つWebシステムでしたが、なんと全体構成図が存在しなかったのです。Webシステムは一般の消費者が利用するECサイトの他に、巨大な管理画面群があるという構成です。この管理画面群を目で確認しながら、MarkdownのMermaid記法を使ってフロー図を作成しました。
全体構成図を作成したことにより、どこにどんな機能があるかということが俯瞰できるようになりました。全体構成図があることによって楽にテスト方針が策定することができましたし、QAのコントロールにも役立ちました。
また、ローンチから7年が経過するそれなりに大きいWebシステムにもかかわらず、それまで全体構成図が存在しなかった(なぜか誰も作成しようと思わなかった)ため、チームメンバーからは大いに感謝されたことも良かったです。
QAの利用
今回のRailsアップグレード作業を行っているとき、偶然(?)にも会社としてQAを導入することが決まったため、その第一弾としてECサイトのテストをQAにお願いすることにしました。初めはQAの稼働開始時期の調整などにより、QAを利用できるか微妙だったのですが、Railsアップグレードの重要性を説明し、無理を言ってねじ込んでもらいました。
QAには簡単にWebシステムの概要を説明し、先述の全体構成図を共有しただけでしたが、約3週間ほどでテスト項目の作成からテスト実施まで行ってもらいました。QAのテスト中に見つかった不具合については都度チケットを作成してもらい、ほぼチケット上のやり取りだけで済みました。
QAのテストで見つかった不具合はどれも既存バグが原因によるもので、それらはRailsアップグレード作業とは分けて対応することにしました。結果的にRailsアップグレードが原因と思われる不具合は見つからなかったので、その確認が取れたのは大きかったです。
悪かった点
リリースの遅れ
今回のアップグレード作業に着手し始めたのは2022年4月頃で、アップグレード作業が完了した(リリースした)のは2022年8月頃でした。Rails 5.2のEOLは2022年6月1日なので、EOLを迎えてから約2ヶ月後にアップグレードが完了したことになります。
2ヶ月程度なら問題になることは少ないですが、本来ならEOLを迎える前にアップグレード作業を完了しているべきです。今回は様々な要因でリリース日が後ろ倒しになっていったのですが、それも勘案してアップグレード作業に着手し始めるべきです。
遅くともEOLを迎える半年前には着手し始めるべきだと考えます。大手Webシステムの中には、EOLを迎える半年前にアップグレード作業が完了しているところもあります。
ビルド失敗の原因特定
GemのバージョンアップにしろRailsのアップグレードにしろ、何かを行ったらVCSにpushし、CIによるビルド成功とテストがすべてパスすることを確認することが基本戦略になります。
今回のRailsアップグレード作業は比較的スムーズに進められましたが、1度だけCIによるビルド失敗の原因特定に手間取ったことがありました。原因特定に手間取った遠因として、複数の重要なコミットを含むpushをしてしまったことが挙げられます。
- ローカルでコミットAを行う
- ローカルでコミットBを行う
- コミットA/コミットBをリモートにpush
- CIによるビルド失敗(コミットAが原因)
ビルド失敗の主原因はコミットAで行った設定だったのですが、私は直近に行ったコミットBに原因があると思い込み、そのアプローチで調べ続けていました。コミットBで行った設定をどう見直してもビルド失敗が解消されず、1日半ほど経ってようやくコミットAに思い至りました。
このことから、特に重要なコミットを含む場合はこまめにpushすることが大切だと思いました。
ステージング環境の設定
Railsアップグレードのテストはステージング環境で行いました。ステージング環境は「準本番環境」なので、そこで問題なければ本番環境でも問題ないだろうと考えられます。
しかし今回、本番環境とステージング環境のわずかな設定の相違が原因で、本番環境にリリースした直後に監視サービスがエラーを出力する事象に遭遇しました。幸いすぐにロールバックを行って復帰できたので、問い合わせがくるような事態には至りませんでした。
もし本番環境とステージング環境の設定が同じだったら、ステージング環境にリリースした時点でエラーに気づけたはずです。現実的には様々な理由により本番環境とステージング環境の設定を完全に同じにすることは難しいかもしれませんが、本番環境とステージング環境の設定は可能な限り合わせておくに越したことはないでしょう。
まとめ
過去に行ったRailsアップグレード作業を思い出しながら記事にまとめてみました。Railsアップグレード作業と聞くと大変そうだと思われるかもしれません(実際、それは間違っていません)。大変そうだから「やりたくない」と思うより、「スキルがつく」と思えるのが「良いエンジニア」なのではないかと思います。
初めてのRailsアップグレード作業は確かに大変ではありましたが、やっぱりやって良かったという気持ちのほうが大きいです。Railsアップグレードをやり切ったという自信が持てたのと、一人のエンジニアとして箔が付いたのではないかと思っています。
今後もし、Railsアップグレード作業を担当する機会が訪れたら、それをチャンスだと思ってぜひ挑戦してみてください。そして、その際は本記事を参考にしていただければと思います。