こんにちは、crowdworks.jpのRubyバージョンアップを担当した高橋です。
クラウドワークスは、RubyKaigi Takeout 2021のPlatinum Sponsorとして参加しており、crowdworks.jpのメインプログラミング言語であるRubyを応援しています。
今回の記事では、crowdworks.jpの開発から10年目を迎え、そこそこ巨大なモノリシックアーキテクチャで構成されたRailsアプリケーションのRuby2.6から2.7へのバージョンアップ、およびRuby2.7から3.0へのバージョンアップ事例を紹介します。
Rubyバージョンアップの経緯
アプリケーションを長く運用していると各種ソフトウェアのバージョンが古くなります。定期的に変更差分の少ない段階でバージョンアップを繰り返さないとバージョンアップ時のコストが大きくなり、かつ変更対応に時間がかかるため、脆弱性の問題を抱えるリスクがあります。
クラウドワークスでは、crowdworks.jpのコアなRailsアプリケーションをはじめ、複数のサブシステムで利用しているソフトウェアのバージョン確認を定期的に行い、EOLを迎える前に対策を実施しています。
私が入社した2021年6月の段階でcrowdworks.jpはRuby2.6で運用されていました。Ruby2.6のEOLは2022年03月31日であるため、バージョンアップまでの期間には少し余裕があります。しかし、期限ギリギリに対応を行うと予期せぬ事象によって対応が間に合わなくなる恐れがありますので、Rubyのバージョンアップ対応を少しづつ進めることにしました。
Ruby2.6から2.7へのバージョンアップ
Ruby2.7では、Ruby3.0へのメジャーバージョンアップに向けて後方互換性が確保されていない仕様の変更点がいくつかあり、Ruby3.0で変更される機能は、Ruby2.7で対象の処理に非推奨警告が出力される仕組みが提供されています。この非推奨警告が出力される処理を修正しておかないとRuby3.0にバージョンアップした際にはエラーになってしまうため、予め修正を行なっておくことが望ましいです。
調査内容
crowdworks.jpのRailsアプリケーションはDockerコンテナで運用されています。そのため、DockerイメージのRubyを変更するだけで新しいバージョンのRubyに切り替えて動作確認をすることができます。また、Ruby 2.7.2以降では、非推奨機能に対する警告出力がデフォルトでOFFになっていますので、非推奨の警告を表示する設定をDockerfileに追加して非推奨の対象箇所を探しました。
以下は、Dockerコンテナ化したときの話です。 engineer.crowdworks.jp
対応内容
Ruby2.7のDockerイメージでCIを実行した結果、テストが失敗したり、多くの警告が出力されました。特に影響箇所が大きかったのは、「キーワード引数を通常の引数から分離」です。キーワード引数とpositionalな引数(ふつうの引数)の間の自動変換が非推奨となったことで、該当の処理を実行すると以下のメッセージが表示されます。
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
「キーワード引数を通常の引数から分離」についての詳細は、以下をご確認ください。 www.ruby-lang.org
「キーワード引数を通常の引数から分離」の影響で修正したファイル数は100を超え、1ファイルあたりに複数箇所の修正を行なっていることもあり、対応に時間がかかりました。また、本体のコード以外にもcrowdworks.jpで利用しているgemからの警告もあり、gemのバージョンアップが必要でした。
さらには、メンテナンスされなくなったgemを利用している場合もあります。過去にforkしてメンテナンスしているgemを修正したり、モンキーパッチを当てるなどの対応も必要になりました。
また、非推奨警告が出力されていても該当のコードが見当たらないケースもあり、非推奨警告が出ている処理を発見するために ruby-warning を利用して警告をエラーに変えることで、詳細なバックトレースを表示させて原因を特定しながら修正を行いました。
Ruby2.7から3.0へのバージョンアップ
Ruby2.7バージョンアップ後に、なぜRuby3.0までのバージョンアップ対応をしたのか。
執筆時点では、Ruby2.7のEOL日付は確定していません。Ruby2.7のままでも、しばらく運用し続けることもできます。しかし、Ruby3.0はRuby2.0.0のリリースから、およそ7年ぶりのメジャーバージョンアップであり、魅力的な機能が追加されています。エンジニアであれば、最新の機能を試してみたくなる欲求があると思います。ということで、最新のRubyを使いたいという憧れから、このまま継続してRubyのバージョンアップ対応を行うことにしました。
調査内容
Ruby2.7対応の段階で非推奨警告を出力するように設定していますので、しばらく本番運用し続けながら、非推奨警告が出力される処理を調査しました。
クラウドワークスでは、ログの検索にAmazon Athenaを利用しており、クエリライクに検索ができます。以下のようなクエリを実行して非推奨警告ログの確認を行いました。
SELECT role, log FROM rails_log WHERE datehour >= '2021/09/15' GROUP BY role, log HAVING( log LIKE '%deprecated%' OR log LIKE '%DEPRECATED%' ) LIMIT 100
以下は、ログ基盤を構築したときの話です。 engineer.crowdworks.jp
対応内容
本番運用中の非推奨警告ログを確認すると、「キーワード引数を通常の引数から分離」によるメッセージが一番多く確認できました。Ruby2.7で修正完了したと思っていましたが、まだまだ修正が必要です。 実際にRuby3.0のDockerイメージに変更してCIを実行したり、ステージング環境にデプロイしてみるとエラーで落ちる箇所が複数あり、目に見えた非推奨警告の対応だけではRuby3.0へのバージョンアップは実現出来ません。 地道にエラーの原因を調べて修正を行いながら、ようやくRuby3.0のDockerイメージで本番環境にデプロイすることが出来ました。
まとめ
Rubyに限った話ではありませんが、ソフトウェアのバージョンアップは地道な作業が多く、現行と同じ振る舞いで動いているかを確認するために時間がかかります。常に最新バージョンを追い続けるのは大変ですが、バージョンアップによって、パフォーマンスが向上したり、新機能が使える喜びがあったりと苦労してでも対応するメリットはあります。
crowdworks.jpでは、日々、Rubyで開発を行なっていますので開発体験の向上にも貢献できたと捉えています。
継続的なソフトウェアのバージョンアップ作業は大切ですので、地道に対応していきましょう。