株式会社TECH LUCKという会社で代表兼エンジニアをしている齊藤です。
DXプロジェクト、開発プロジェクト、Rails開発などでお困りごとがありましたら弊社HPからご相談をいただけますと幸いです。
以下のような問題に対応することが可能です。
- プロジェクトでRailsエンジニアが足りなくて困っている
- Railsのバージョンアップをしたいがノウハウ・リソースが足りなくて困っている
- オフショア開発をしているが、要件の齟齬やコード品質が悪いので改善したい
また、Railsエンジニアも募集しておりますので、興味がありましたら弊社HPからご連絡いただけますと幸いです。
前提
4年ほど本番運用していたRuby on Railsのプロジェクトに携わっていました。
その際に、Ruby, Ruby on Rails共にサポート外のバージョンを利用していたので、それらのバージョンアップをしようということになりました。
本記事はRuby, Ruby on Railsを並行してバージョンアップし本番運用まで稼働させた際の備忘録になります。
サマリーとしてやったことは以下の通りです。
- 重要箇所のテストの記述
- 各Controllerの最小限のテストの記述
- 各Modelの最小限のテスト記述
-
gem 'rails'
以外のgemのバージョンアップ -
gem 'rails'
のマイナー&パッチバージョンアップ -
gem 'rails'
のメジャーバージョンアップ-
rails app:update
の実行 - 手動での動作確認と自動テストの実行
- 警告の対応
-
- Rubyのバージョンアップ
-
gem 'rails'
のメジャーバージョンアップ - Staging環境の構築と動作確認
- Capistranoでのリリース対応
- 本番リリース
重要な箇所のテストの記述
4年ほど運用していましたが、テストコードがない状態(本当に何もない)でした。この状態でアップデートするのは、デグレードなどの検知や手動テストの工数などの観点から危険なので、まずはテストコードを記述することから始めました。(当たり前と言ったら当たり前)
しかし、工数的にもすべてのテストを網羅するというのは厳しいので、プロダクトの中でここだけは確実にエラーを出してはいけないという重要な箇所のテストコードから重点的に記述していきました。
例えば、サインアップ画面やサインイン画面での結合テスト(featureテスト)など。
エラーが起きてしまった場合に、サービスとして成り立たなくなる、プロダクトとして信頼がなくなってしまうところに関してはテストコードの網羅しました。
各Controllerの最小限のテストの記述(各画面の最低限のテスト)
GETで画面を表示するという単純なアクションでも、正確なリクエストを投げたら200, 302などの正常系のステータスが返ってくるかの最低限のテストを記述しました。
各Modelの最小限のテストの記述
各Modelごとに必須プロパティを設定している際に、DBに保存ができるのかの最低限のテストを記述しました。
gem 'rails'
以外のgemのバージョンアップ
2つの段階を踏んでgem 'rails'
以外のgemのバージョンアップをしました。
- 本番環境に影響を及ぼしにくい
development
,test
のgemのupdate - 本番環境で利用している
production
のgemのupdate
以下のコマンドで開発・テスト用のgemをアップデートしてテストが通るか、一通りの動作が問題ないか確認しました。
この時、バージョン指定しているgemがあった場合には、特別な理由がない限りはバージョン指定を削除してupdateを行いました。
# 変更前
gem 'faker', '2.0'
# 変更後
gem 'faker' #バージョン指定を外した
bundle update -g development -g test
次に、defaultの(testとdevelopmentなどに入っていない)gemのbundle update
を1つ1つ行いました。
1つずつだったため時間がかかりましたが、その都度自動テストを走らせることですぐにエラーを検知してエラー原因の特定範囲を絞ることができました。
すべてのgemを一度にbundle update
すると、何のgemのupdateによってエラーが起きているのかの問題特定に時間がかかると思い、1つ1つ行うことにしました。エラー解消にかかる時間が非常に短くなったので精神的によかったと思います。
gem 'rails'
のマイナー&パッチバージョンアップ
まずは、現時点のRailsバージョンの最新のパッチバージョンにアップしました。
Railsのバージョン番号は「メジャー番号.マイナー番号.パッチ番号」ということを表しているので、Rails4.2.6であれば、Rails4.2系で最新のパッチバージョンである4.2.11にして動作検証や自動テストを走らせるということです。
4.2.6を4.2.11に変更して自動テスト、手動テストを行い、特に問題はありませんでした。
いよいよメジャーバージョンアップです。
メジャーバージョンアップ
GemfileのRailsのバージョンをアップしてbundle update
をしました。
Railsガイドには、「メジャーバージョンアップをする際には、次のマイナーバージョンの最新パッチに移行する。」と記載があったので、5.0.0ではなく5.0.7.2にしてbundle update
を行いました。
gem 'rails', '5.0.7.2'
しかし、ここで以下のようなエラーがでてきました。
Bundler could not find compatible versions for gem "actionmailer":
In Gemfile:
exception_notification was resolved to 4.4.3, which depends on
actionmailer (< 7, >= 4.0)
premailer-rails was resolved to 1.11.1, which depends on
actionmailer (>= 3)
rails (= 5.0.7.2) was resolved to 5.0.7.2, which depends on
actionmailer (= 5.0.7.2)
Bundler could not find compatible versions for gem "actionpack":
In Gemfile:
gon (= 6.3.2) was resolved to 6.3.2, which depends on
actionpack (>= 3.0.20)
rails (= 5.0.7.2) was resolved to 5.0.7.2, which depends on
actionpack (= 5.0.7.2)
rspec-rails was resolved to 4.1.2, which depends on
actionpack (>= 4.2)
....略
gemのバージョン依存が解決できないとのことなので、粛々と解決して次に進みます。
多くのエラーが出てきましたが、1つのgemの依存関係を解消するだけですべてのエラーが消えました。(そういうこともあるんですね。)
rails app:updateの実行
bundle update
が無事に終わったら、railsのアップデートコマンドを実行します。
rails app:update
このコマンドを実行すると、諸々のファイルが作成されますが、既存のファイルがある場合にはそれを上書きするかどうかを聞かれます。差分をきちんと確認して実行するかどうか決めましょう。
特にroutes.rbに関しては上書きしない方がいいと思いますので、何も考えずに全部Yにして悲惨なことにならないようにしましょう。
(Gitで管理していれば復元できるので、特に大きな事故には繋がらないと思いますが念のため。)
手動での動作確認と自動テストの実行
rails app:update
が終わったら、railsサーバーを起動して手動で挙動と自動テストでの確認を行いました。
手動確認では特に問題ありませんでしたが、自動テストを実行したところテストでエラーが発生しました。
調べてみると、Rspec関連の gem 'rails-controller-testing'
を導入していないため、テストでエラーが発生していました。
そのため、gem 'rails-controller-testing'
を導入して対応しました。(Rails5からcontroller spec
よりも、request spect
を推奨するとのことでしたが、どうしてもrequest testを実行できない箇所があったので今回は導入しました。)
警告の対応
Rail4からRails5になるとエラーは起きないものの、サーバーのログで警告ができてたので対応しました。
どのような警告に対応したのかは、下の箇条書きのところに含めて記述しておきました。
railsのマイナーアップデート ~続~
Rails5.0.7のメジャーバージョンアップ対応が終わったので、Rails5.1.7、Rails5.2.5と順々にマイナーアップデートしました。
先ほどと同様、手動での動作確認、自動テストの実行、警告の対応などを行っていきました。
Rails4.2→Rails5.2へのバージョンアップで主に対応したことは以下の通りです。
(代表的なものだけを取り上げています。他にも色々ありましたが割愛。)
- after_filterから、after_actionに置き換え
- blongs_toのoptional: trueの対応
- Webpackerの導入
- secrets.ymlからcredentials.yml.encへの切り替え
- Rspecでのparamsの渡し方での警告の対応
- paperclipがサポート終了したのでActiveStorageへの移行
Rubyのバージョンアップ
Rubyのバージョンアップを行いました。
Ruby2.3→2.4→2.5→2.6→2.7と順々にバージョンアップしました。
Ruby2.7にして一通りの動作確認をした後にbundle update
を行い、Gemのバージョンを最新のものにしていきました。
その後、先ほどと同様にしてRails6.0にして、動作確認、テスト、警告の対応を行いました。
staging環境での動作確認
バージョンアップの対応が終わったので、本番環境と同じインフラ構成、サーバースペックでstaging環境を用意しリリースを行いました。
この時、Rails4.2で動いているstaging環境は残しておきました。
これにより、新しいバージョンであるRails6.0のテストをしている際に「こんな動作したっけ?」という時に、すぐに過去のバージョンのサーバーを触ることができ正常な動きかどうか確認できるからです。
実際に稼働している本番環境で以前のバージョンの動作確認を行うのもよさそうですが、それだと本番DBにテストデータが大量にできてしまうので望ましくないと判断しました。
Capistranoでのリリース対応
Rails6.0になりアプリケーションサーバーをUnicornからPumaになったため、Capistranoの設定を変更しました。
対応する内容は諸々ありましたが割愛します。
本番リリース
ここまできたらついにリリースです。
当初はRails4.2が動いているサーバーにそのままデプロイしようとしましたが、バージョンアップしたgemで使用するミドルウェアがRails4.2で動いているサーバーのものと動作するバージョンが違っていたため、そのままデプロイするだけでは動かないことがわかってました。
ミドルウェアをアップデートする対応は面倒なため、新しいサーバーを用意し最新のバージョンのミドルウェアをインストールして稼働させるようにしました。
新しいサーバーのリリースの際には、ロードバランサーを使っていたため、事前にRails6.0のアプリケーションをデプロイして動いている状態のサーバーを新しいターゲットにするという形にしてリリースしました。
あとがき
エラーなく無事にリリースまで行うことができました 🎉
一人で対応したので上記の手順がベストプラクティスか怪しいですが、本番環境で1つのエラーもなく動作したので成功といってよいでしょう。
参考記事
Railsのバージョンアップの対応の際に参考にした記事(公式のガイド以外で参考になったもの)
gemの依存関係の対応の際に参考にした記事
assets関連で参考にした記事