Cells: HTTP ルーティングサービス
このドキュメントでは、Cells で使用されるルーティングサービスの設計目標とアーキテクチャについて説明します。ルーティングサービスがアーキテクチャのどこに位置するかをより理解するには、インフラストラクチャアーキテクチャ を参照してください。
ゴール
ルーティングレイヤーは、すべての Cells が(例えば gitlab.com のような)単一ドメインの下で提供される一貫したユーザーエクスペリエンスを提供することを目的としています。
ユーザーは https://gitlab.com を使用して Cell 対応の GitLab にアクセスできます。
URL アクセスに応じて、この特定の情報を提供できる正しい Cell に透過的にプロキシされます。
例えば:
https://gitlab.com/users/sign_inへのすべてのリクエストはすべての Cells にランダムに分散されます。https://gitlab.com/gitlab-org/gitlab/-/tree/masterへのすべてのリクエストは、例えば Cell 5 に常に転送されます。https://gitlab.com/my-username/my-projectへのすべてのリクエストは Cell 1 に常に転送されます。
テクノロジー。
ルーティングサービスがどのテクノロジーで書かれるかを決定します。 選択は最もパフォーマンスの高い言語と、ルーティングレイヤーのデプロイの期待される方法と場所に依存します。 サービスをマルチクラウドにする必要がある場合、CDN プロバイダーへのデプロイが必要かもしれません。 そうすると、サービスは CDN プロバイダーと互換性のあるテクノロジーで書かれる必要があります。
Cell ディスカバリー。
ルーティングサービスはすべての Cells を検出し、ヘルスを監視できる必要があります。
ユーザーが単一ドメインで多数の Cells と対話できる。
ルーティングサービスは、アクセスされるリソースとそのデータを含む Cell に基づいて、 すべてのリクエストを Cells にインテリジェントにルーティングします。
ルーターエンドポイントの分類。
ステートレスなルーティングサービスは、Cells の 1 つからエンドポイントに関する情報を フェッチしてキャッシュします。受信リクエストを正確に記述するプロトコル(そのフィンガープリント)を実装する必要があり、 そのプロトコルにより Cell の 1 つによってリクエストが分類され、その結果がキャッシュされます。 また、ネガティブキャッシュとキャッシュの無効化メカニズムを実装する必要があります。
GraphQL およびその他の曖昧なエンドポイント。
ほとんどのエンドポイントには一意の分類キーがあります: Organization(直接または間接的に(グループやプロジェクトを通じて)エンドポイントの分類に使用できます)。 一部のエンドポイントは使用方法が曖昧(分類キーをエンコードしない)か、分類キーがペイロードの深い部分に格納されています。 これらの場合、
/api/graphqlなどのエンドポイントの処理方法を決定する必要があります。小規模。
ルーティングサービスは設定とルール駆動であり、ビジネスロジックを実装しません。 プロジェクトは最小限に保ち、ルーティングの懸念事項のみを処理するべきです。
要件
| 要件 | 説明 | 優先度 |
|---|---|---|
| ディスカバリー | すべての Cells を検出しヘルスを監視できる必要がある | high |
| セキュリティ | 認可された Cells のみにルーティングされる | high |
| 単一ドメイン | 例えば GitLab.com | high |
| キャッシュ | パフォーマンスのためにルーティング情報をキャッシュできる | high |
| 低レイテンシー | 50 ms の増加レイテンシー | high |
| パスベース | パスに基づいてルーティング決定ができる | high |
| 複雑さ | ルーティングサービスは設定駆動で小規模であるべき | high |
| ローリング | ルーティングサービスは混在バージョンの Cells で動作する | high |
| フィーチャーフラグ | 機能をオン、オフ、% ロールアウトできる | high |
| プログレッシブロールアウト | 変更をゆっくりとロールアウトできる | medium |
| ステートレス | データベース不要、Cells がすべてのルーティング情報を提供する | medium |
| シークレットベース | シークレット(例: JWT)に基づいてルーティング決定ができる | medium |
| オブザーバビリティ | 既存のオブザーバビリティツールを使用できる | low |
| セルフマネージド | 最終的に セルフマネージド で使用できる | low |
| リージョン | 異なる リージョン へのリクエストをルーティングできる | low |
低レイテンシー
ルーティングサービスの目標レイテンシーは 50 ms 未満 であるべきです。
urgency: high リクエストを見ると、p50 でのヘッドルームはあまりありません。
追加の 50 ms を加えると、p95 レベルで SLO 内に収まることができます。
アプリケーションへの主なエントリーポイントは 3 つあります; web、api、git です。
各サービスには apdex 標準を使用したレイテンシーに基づくサービスレベルインジケーター(SLI)が割り当てられています。
これらの SLI に対応するサービスレベル目標(SLO)は、大量のリクエストに対して低レイテンシーを要求します。
これらのサービスの前にルーティングレイヤーを追加しても SLI に影響を与えないことが重要です。
ルーティングレイヤーはこれらのサービスのプロキシであり、リクエストフロー全体(エッジネットワークやロードバランサーなどのコンポーネントを含む)に対する包括的な SLI 監視システムがないため、web、git、api の SLI をターゲットとして使用します。
使用する主な SLI は rails リクエスト です。
リクエストの緊急度 に応じた複数の satisfied ターゲット(apdex)があります:
| 緊急度 | 時間(ms) |
|---|---|
:high | 250 ms |
:medium | 500 ms |
:default | 1000 ms |
:low | 5000 ms |
分析
ヘッドルームの計算方法は以下の通りです:
web:
| 目標時間 | パーセンタイル | ヘッドルーム |
|---|---|---|
| 5000 ms | p99 | 4000 ms |
| 5000 ms | p95 | 4500 ms |
| 5000 ms | p90 | 4600 ms |
| 5000 ms | p50 | 4900 ms |
| 1000 ms | p99 | 500 ms |
| 1000 ms | p95 | 740 ms |
| 1000 ms | p90 | 840 ms |
| 1000 ms | p50 | 900 ms |
| 500 ms | p99 | 0 ms |
| 500 ms | p95 | 60 ms |
| 500 ms | p90 | 100 ms |
| 500 ms | p50 | 400 ms |
| 250 ms | p99 | 140 ms |
| 250 ms | p95 | 170 ms |
| 250 ms | p90 | 180 ms |
| 250 ms | p50 | 200 ms |
分析は https://gitlab.com/gitlab-org/gitlab/-/issues/432934#note_1667993089 で行われました
api:
| 目標時間 | パーセンタイル | ヘッドルーム |
|---|---|---|
| 5000 ms | p99 | 3500 ms |
| 5000 ms | p95 | 4300 ms |
| 5000 ms | p90 | 4600 ms |
| 5000 ms | p50 | 4900 ms |
| 1000 ms | p99 | 440 ms |
| 1000 ms | p95 | 750 ms |
| 1000 ms | p90 | 830 ms |
| 1000 ms | p50 | 950 ms |
| 500 ms | p99 | 450 ms |
| 500 ms | p95 | 480 ms |
| 500 ms | p90 | 490 ms |
| 500 ms | p50 | 490 ms |
| 250 ms | p99 | 90 ms |
| 250 ms | p95 | 170 ms |
| 250 ms | p90 | 210 ms |
| 250 ms | p50 | 230 ms |
分析は https://gitlab.com/gitlab-org/gitlab/-/issues/432934#note_1669995479 で行われました
git:
| 目標時間 | パーセンタイル | ヘッドルーム |
|---|---|---|
| 5000 ms | p99 | 3760 ms |
| 5000 ms | p95 | 4280 ms |
| 5000 ms | p90 | 4430 ms |
| 5000 ms | p50 | 4900 ms |
| 1000 ms | p99 | 500 ms |
| 1000 ms | p95 | 750 ms |
| 1000 ms | p90 | 800 ms |
| 1000 ms | p50 | 900 ms |
| 500 ms | p99 | 280 ms |
| 500 ms | p95 | 370 ms |
| 500 ms | p90 | 400 ms |
| 500 ms | p50 | 430 ms |
| 250 ms | p99 | 200 ms |
| 250 ms | p95 | 230 ms |
| 250 ms | p90 | 240 ms |
| 250 ms | p50 | 240 ms |
分析は https://gitlab.com/gitlab-org/gitlab/-/issues/432934#note_1671385680 で行われました
非ゴール
未定義です。
提案
ルーティングサービスは以下の設計ガイドラインを実装します:
- シンプル:
- ルーティングサービスはリクエストをバッファリングしません。
- ルーティングサービスは受信リクエストに基づいて単一の Cell にのみプロキシできます。
- ステートレス:
- ルーティングサービスは永続ストレージを持ちません。
- ルーティングサービスはマルチレベルキャッシュを使用します: インメモリ、外部共有キャッシュ。
- ゼロトラスト:
- ルーティングサービスはプロキシされる各リクエストに署名します。
- 信頼は JWT トークンまたは相互認証スキームを使用して確立されます。
- Cells はゼロトラストモデルに従う限り、パブリックインターネット上で利用可能になれます。
- 設定ベース:
- ルーティングサービスは静的な Cells のリストで設定されます。
- ルーティングサービスの設定はサービスデプロイの一部として適用されます。
- ルールベース:
- ルーティングルールは設定可能です。
- 非依存:
- ルーティングサービスは Organization のような高レベルの概念を認識していません。
- 分類はルールで提供される仕様に基づいて、分類キーを見つけるために行われます。
- 分類キーの結果はキャッシュされます。
- キャッシュされた単一の分類キーは、多くの類似リクエストの処理に使用されます。
以下のダイアグラムは、ユーザーリクエストが DNS を通じて Cloudflare Worker としてデプロイされたルーティングサービスにルーティングされ、ルーターがリクエストを送信する Cell を選択する方法を示しています。
graph TD;
user((User));
router[Routing Service];
cell_us0{Cell US0};
cell_us1{Cell US1};
cell_eu0{Cell EU0};
cell_eu1{Cell EU1};
user-->router;
router-->cell_eu0;
router-->cell_eu1;
router-->cell_us0;
router-->cell_us1;
subgraph Europe
cell_eu0;
cell_eu1;
end
subgraph United States
cell_us0;
cell_us1;
endルーティングルール
- ルーターはリクエストをルーティングするためにルールの階層を適用します。
- 受信リクエストは最初にルーター内で設定されたパスパターンに照合されます。クレームはパスから抽出され(例:
/:NAMESPACE_PATH)、トポロジーサービス を通じてターゲット Cell を決定するために使用されます。 - パス内のクレームが見つからない場合、またはパスにクレームが含まれていない場合(例:
/api/graphqlやcable)、ルーターはトークンベースのルーティングにフォールバックします。
- 受信リクエストは最初にルーター内で設定されたパスパターンに照合されます。クレームはパスから抽出され(例:
- ルーティングルールは、リクエストをデコードし、分類キーを見つけ、ルーティング決定を行う方法を記述します。
- ルーティングルールは静的であり、HTTP Router のデプロイの一部として事前に定義されます。
- パスルーティングルール以外のルーティングルールは、順番に操作のシーケンスを記述する JSON ドキュメントとして定義されます。
- ルーティングルールはより高速な実行スキームを提供するためにアプリケーションコードにコンパイルされる場合があります。
- 各ルーティングルールは
cookies、headers、method、actionによって記述されます。 actionはclassifyにすることができ、動的な分類を実行するためにトポロジーサービスを使用することを示します。actionはproxyにすることができ、proxyアドレスが指定されていない限り、HTTP Router 設定に格納された固定ホストへのパススルーを実行することを示します。 通常、これはクラスター内のCell 1になります。
ルーティングルールの JSON 構造はすべてのマッチャーを記述します:
{
"rules": [
{
"cookies": {
"<cookie_name>": {
"match_regex": "<regex_match>"
},
"<cookie_name2>": {
"match_regex": "<regex_match>"
}
},
"headers": {
"<header_name>": {
"match_regex": "<regex_match>"
},
"<header_name2>": {
"match_regex": "<regex_match>"
},
},
"method": ["<list_of_accepted_methods>"],
"action": "classify",
"classify": {
"type": "session_prefix|...",
"value": "string_build_from_regex_matchers"
},
"action": "proxy",
"proxy": {
"address": "cell1.gitlab.com"
}
}
]
}
セッションクッキーとシークレットに基づいてルーティング決定を行うルーティングルールの例:
{
"rules": [
{
"cookies": {
"_gitlab_session": {
"match_regex": "^(?<cell_name>cell.*:)" // accept `_gitlab_session` that are prefixed with `cell1:`
}
},
"action": "classify",
"classify": {
"type": "session_prefix",
"value": "${cell_name}"
}
},
{
"headers": {
"GITLAB_TOKEN": {
"match_regex": "^(?<cell_name>cell.*:)" // accept `_gitlab_session` that are prefixed with `cell1:`
}
},
"action": "classify",
"classify": {
"type": "token_prefix",
"value": "${cell_name}"
}
}
]
}
分類
分類は トポロジーサービスの分類サービス によって実装されます。
- 分類エンドポイントはアクセスを保護するために REST(mTLS 付き)を使用します。
- 分類エンドポイントは情報をルーティングする Cell 名のみを返します。
- 分類は類似リクエストのキャッシュを汚染するために他の同等の分類キーを返すことがあります。 これにより、すべての類似リクエストが毎回分類することなく迅速に処理されることを確保します。
- HTTP Router は
classify呼び出しを適切な回数リトライします。 - 特定の値の分類は返されたレスポンスに関わらずキャッシュされます(肯定的または否定的)。 見つからない分類キーに対する過剰なリクエストを防ぐために、拒否された分類がキャッシュされます。
- レスポンスにはリクエストがキャッシュされる期間を制御する
Cache-*ヘッダーが含まれます: Cloudflare Workers - Cache。 - キャッシュが使用される場合、エッジで特定のタイプのキャッシュを選択的にワイプするメカニズムとして
Cache-Tag:の使用が必要です。 - キャッシュはトポロジーサービスによって制御されますが、HTTP Router は一部のレスポンスをキャッシュに強制することがあります。
パスベースルーティングの例
/o/swh/dashboard/groups へのリクエストの場合:
sequenceDiagram
participant user as User
participant router as Router
participant ts as Topology Service
participant cell_2 as Cell 2
user->>router: GET /o/swh/dashboard/groups
router->>router: Router matches /o/:ORGANIZATION_PATH<br/>Extract ORGANIZATION_PATH: "swh"
router->>+ts: Classify({bucket: {type: "ORGANIZATION_PATH", value: "swh"}})
ts->>-router: Proxy(address="cell-2.gitlab.com")
router->>cell_2: /o/swh/dashboard/groups
cell_2->>user: <h1>...JSON ルール分類の例
cell-2 をエンコードした JOB-TOKEN ヘッダーを持つ /api/v4/job/allowed_agents へのリクエストの場合:
sequenceDiagram
participant client as Client
participant router as Router
participant ts as Topology Service
participant cell_2 as Cell 2
client->>router: GET /api/v4/job/allowed_agents<br/>JOB-TOKEN with cell-2 encoded
router->>router: Does not contain a claim<br/>Falls back to token based routing<br/>Extracts the cell from the header
router->>+ts: Classify({session_prefix: "cell-2"})
ts->>-router: Proxy(address="cell-2.gitlab.com")
router->>cell_2: /api/v4/job/allowed_agents
cell_2->>client: <h1>...設定
すべての設定は環境変数で提供されます:
- HTTP Router はトポロジーサービスへのアドレスのみを設定します
- トポロジーサービスへの接続には mTLS が認証/認可に使用されます。
デプロイ
HTTP ルーティングサービスを GitLab.com に完全にデプロイするには、いくつかのフェーズがあります。
- 最初のフェーズは、webservice(
gitlab.com)の前にシンプルなパススループロキシをデプロイすることです。- まず、DNS を変更せずに Worker を徐々にロールアウトするために Cloudflare Routes を利用します。
- (おそらくオプション)次のステップは、レガシー Cell の内部専用 DNS をプロビジョニングすることです(例:
cell-1.gprd.int.gitlab.com)。 その後、HTTP Router をこの新しい DNS にプロキシし、mTLSや Cloudflare Tunnel のようなソリューションでこの接続を保護します。 これを行うためには、HTTP Router にgitlab.comDNS レコードを割り当てる必要があります。おそらく カスタムドメイン を使用します。
- 2 番目のフェーズは、コンテナレジストリ(
registry.gitlab.com)の前にシンプルなパススループロキシをデプロイすることです。 これはgitlab.com用の HTTP Router の同じデプロイメントを使用します。- まず、DNS を変更せずに Worker を徐々にロールアウトするために Cloudflare Routes を利用します。
- (おそらくオプション)次のステップは、レガシー Cell の内部専用 DNS をプロビジョニングすることです(例:
cell-1-registry.gprd.int.gitlab.com)。 その後、HTTP Router をこの DNS にプロキシし、mTLSや Cloudflare Tunnel のようなソリューションでこの接続を保護します。 これを行うためには、HTTP Router にregistry.gitlab.comDNS レコードを割り当てる必要があります。おそらく カスタムドメイン を使用します。
- 3 番目のフェーズは複数の Cell を含みます。
- HTTP Router がルーティングする新しい Cell それぞれに、以下が必要です:
cell-2.gdrd.int.gitlab.comのような内部専用 DNS(HTTP Router 経由でのみアクセス可能)。- HTTP Router と Cell の間の安全な暗号化された接続。
- HTTP Router がルーティングする新しい Cell それぞれに、以下が必要です:
ルールセットのロールアウト
HTTP Router のルールセットは、Cells 環境内でどのように HTTP リクエストをルーティングするかのロジックを定義します。
これらのルールセットを変更すると、サイト全体の可用性または特定のサービスの SLO に影響を与える可能性があります。
そのため、ルールセットへの変更をロールアウトする際は極めて慎重に行う必要があります。
ユーザーへの影響を最小限に抑え、ダウンタイムなしでこれらの変更を実装するために、Cloudflare が提供する 段階的デプロイ 機能を使用します。このアプローチにより、問題のある変更の影響を総リクエストの小さなサブセットに限定できます。
使用されるルールセットは HTTP Router 設定ファイル で設定されており、GITLAB_RULES_CONFIG 環境変数が src/rules ディレクトリからの相対的なルールセットファイル名を定義します。
既存の デプロイメカニズム を使用します。ルールセットの変更の品質と期待される結果に確信を持った場合にのみ、ロールアウトのパーセンテージを徐々に増加させます。推奨されるロールアウトパーセンテージのシーケンスは: 5% → 25% → 50% → 75% → 100% です。
前提条件
- ロールアウト手順を進める前に、タイムラインを明確に定義してください。
- 変更をスケジュールしてください
- 設定 ファイルに新しい変更ロックエントリを追加してください。このエントリには
http-router変更ロックタグを使用してください。
注意: このロールアウト戦略ではタイムラインに従うことが重要です。一定の間隔で MR をマージする必要があります。そのため、ペアで作業することをお勧めします。
ロールアウト手順
- HTTP Router Deployer の CI 設定
.gitlab-ci.ymlを変更する MR を作成します。グローバル変数セクションで、変更管理の Issue へのリンクを付けてCHANGE_LOCK_OVERRIDEとOVERRIDE_LAST_PERCENTAGE環境変数の両方をtrueに設定します。 - 同じ MR で、deploy-worker.sh スクリプトの
ROLLOUT_PERCENTAGES環境変数を変更します。値を5に設定します。例:ROLLOUT_PERCENTAGES="5" - MR をマージします。
wrangler.toml内のGITLAB_RULES_CONFIG設定を新しいルールセットに更新する MR を作成してマージします。- 新しいルールセットの検証を行い、SLO に影響がないことを確認します。
ROLLOUT_PERCENTAGESを増加する前に、環境によって変化するベーキング時間を設けます。- 異常が見つからず SLO への影響がない場合は、
25、50、75、100パーセントに対してステップ 1 を繰り返します。ロールアウトサイクル全体を通じてCHANGE_LOCK_OVERRIDEとOVERRIDE_LAST_PERCENTAGEをtrueに保ちます。 - トラフィックの 100% がロールアウトされたら、deploy-worker.sh スクリプトの値を完全なシーケンス
"5 25 50 75 100"に戻す MR を開きます。例:ROLLOUT_PERCENTAGES="5 25 50 75 100"。.gitlab-ci.ymlのOVERRIDE_LAST_PERCENTAGEとCHANGE_LOCK_OVERRIDE環境変数を削除します。
代替案
リクエストのバッファリング
リクエストバッファリングを使用したステートレスルーター は、Cell が X-Gitlab-Cell-Redirect で応答して別の Cell にリクエストをリダイレクトするアプローチを説明しています:
- これはリクエスト全体(ヘッダー + ボディ)をバッファリングする必要があり、非常にメモリ集約的です。
- この提案は、Cells が異なるバージョンで実行されている混在デプロイを扱う簡単な方法を提供しません。
- この提案はデコードされた分類キーではなくリクエストに基づいているため、より多くの情報をキャッシュする必要があります。
ルート学習
ルート学習を使用したステートレスルーター は、このドキュメントのアプローチに似たアプローチを説明しています。ただし、ルートルールと分類は /api/v4/internal/cells/learn のプリフライトチェックの形式で一度に行われます:
- これにより、全体のルート学習が動的になり、Cells の可用性に依存します。
- この提案は、Cells が異なるバージョンで実行されている混在デプロイを扱う簡単な方法を提供しません。
- この提案はデコードされた分類キーではなくリクエストに基づいているため、より多くの情報をキャッシュする必要があります。
単一ドメイン
すべての Cell に対して単一ドメインを維持するために、webserver はリダイレクトを実行する際にパブリックホストとして応答する必要があります。Dedicated の BYOD 機能は、Cell がパブリックドメイン上でサービスを提供しているかのように動作できるようにすることでこの目的を果たします。
byod 設定スニペットの例。注意 - インスタンスのみが設定されており、kas やレジストリドメインは設定されていません
"byod": {
"instance": "gitlab.com",
}
ドメイン設定
- BYOD パブリックドメイン(例: gitlab.com)を使用して設定された本番 Cell
- 各 Cell は設定された
managed_domainにも応答します - Nginx ingress は両方のドメインを処理します
SSL/TLS 設定
- 設定された BYOD ドメインの証明書はすでに管理されており、インストルメンターが管理するものではありません。
managed_domainは、Cells がパブリックにルーティング可能でない場合に特にプロキシの背後でデフォルトの http ソルバーが機能しないため、cert-manager DNS ソルバーを使用して処理されます
Nginx-ingress 設定
- プライマリドメインと
managed_domainドメインの両方でリッスンします - 適切なルーティングのために
X-Forwarded-Hostヘッダーを処理します。ホストヘッダーは使用できないため、nginx はルーターによって渡されるX-Forwarded-Hostを使用します
Cell インフラストラクチャルーティング
ここで説明されている session_prefix ルールを使用します
sequenceDiagram
participant User as User (Browser)
participant HTTPRouter as HTTP Router (Cloudflare)
participant TopologyService as Topolgoy Service
box Cell
participant CellIngress as Cell Ingress (nginx-ingress)
participant CellWebservice as Webserivce Container(workhorse/puma)
end
participant LegacyCell as Legacy Celll
Note over User,CellWebservice: Cell-based routing path
User->>HTTPRouter: Cookie: _gitlab_session=cell-$ID-xxx
HTTPRouter->>+TopologyService: Query Cell ID extract from _gitlab_session
TopologyService-->>-HTTPRouter: Return managed_domain for Cell
HTTPRouter->>HTTPRouter: Set X-Forwarded-Host: gitlab.com
HTTPRouter->>CellIngress: Proxy to managed_domain
CellIngress->>CellIngress: Use Host header matching X-Forwarded-Host
CellIngress->>+CellWebservice: Proxy with Host: gitlab.com
CellWebservice-->>-CellIngress: Response
CellIngress-->>HTTPRouter: Response
HTTPRouter-->>User: Response
Note over User,LegacyCell: Default routing (no cell prefix in _gitlab_session)
User->>HTTPRouter: Cookie: _gitlab_sesion=xxxx
HTTPRouter->>LegacyCell: Proxy to GitLab.com
LegacyCell-->>HTTPRouter: Response
HTTPRouter-->>User: ResponseFAQ
- ルーティングサービスはいつ、どのようにしてルールセットをコンパイルしますか?
未定義です。
