こんにちは、READYFORでバックエンドエンジニアをしている安本です。
2022年10月17日に主要サービスのアプリケーションをRails 5系からRails 6系へとメジャーアップデートしました🎉
EOLが"Support for Rails 5.2.8 ended on 2022-06-01"
ということもあり、空き時間に進める趣味プロジェクトではなく、スクワッドのミッションのプロジェクトとして対応しました。
issueを振り返ると、2022年5月に開始、リリース完了したのが10月なので、約5ヶ月間かかりました。
一言にRailsアップデートと言っても、モノリシックRailsアプリとAPIサーバーの2つを対応する必要があり、また、10年以上続いているサービスのため、技術的負債を一掃しつつ、コードベースを改善しつつ対応しました。
私はこれまで Rails のアップグレードを行ったことがなかったため、改めてここにやったことを振り返ろうと思います。
やったこと
- ドキュメントを一読
- デッドコード削除とテストカバレッジを上げる
- Rails以外のgemを最新にする
- 5系6系の両バージョンで起動・CIできるようにする
- rails app:update
- DEPRECATIONWARNINGを潰す
- 動作検証
- リリース
ドキュメントを一読
- 変更点を確認する。深追いせず、こういう変更点があったという頭の片隅に入れておいた。(影響があったのはほぼこの中の変更点の影響なのであたりがつけやすくなります)
load_defaults
のデフォルト値や意味を理解する。- configの差分を
railsdiff
で確認する。
デッドコード削除とテストカバレッジを上げる
冒頭に書いたように10年以上続いているサービスのため、デッドコードがこんもりあります。そこで、その作業をRails6のアップデートの作業の一環として対応しました。
また、カバレッジレポートを眺めて、コード行数が多いのにカバレッジが低いものはテストコードを追加したりしました。
Rails以外のgemを最新にする
弊社はDependabot
をランダムアサインしてライブラリのアップデートを対応する運用ですが、お世辞にもうまくワークしておらず、Dependabot
のPRが溜まりに溜まっている状態でした。そのため、こちらもRails6のアップデートの作業の一環として対応しました。
- 未使用gemを削除する。
- development,testはエイヤ!でまとめて上げます。
bundle update -g development -g test
- Rails以外のgemを最新にしていきます。
- 影響範囲の小さそうなものはある程度まとめてリリースします。
- 影響範囲の(大きそう|わからない)ものは一つ一つリリースします。
5系6系の両バージョンで起動CIできるようにする
gemのconflictを避けることができ、また、Rails5は通るがRails6はエラーとなるコードが把握しやすくなります。
main
ブランチ判定用の環境変数RAILS_MASTER
を追加
gem "rails", "~> 5.2.8" if ENV["RAILS_MASTER"]
BUNDLE_GEMFILE
でgemを切り替えられるようにします。gemfiles/rails_6.0.6.gemfile
eval_gemfile File.expand_path("../Gemfile", __dir__) gem "rails", "6.0.6"
- Rails6の
Gemfile.lock
を用意します。- Rails5の
Gemfile.lock
をコピー
- Rails5の
cp Gemfile.lock gemfiles/rails_6.0.6.gemfile.lock
- 環境変数
RAILS_VERSION
BUNDLE_GEMFILE
をセットしてgemをupdateします。
export BUNDLE_GEMFILE=gemfiles/rails_6.0.6.gemfile bundle update rails
- CIの設定
- 環境変数
BUNDLE_GEMFILE
を設定します。
- 環境変数
jobs: rspec: name: rails${{ matrix.rails }} [${{ matrix.index }}] runs-on: ubuntu-latest timeout-minutes: 20 strategy: fail-fast: false matrix: index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] rails: - "5.2.8" - "6.0.6" env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
- CIが両バージョンで通るように修正していきます。
Rails.gem_version < Gem::Version.new("6.0")
の分岐などを駆使しました。
rails app:update
- 差分を1つ1つ確認しつつ、なるべくレビューしやすいDiffになるように取り込んでいきました。
bundle exec rails app:upate
new_framework_defaults_6_0.rb
を確認していきます。- 今回のリリースでは
load_defaults 5.2
の状態でリリースしました。なので、まだ残作業としてload_defaults 6.0
にする作業が残っています。new_framework_defaults
をどう進めるのが良いのか?は、こちらの記事がとても参考になりました。
- 今回のリリースでは
DEPRECATIONWARNINGを潰す
- 対応したもの
- [x]
secrets.secret_token is deprecated
- [x]
Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s)
- [x]
DEPRECATION WARNING: Accessing hashes returned from config_for by non-symbol keys is deprecated and will be removed in Rails 6.1. Use symbols for access instead.
- [x]
DEPRECATION WARNING: ActionDispatch::Http::ParameterFilter is deprecated and will be removed from Rails 6.1. Use ActiveSupport::ParameterFilter instead.
- [x]
DEPRECATION WARNING: Initialization autoloaded the constants ・・・
- [x]
DEPRECATION WARNING: NOT conditions will no longer behave as NOR in Rails 6.1.
- [x]
DEPRECATION WARNING: render file: should be given the absolute path to a file
- [x]
動作検証
テストカバレッジは80%をキープしていますが、いざ結合テスト、モンキーテストを実施してみると、ポロポロと不具合が見つかりました。
READYFORで影響のあったRailsの変更点をまとめておきます。
- DNSリバインディング攻撃からの保護を導入
- send_dataやsend_fileでContent-Dispositionファイル名をエンコードするようになった
- ActionDispatch::Response#content_typeがContent-Typeヘッダーをそのまま返すよう変更
- レコードにアップロードされたファイルを即座でない形で保存するとストレージで永続化するようになった
- ActiveStorage::Downloadingを非推奨化(今後はActiveStorage::Blob#openを利用)
- 画像のvariant生成にmini_magickを直接使うことを非推奨化(今後はimage_processingを利用)
- config_forから返されるハッシュにシンボルでないキーでアクセスすることを非推奨化
- Extract all base_class.name as polymorphic_name
- respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action.
リリース
- 上記作業で、Rails6に関係ない修正は、別PRで小さくリリースしていきました。
- 最終のリリースPRは、Gemfileとconfigの差分のみになるようにしていきました。
- ロールバックが許されないリリースではないこと、弊社はECSでBlue/Greenデプロイということもあり、心理的負担はそれほど高くない状態でリリースできました。
- 想定外のエラーはあるものと覚悟してリリースしましたが、クリティカルな問題は起きず無事リリースを終えることができました。
まとめ
- Rails の新しいバージョンに近づくほど、アップグレードが容易になるので、なるべく早期にアップグレードを対応することが重要。
- アップグレードが遅れだすと、アプリケーションにモンキーパッチを適用したり、ワークアラウンドなコードが増え、技術的負債が溜まり、アップグレードのボトルネックになります。
- Railsアップグレードのような影響範囲のわかりずらい対応には、とにかくカバレッジは重要。
- 80%は許容範囲かと思っていましたが、90%以上にはしていきたいです。