Monzoが2,800個のマイクロサービスでマイグレーションを進める方法
(monzo.com)- MSAで2,800以上のサービスを運用し、多くの価値を得ている
- しかし、このようなアーキテクチャには難しさもある。その1つが、すべてのサービスに対するライブラリ変更を実施すること
- 一般的には、最新ライブラリ、一貫したライブラリバージョン、低いアップグレード工数のうち2つしか選べない
集中型マイグレーション戦略
- Monzoでは、集中的に主導するマイグレーションアプローチによって、上記3つの特性を高いレベルで実現できると考えている
- マイグレーションの責任をサービスオーナーに委ねるのではなく、単一のチームが主導することを好んでいる
- これにより、高い調整オーバーヘッド(遅いマイグレーションにつながる)や、プロジェクトが途中で止まるリスク(不整合につながる)を回避できる
- 単一チームが妥当な時間内にマイグレーションを完了できるようにするため、次のような点に大きく依存している:
- 中核となる技術選定(例: 高いレベルの一貫性、monorepoの利用)
- 大規模な自動化の活用(例: 大量サービスデプロイツール、自動ロールバックチェック)
OpenTracing SDKからOpenTelemetry SDKへのマイグレーション
- 最近、OpenTracing SDKからOpenTelemetry SDKへ移行するプロジェクトを通じて、Monzoの戦略を実践した
- すべてのサービスはJaegerにトレースデータをエクスポートしている。以前はOpenTracingおよびJaeger Go SDKを通じてこれを行っていた
- これらのライブラリは現在では非推奨となっており、コミュニティは代わりにOpenTelemetryへ統合された
- トレーシングシステム改善の基盤を整えるため、まず非推奨ライブラリをOpenTelemetry SDKに置き換えたかった
マイグレーション原則
- サービスオーナーにとって透過的な集中型マイグレーション。調整オーバーヘッドを最小化し、マイグレーション中断のリスクを減らすため、単一チームが集中して実行できる戦略を好む
- ダウンタイムなし。ほとんどのマイグレーションは銀行の中核機能に重要なサービスを扱うため、ダウンタイムは許容できない
- 段階的なロールフォワードと迅速なロールバック。大規模変更では、問題発生時の影響範囲を減らすため段階的にロールフォワードできる必要があるが、必要な場合はすばやくすべてをロールバックできなければならない
- 自動化に対する80/20ルール。大規模マイグレーションでは、通常は共通テンプレートに当てはまる変更の割合が高い。これらの変更は容易に自動化できる。一方で、より特殊なユースケースでは自動化のリターンは逓減するため、ケースごとに対応するほうが効率的である。悪いサプライズを避け、進捗を追いやすくするためには、必要な変更を事前にこの2つのカテゴリに分類するのがよい
マイグレーション戦略
- Monzoでは、体系的に用いている一連のマイグレーション手順がある
1. 計画と調整
- マイグレーションには相応のリスクが伴う。大規模なサービスに影響するだけでなく、マイグレーションを実行するチームが、対象サービスについて十分な文脈を持っていない場合もある
- サービスの一貫性を志向しているが、例外は常に存在し、そのようなサプライズはできるだけ早く捕捉したい
- そのため、計画プロセスが透明で、すべてのエンジニアが貢献する機会を持つことが非常に重要である
- このために2つのプロセスがある:
- 提案: Monzoではこれを数多く書いている。実質的にあらゆるものが最終的には、社内の誰もが意見を述べられる単一のSlackチャンネルで共有される
- アーキテクチャレビュー: より大きな変更では、論争になりやすい領域やリスクの高い領域について、さらに深く掘り下げる同期型のアーキテクチャレビュー会議を行う。目的は承認やサインオフを得ることではなく、設計の状態を意味ある形で前進させ、プロジェクトを加速することにある
2. 旧ライブラリのラップ
-
新しいライブラリをインストールしてサービスコードを更新し、それを呼び出すのではなく、まず旧ライブラリをラップすることにした
- 基盤ライブラリへの呼び出しを横取りし、動的設定に基づいてどの実装を使うかを決定できる。これにより、すべてのサービスを再デプロイせずに、容易にロールフォワードおよびロールバックできる
- 新しいライブラリでは型や関数に大きな違いがあった。すべての呼び出し箇所を更新するには大きな労力が必要で、場合によっては新APIの利点がわずかだった。旧ライブラリをラップすることで、そのようなケースでは旧ライブラリに近いインターフェースを維持し、呼び出し箇所を更新しやすくできる
-
ライブラリをラップすることによる他の利点:
- 独自のテレメトリライブラリで計測できる
- より意図の明確なインターフェースを提供できる
3. 呼び出し箇所の更新
-
このライブラリの利用は共通パターンに当てはまっていた:
- コードベース全体で何度も参照される少数の関数/型があった
- その一方で、ごく数か所でしか参照されない関数/型のロングテールがあった
-
それぞれを異なる方法で扱った:
- 多数の箇所で参照される少数の関数/型については、可能な限り自動化した。このライブラリでは主に
goplsとgorenameに依存して、自動リファクタリングを行った - ごく数か所でしか参照されない関数/型のロングテールに対しては、手動でケースごとに対応した。場合によっては手作業でマイグレーションした。別の場合には、より既存のAPIを使って同じことができると分かり、それに切り替えた。これにより、そのケースを特別扱いする必要がなくなり、ラッパーライブラリのAPIを小さく意図の明確なものに保てるという副次的な利点もあった
- 多数の箇所で参照される少数の関数/型については、可能な限り自動化した。このライブラリでは主に
-
旧ライブラリをラップすることに加え、旧ライブラリへの新たな依存が生まれるのを防いだ。これはsemgrepを使ってCIチェックを追加することで実現した
4. 新ライブラリのラップ
- 旧ライブラリをラップした後は、ラッパーライブラリの背後に新ライブラリを追加し始められる
- 当初、新しい実装は設定によって無効化されていた。これにより、動作変更は想定されず、masterブランチへの変更を引き続き段階的にマージできた
5. 大量サービスデプロイ
- 新しい実装を有効化し始める前に、稼働中のすべてのサービスが新しい実装をサポートできることを確認する必要がある
- 他の種類のライブラリ変更では、新機能を含むサービスのサブセットだけを一度にデプロイできる場合がある。しかしトレーシングライブラリでは、あるサービスが新ライブラリを使うように移行された場合、(過渡的に)そのサービスが呼び出し得るすべてのサービスも新機能をサポートしている必要がある
- 多数のサービスデプロイを管理するため、非同期バッチジョブとしてすべてのサービスにライブラリ変更をプッシュできる大量デプロイツールを構築した
- 潜在的な誤デプロイの影響を緩和するために:
- 自動ロールバックチェックを使用
- 重要度の低いサービスから先にデプロイ。すべてのサービスに「tier」タグを付けており、大量デプロイツールはこれを使って最もリスクの低いデプロイを優先する
6. 設定によるロールアウト制御
- 大量デプロイツールの問題は、比較的遅いこと。本当に避けたいのは、全サービスをデプロイした後で新ライブラリに問題があると分かり、すばやくロールバックできない状況である
- そこで、新しい実装を有効化した状態でデプロイするのではなく、設定システムを通じて新しい実装を有効化できる機能をデプロイした
- 通常のデプロイと比べて設定システムを使う利点は、速いことにある。すべてのサービスは60秒ごとに設定を再読み込みするため、必要ならすばやくロールバックできる
- また、新しい実装がいつ使われるかについて、はるかに細かな制御が可能になる。たとえば特定のユーザー集合や、リクエストのランダムな割合に対してのみ有効化できる
- このケースでは、チームが所有するAPIエンドポイントに対してのみロールアウトすることを選び、徐々に増やしていく確率に従って有効化した
7. クリーンアップ
- 新しい実装へ完全に切り替えたら、ラッパーライブラリから旧実装を削除するという満足感のある作業を行う
マイグレーションのスーパーパワー
- この種の集中型マイグレーションは、Monzoが下した基本的な技術選択と、継続的に投資しているツールのおかげで可能になっている
- 一貫した技術: すべてのサービスはGoで書かれ、旧ライブラリの同じバージョンを使っていた。これにより、変更の自動化がはるかに容易になる。たとえば、(言語ごとにではなく)単一のリファクタリングツールだけで済む
- Monorepo: すべてのサービスコードが単一のmonorepoにあるため、大規模リファクタリングを単一コミットで行いやすい。また、CIチェックで特定ライブラリの使用をグローバルに適用できるため、一貫性維持にも役立つ
- 大量デプロイ: デプロイ可能なコンポーネントが多い場合、ライブラリ変更をプッシュするための自動化されたデプロイプロセスが必要になる
- 軽量で柔軟な設定サービス: デプロイプロセスは安全だが遅い(1回のデプロイに数分)。大規模なサービス群で新機能をすばやく即時に有効化/無効化するには、より軽量で柔軟なプロセスが必要である
結論
- 過去にはマイグレーションを分散させようとしたが、それは必然的に未完了のマイグレーションと多大な調整コストにつながった
- これが、Monzoが集中型マイグレーションを強く好む理由である。1つのチームが比較的大きなコストを負担する必要はあるが、全体としてはより少ない労力で済み、一貫性を維持できる可能性が大きく高まる
- このアプローチは好循環を生む:
- マイグレーションを実行するチームには、それを自動化するためのツールへ投資する強い動機がある
- また、技術的一貫性も維持される(ツール構築が容易になる)
- ただし、自動化の度合いについては依然として実務的であり、80/20ルールを適用している
- Monzoが継続的に投資しているツールに加え、このアプローチは開始時点で下したいくつかの中核的な技術選択があって初めて可能になった
- 主として、意図の明確な限定的な技術スタックを使っていることによる
まだコメントはありません。