Ruby アップグレードのガイドライン
私たちは、パフォーマンスやセキュリティのアップデート、新しいRuby APIの恩恵を受けるために、最新のRuby MRIリリースを使ってGitLabを運用するよう努めています。GitLab全体でRubyをアップグレードするときは、次のような方法で行う必要があります:
- コントリビューターにとって最も破壊的でない方法。
- GitLab SaaSの可用性を最適化します。
- GitLabのすべての部分でRubyのバージョンパリティを維持します。
Rubyのバージョンを変更する前に、このドキュメントを注意深く最後まで読み、どのような変更が必要なのかを大まかに理解してください。Rubyのアップグレードは、その前のものとは少し異なる可能性があるので、文書化された手順の順序と必要性を見極めてください。
Ruby アップグレードの範囲
Rubyをアップグレードする際にまず考慮しなければならないのは、その範囲です。一般的には、Rubyのアップデートが発生する可能性のある範囲を次のように考えます:
- GitLab Railsのメインリポジトリ。
- 補助的なRubyシステムのリポジトリ。
- これらのリポジトリ内のシステムで使用されているサードパーティライブラリ。
- これらのリポジトリ内のシステムで使用されているGitLabライブラリ。
これらのすべてに触れる必要はないかもしれません。例えば、パッチレベルのRubyのアップデートでサードパーティのgemのアップデートが必要になることはまずありません。
パッチ、マイナー、メジャーアップグレード
スコープを評価するとき、Rubyのバージョンレベルは重要です。例えば、GitLabをRuby 2.xから3.xにアップグレードするのは、Ruby 2.7.2から2.7.4にアップグレードするよりも難しくリスクがあります。アップグレードを準備するときにはこのことを意識し、それに応じて計画を立ててください。
将来のアップグレードの範囲を見積もるために、以下のアップグレードに必要な労力を参照してください:
影響を受ける対象およびターゲット
アップグレードの前に、すべてのオーディエンスとターゲットについて、Rubyのアップグレードによってすぐに影響を受ける順に考えてください:
-
開発者。GitLab や関連プロジェクトには、社内外に多くの貢献者がいます。
.ruby-version
などのファイルを変更すると、そのファイルを解釈するツールを使っている人全員に影響が及びます。開発者は、マージされた変更を含むリポジトリからプルするとすぐに影響を受けます。 -
GitLab CI/CD。私たちは、コードのインテグレーションとテストのためにCI/CDに大きく依存しています。CI/CD ジョブは
.ruby-version
のようなファイルを解釈しません。その代わりに、.gitlab-ci.yml
で定義されているDockerコンテナにインストールされたRubyを使用します。これらのジョブで使用されるコンテナイメージはgitlab-build-images
リポジトリでメンテナーされています。イメージにアップデートをマージすると、CI/CDジョブはイメージがビルドされるとすぐに影響を受けます。 -
GitLab SaaS。GitLab.com は、クラウドネイティブ GitLab(CNG) の Docker イメージを使用するカスタマイズされた Helm チャートからデプロイされます。CI/CDのように、
.ruby-version
、この環境では意味がありません。代わりに、Rubyをアップグレードするために、それらのDockerイメージにパッチを当てる必要があります。GitLab SaaSは次のデプロイで影響を受けます。 - セルフマネージドGitLab。 Omnibus経由でGitLabをインストールするお客様は、上記のいずれも使用しません。代わりに、RubyのバージョンはOmnibusのRubyソフトウェアバンドルによって定義されます。セルフマネージメントのお客様は、この変更を含むリリースにアップグレードするとすぐに影響を受けます。
Ruby のアップグレード方法
Rubyのアップグレードは、すべてのステップのタイミングを合わせることが重要です。一般的なガイドラインとして、以下のことを考慮してください:
- 本番環境の動作が変わる可能性が低い小規模なアップグレードでは、リポジトリと本番環境の間のバージョンギャップを最小に保つことを目指しましょう。関係者と調整し、ドリフトを避けるために、すべての変更を密接にマージします(1日か2日以内)。このシナリオでは、開発者ツールと環境のアップグレードを最初に行い、本番環境のアップグレードはその次に行うという順序が考えられます。
- より大きな変更の場合、新しいRubyで本番環境に移行するリスクは大きいです。この場合は、新しいRubyのバージョンとの非互換性がすでに修正されていることを確認してから、GitLabの本番環境のサブセットに新しいRubyをデプロイするために本番環境のエンジニアと協力するようにしましょう。このシナリオでは、本番環境のアップデートが先で、開発者ツールと環境のアップデートは後という順番になるでしょう。こうすることで、本番環境で重大なリグレッションが発生した場合のロールバックが容易になります。
いずれにせよ、私たちは過去の経験から、以下のアプローチがうまくいくことを発見しました。これらのステップの中には、並行して行われるものもあれば、上記のように順序が逆になるものもあることに注意してください。
エピック作成
この作業をエピックで追跡することは、進捗状況を把握するのに便利です。大規模なアップグレードの場合は、エピック説明にタイムラインを含め、関係者が最終的なスイッチの稼動予定時期を把握できるようにします。
個々のリポジトリへの変更は、このエピックで別のイシューに分割します。
アップグレードの意図を伝える
特に、機能の導入や廃止を伴うアップグレードの場合は、アップグレードの時期が迫っていることを早めに伝えましょう。重要な、あるいは注目すべき変更点へのリンクを提供することで、開発者は前もって変更点に慣れることができます。
GitLab チームメンバーは、関連する Slack チャンネル(最低でも#backend
と#development
)と Engineering Week In Review(EWIR)でその意図をアナウンスしてください。アップグレードのエピックへのリンクをコミュニケーションに含めてください。
CI/CD と開発環境に新しい Ruby を追加してください。
Ruby gems や GitLab Rails アプリケーションを新しい Ruby でビルドして実行するには、まず CI/CD 環境と開発者環境に新しい Ruby を追加する準備をしなければなりません。このステージでは、新しいRubyをデフォルトのRubyにするのではなく、オプションにする必要があります。これにより、一定期間新旧両方のRubyをサポートすることで、よりスムーズな移行が可能になります。
変更が必要な箇所は2箇所あります:
-
GitLab Build Imagesです。これらはRunnerやその他のDockerベースのプリプロダクション環境に使うDockerイメージです。必要な変更の種類はスコープによって異なります。
-
パッチレベルのアップデートの場合は、
RUBY_VERSION
のパッチレベルを上げるだけで十分です。同じマイナーリリースに対してビルドするすべてのプロジェクトは、新しいパッチリリースを自動的にダウンロードします。 -
メジャーアップデートやマイナーアップデートの場合は、アップグレード中に既存のイメージと並べて使用できる新しいDockerイメージのセットを作成します。重要:
/patches
ディレクトリにあるすべての Ruby パッチファイルを、アップグレード先の Ruby バージョンに一致する新しいフォルダにコピーしてください。
-
パッチレベルのアップデートの場合は、
-
GitLab Development Kit(GDK).GDKをアップデートして、開発者が選択できる追加オプションとして新しいRubyを追加します。これは通常、
.tool-versions
に追加するだけなので、asdf
ユーザーはこの恩恵を受けることができます。その他のユーザーは手動でインストールする必要があります(例)。
大規模なバージョンアップの場合は、品質エンジニアリングと協力してテスト計画を特定し、設定することを検討してください。
サードパーティgemのアップデート
パッチリリースの場合は必要ないと思われますが、マイナーリリースやメジャーリリースの場合、gemsがRubyを特定のバージョンに固定する際に、変更点やBundler依存性のイシューが壊れる可能性があります。それを知る良い方法は、gitlab-org/gitlab
でマージリクエストを作成し、何が壊れるかを確認することです。
GitLab gemsと関連システムのアップデート
私たち自身がメンテナーしているgemsやRubyアプリケーションには、.ruby-version
,.tool-versions
,.gitlab-ci.yml
ファイルのようなビルドセットアップがコンテナとして含まれているため、これは通常必要なことです。GitLab Railsアプリケーションを新しいRubyで動作させるためにこれらのリポジトリを更新する技術的な必要性は必ずしもありませんが、Rubyのバージョンをすべてのリポジトリで統一しておくことは良い習慣です。マイナーバージョンアップやメジャーバージョンアップの際には、新しいRubyを使ったCI/CDジョブをリポジトリに追加します。ビルドマトリックス定義は、これを効率的に行うことができます。
更新するリポジトリを決める
Rubyをアップグレードする際には、以下のリポジトリのアップデートを検討してください:
- Gitaly(例)
- GitLab LabKit(例)
- GitLab Exporter(例)
- GitLab Experiment(例)
- Gollum Lib(例)
- GitLab Helmチャート(例)
- GitLab Sidekiqフェッチャー(例)
- Prometheus Ruby Mmap クライアント(例)
- GitLab-mail_room(例)
これらのリポジトリのうち、メインの GitLab アプリケーションと並行して更新することが重要なものを評価するには、次のように考えます:
- Ruby のバージョン範囲。
- GitLabの全体的な機能の中で、サービスやライブラリが果たすロール。
どのリポジトリが影響を受けるかは、GitLabプロジェクトのリストを参照してください。小規模なバージョンアップの場合、本質的でないライブラリのアップデートを遅らせたり、新しいRubyのバージョンでメインのアプリケーションのテストスイートがリグレッションを検出することが確実なライブラリのアップデートを遅らせたりしてもかまいません。
GitLab アプリケーションの MR を準備します。
依存関係が更新され、新しいgemのバージョンがリリースされたので、gemや関連システムと同様に、必要な変更を加えてメインのRailsアプリケーションを更新します。その上で、インストールとアップデートの説明(例)にバージョン変更を反映させるためにドキュメントを更新します。
開発者にアップグレードする時間を与える(猶予期間)
新しい Ruby がオプションとして利用可能になり、すべてのマージリクエストが準備できたかマージされたならば、開発者が自分のマシンに新しい Ruby をインストールできる猶予期間 (最低でも 1 週間) を設けるべきです。GDK とasdf
のユーザーについては、gdk update
を使って自動的にインストールされるはずです。
この休止期間は、GitLab SaaS のアップグレードのリスクを評価する良い機会です。メジャーバージョンのアップグレードなど、リスクの高い Ruby のアップグレードについては、変更管理リクエストを通してインフラチームと調整を行うことをお勧めします。このイシューを早めに作成し、全員がスケジュールを立てて変更を準備するのに十分な時間を確保しましょう。
デフォルトRubyにする
既知のバージョン互換性のイシューが残っておらず、猶予期間が過ぎている場合は、影響を受けるすべてのリポジトリと開発者ツールを更新して、新しい Ruby をデフォルトにする必要があります。
この時点で、GitLab Composer Kit(GCK) を更新してください。これは、GitLab をdocker-compose
で実行したいユーザーのための代替開発環境です。このプロジェクトは私たちの Runner と同じ Docker イメージに依存しているので、リポジトリの変更と同じメンテナーを維持する必要があります。この変更が必要なのは、マイナーバージョンやメジャーバージョンが変更されたときだけです(例)。
上述したように、RubyのアップグレードがSaaSの可用性に与える影響が不確かな場合は、段階的なロールアウトによって本番環境でスムーズに動作することを確認するまで、このステップをスキップするのが賢明です。この場合、まず次のステップに進み、検証期間が過ぎたら新しい Ruby を新しいデフォルトに昇格させます。
CNG、Omnibus、セルフコンパイルを更新し、GitLab MRをマージします。
最後のステップは、本番環境で新しいRubyを使うことです。そのためには、Omnibusと本番環境のDockerイメージを新しいバージョンにアップデートする必要があります。Helm チャートも、独自のチャートを管理する関連システム (gitlab-exporter
など) に変更があった場合は更新する必要があるかもしれません。
本番環境で新しいRubyを使用するには、以下のプロジェクトを更新します:
変更管理リクエストを提出する場合は、インフラエンジニアとロールアウトを調整してください。大規模なアップグレードの場合は、リリースマネージャをロールアウト計画に参加させてください。
セキュリティパッチのパッチリリースとバックポートの作成
アップグレードがパッチリリースであり、重要なセキュリティフィックスを含んでいる場合、GitLabパッチリリースとしてセルフマネージドカスタマーにリリースする必要があります。どのように進めるかについては、リリースマネージャーに相談してください。
Ruby アップグレードツール
アップグレードを簡単にするツールがいくつかあります。
非推奨ツールキット
Rubyのアップグレードでよくある問題は、非推奨の警告がエラーになってしまうことです。これは、非推奨の警告をひとつひとつ解決してからアップグレードしなければならないことを意味します。新しい警告がメインのアプリケーションブランチに入るのを避けるために、DeprecationToolkitEnv
を使います。このモジュールは、spec の実行から発せられる deprecation 警告を監視し、それをテストの失敗に変えます。これにより、開発者が新しい Ruby の下で失敗するような新しいコードをチェックインすることを防ぎます。
例えば、私たちが使っているRuby gemがこのような警告を発し、私たちがそれをコントロールできない場合などです。このような場合は、今回のマージリクエストのように、警告を消すようにしましょう。
非推奨ロガー
また、Ruby と Rails の非推奨に関する警告を専用のログファイルlog/deprecation_json.log
に記録しています (GitLabのログファイルの場所についてはGitLab Developers Guide to Loggingを参照)。これは、テストによって十分にカバーされておらず、したがってDeprecationToolkitEnv
を通過してしまうようなコードがある場合の手がかりとなります。
GitLab SaaSの場合、GitLabチームメンバーはKibana (https://log.gprd.gitlab.net/goto/f7cebf1ff05038d901ba2c45925c7e01
) でこれらのログイベントを検査することができます。
推奨
アップグレードプロセスでは、以下の推奨事項を検討してください:
- できるだけ多くの変更をフロントローディングしてください。特にマイナーリリースやメジャーリリースでは、アプリケーションコードが壊れたり変更されたりする可能性があります。後方互換性のある変更はすべてメインブランチにマージし、Ruby のバージョンアップに先立って単独でリリースすべきです。こうすることで、小刻みに作業を進め、本番環境からのフィードバックを早期に得ることができます。
- より大きなアップデートのために experimental ブランチを作成します。私たちは通常、長期間のトピックブランチは避けるようにしています。しかし、フィードバックや実験の目的で、新しい Ruby を実行したときに CI/CD から定期的にフィードバックを得るために、このようなブランチを用意しておくと便利です。このMRが示しているように、どのような問題が発生するかを最初に評価するときに役立ちます。これらの実験用ブランチはマージするためのものではありません。必要な変更をすべて取り除いて、個別にマージし直したら、ブランチを閉じることができます。
- マイルストーンリリースの前に、問題を修正するための十分な時間を確保しましょう。GitLab は動きが速いです。Ruby のアップグレードには多くの MR を送信してレビューする必要があるので、リリース日の少なくとも一週間前にはすべての変更をマージしておきましょう。そうすることで、何か問題が発生したときに対応できる時間が増えます。疑問がある場合は、アップグレードを翌月に延期したほうがよいでしょう。