GitLab Runner アドミッションコントローラー
| Status | Authors | Coach | DRIs | Owning Stage | Created |
|---|---|---|---|---|---|
| proposed | ajwalker
johnwparent | ayufan | DarrenEastman
engineering-manager | devops <stage> | 2023-03-07 |
GitLab の アドミッションコントローラー(Kubernetes のアドミッションコントローラーコンセプトに着想を得た)は、ジョブが永続化されるか実行のためにビルドキューに追加される前にジョブをインターセプトする提案された技術的ソリューションです。
アドミッションコントローラーは GitLab インスタンスに登録され、作成するジョブを含むペイロードを受け取ることができます。アドミッションコントローラーは_ミューテーティング_、バリデーティング、またはその両方である可能性があります。
- _ミューテーティング_の場合、可変のジョブ情報を変更して GitLab インスタンスに送り返すことができます。ジョブは組織のポリシー、セキュリティ要件に準拠するように変更されたり、例えばタグリストを変更して特定の Runner にルーティングされたりする場合があります。
- _バリデーティング_の場合、ジョブは実行を拒否される可能性があります。
動機
職務分離、組織的ポリシー、またはセキュリティ要件に準拠するために、金融サービス、米国連邦政府市場セグメント、またはその他の規制の厳しい業種の顧客は、特定の CI ジョブ環境に関連付けられた Runner を認可されたユーザーのみが使用できることを確保する必要があります。
このコンテキストでは、環境という用語を使用することは、GitLab CI の環境とデプロイのドキュメントで使用される環境の定義と同等ではありません。SLSA ガイドの定義を使用して、環境はジョブが実行される「マシン、コンテナ、VM、または同様のもの」です。
追加の要件はLawrence Livermore National Laboratoryの Remote Computing Enablement(RCE)グループから来ています。この例では、ユーザーは CI ジョブを実行するためにターゲットの Runner CI ビルド環境にユーザー ID を持っている必要があります。ユーザーベース全体での管理を簡素化するために、RCE は Runner を GitLab ユーザーエンティティに関連付けることができる必要があります。
現在の GitLab CI ジョブ処理メカニズム
さらに進む前に、GitLab CI と GitLab Runner の現在のジョブ処理メカニズムを整理することが役立ちます。
- まず、GitLab インスタンスに関連付けられた Runner が GitLab インスタンス API に継続的にクエリを送信し、実行できる新しいジョブがあるかどうかを確認します。
- GitLab 上のプロジェクトリポジトリへのすべてのプッシュで
.gitlab-ci.ymlファイルが存在する場合、GitLab インスタンスに存在する CI サービスがイベントをキャッチして新しい CI ジョブをトリガーします。 - CI ジョブは、Runner がジョブを要求するまでキューで保留状態に入ります。
- Runner がジョブの API にリクエストを送信すると、データベースにクエリが送信されてジョブパラメーターが Runner のものと一致するかどうかが確認されます。言い換えれば、Runner が GitLab インスタンスにジョブをポーリングすると、指定された基準と一致する場合にジョブが割り当てられます。
- ジョブが問題の Runner と一致する場合、GitLab インスタンスはジョブを Runner に接続してジョブの状態を実行中に変更します。言い換えれば、GitLab は
jobオブジェクトをRunnerオブジェクトに接続します。 - Runner はタグなしのジョブを実行するように設定できます。タグは今日顧客が特定の種類のジョブを実行する Runner を制御するために使用される主なメカニズムです。
- したがって、Runner はインスタンス、グループ、プロジェクトにスコープされていますが、ユーザーまたはグループ識別子に基づいて Runner へのアクセスを拒否するために拡張できる追加のアクセス制御メカニズムは今日存在しません。
現在の CI ジョブキューロジックは以下の通りです。注意 - コードでは非常に古い build 命名構造をまだ使用していますが、製品とドキュメントでは build から job に移行しました。
jobs =
if runner.instance_type?
jobs_for_shared_runner
elsif runner.group_type?
jobs_for_group_runner
else
jobs_for_project_runner
end
jobs = jobs.matches_tag_ids(runner.tags.ids)
unless runner.run_untagged?
jobs = jobs.with_any_tags
end
目標
- CI ジョブの実行を
許可、拒否、またはリダイレクトするために、基本的なジョブの詳細(ユーザー、グループ、またはプロジェクトメンバーシップなど)に基づいて特定の Runner エンティティで CI ジョブ実行を許可、拒否、またはリダイレクトするための設定・使用が容易なメカニズムを提供する初期ソリューションを実装する。
非目標
- CI ジョブのキューイングメカニズムの再設計は、このブループリントの範囲に含まれません。
提案
CI ジョブをインターセプトして、それらをミューテートするか、バリデートするか、あるいはその両方を行うことができる アドミッションコントローラー というメカニズムを実装します。アドミッションコントローラーは CI ジョブを変更したり、ポリシーに従ってジョブを拒否したりできるミューテーティング Webhook です。Webhook は CI ジョブキューにジョブが挿入される前に呼び出されます。
ガイド原則
- Webhook ペイロードスキーマは公開向け API の一部となります。
- Webhook ペイロードを拡張する際には後方互換性を維持する必要があります。
- コントローラーは冪等性を持つ必要があります。
アドミッションコントローラーはどのように機能するか?
シナリオ 1: 特定の Runner へのアクセスを拒否したい。
- 特定のプロジェクトからのジョブのみを受け入れるようにアドミッションコントローラーを設定する。
- ジョブが作成されると
プロジェクト情報(project_id、job_id、api_token)が使用されて特定の詳細を GitLab に照会します。 プロジェクト情報が許可リストに一致する場合、ジョブペイロードは変更されず、ジョブはターゲット Runner で実行できます。プロジェクト情報が許可リストに一致しない場合、ジョブペイロードは変更されず、ジョブはドロップされます。- ジョブタグは変更されません。
- アドミッションコントローラーはオプションで拒否決定の理由の任意のテキスト説明を送り返すことができます。
シナリオ 2: 共通の設定とタグを使用する大きな Runner フリート。
各 Runner には zone_a、zone_b などのタグがあります。このシナリオでは、顧客は特定のジョブが実行できる場所を知らず、一部のユーザーは zone_a にアクセスでき、一部は zone_b にアクセスできます。顧客は zone_a で実行すべきジョブを失敗させたくなく、代わりに zone_a で実行するために正しくタグ付けされていない場合はジョブをリダイレクトしたいと考えています。
user_idに基づいてジョブをミューテートするようにアドミッションコントローラーを設定する。- ジョブが作成されると
プロジェクト情報(project_id、job_id、api_token)が使用されて特定の詳細を GitLab に照会します。 user_idが一致する場合、アドミッションコントローラーはジョブタグリストを変更します。コントローラーはジョブをトリガーするユーザーのジョブをzone_aで実行すべきと検出したため、zone_aがタグリストに追加されます。
シナリオ 3: 特定のタグスキームを持つ Runner プール、ユーザーは特定のサブセットにのみアクセス可能
各 Runner には DiscoveryOne、tugNostromo、MVSeamus などのその Runner に固有のタグ識別子があります。ユーザーはこれらの Runner への任意のアクセスを持っていますが、アクセス拒否でジョブを失敗させたくなく、代わりにユーザーがアクセスできない Runner でジョブが実行されないようにしたいです。また、ジョブを実行できる Runner のプールを減らしたくありません。
user_idに基づいてジョブをミューテートするようにアドミッションコントローラーを設定する。- ジョブが作成されると
プロジェクト情報(project_id、job_id、api_token)が使用されて特定の詳細を GitLab に照会します。 - アドミッションコントローラーは
user_idを持つ利用可能な Runner を照会して、ジョブを実行できないすべての Runner を収集します。これが_すべての_Runner の場合、アドミッションコントローラーはジョブを拒否し、ジョブはドロップされます。タグは変更されず、推論を示すメッセージが含まれます。ユーザーが権限を持つ Runner がある場合、アドミッションコントローラーは権限のない Runner に関連付けられた Runner をフィルタリングします。
MVC
アドミッションコントローラー
- 単一のアドミッションコントローラーをインスタンスレベルのみで登録できます。
- アドミッションコントローラーは 1 時間以内に応答する必要があります。
- アドミッションコントローラーは個々のジョブを受信します。レスポンスにはそのジョブに対するレスポンスのみが含まれる必要があります。
- アドミッションコントローラーは拒否と承認の API コールバックを受信し、承認コールバックはミューテーションパラメーターを受け入れます。
ジョブのライフサイクル
preparingジョブ状態は検証プロセスの前提条件を含むように拡張されます。stateDiagram-v2 created --> preparing state preparing { [*] --> accept [*] --> reject } reject --> failed accept --> pending pending --> running: picked by runner running --> executed state executed { [*] --> failed [*] --> success [*] --> canceled } executed --> created: retry状態が
preparingの場合、ミューテーティング Webhook ペイロードがアドミッションコントローラーに非同期で送信されます。これは必要に応じて何度もリトライされます。preparing状態は Webhook からのレスポンスを待つか、タイムアウトするまで待ちます。UI はジョブ前提条件に関するジョブ準備の現在のステータスで更新される必要があります。
Webhook がタイムアウトした(1 時間)ジョブは、タイムアウト推論でアドミッションが拒否されたかのようにステータスが設定される必要があります。これは通常の状況では稀であるはずです。
アドミッションが拒否されたジョブはリトライできます。リトライされたジョブはタグミューテーションや Runner フィルタリングがリセットされずにアドミッションコントローラーに再送信されます。
allow_failureはアドミッションが拒否されたときに失敗するジョブをサポートするように更新される必要があります。例えば:job: script: - echo "I will fail admission" allow_failure: on_denied_admission: trueUI はジョブミューテーションの理由(提供された場合)または拒否の理由を表示するように更新される必要があります。
アドミッションコントローラーによって適用されたタグ変更は、変更、承認、拒否に対する関連する推論とともにシステムによって永続化される必要があります。
ペイロード
- ペイロードは以下で構成される個々のジョブエントリで構成されます:
- ジョブ ID。
- 定義済み変数
- ジョブタグリスト。
- レスポンスペイロードは以下で構成される個々のジョブエントリで構成されます:
- ジョブ ID。
- アドミッション状態:
acceptedまたはdenied。 - ミューテーション:
additionsとremovals。additionsは既存のタグセットを補足し、removalsは現在のタグリストからタグを削除します。 - 理由: コントローラーはアドミッションとミューテーションの理由を提供できます。
- 承認された Runner: ジョブマッチングに考慮される Runner、空の場合はすべての Runner に一致します。
- 拒否された Runner: ジョブマッチングで考慮されない Runner、空の場合はすべての Runner に一致します。
リクエスト例
[
{
"id": 123,
"variables": {
# predefined variables: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
"CI_PROJECT_ID": 123,
"CI_PROJECT_NAME": "something",
"GITLAB_USER_ID": 98123,
...
},
"tags": [ "docker", "windows" ]
}
]
[
{
"id": 245,
"variables": {
"CI_PROJECT_ID": 245,
"CI_PROJECT_NAME": "foobar",
"GITLAB_USER_ID": 98123,
...
},
"tags": [ "linux", "eu-west" ]
}
]
[
{
"id": 666,
"variables": {
"CI_PROJECT_ID": 666,
"CI_PROJECT_NAME": "do-bad-things",
"GITLAB_USER_ID": 98123,
...
},
"tags": [ "secure-runner" ]
},
]
レスポンス例
[
{
"id": 123,
"admission": "accepted",
"reason": "it's always-allow-day-wednesday"
}
]
[
{
"id": 245,
"admission": "accepted",
"tags": {
"add": [ "linux", "us-west" ],
"remove": [...]
},
"runners": {
"accepted_ids": ["822993167"],
"rejected_ids": ["822993168"]
},
"reason": "user is US employee: retagged region; user only has uid on runner 822993167"
}
]
[
{
"id": 666,
"admission": "rejected",
"reason": "you have no power here"
}
]
MVC +
- グループとプロジェクトレベルで複数のアドミッションコントローラー。
- コントローラーのチェーン(プロジェクトから始まり、定義されたすべてのグループコントローラーを経てインスタンスコントローラーまで)を通じてジョブ定義を渡す。
- 各レベルは前のコントローラーによって変更された定義を受け取り、現在の状態に基づいて決定を行います。
- 複数のコントローラーによって報告された場合、変更の理由は連結されます。
- アドミッションコントローラーの使用はオプションであるため、プロジェクト+インスタンス、プロジェクト+グループ+親グループ+インスタンス、プロジェクト+グループ、グループ+インスタンスなどを含むチェーンを持つことができます。
実装の詳細
GitLab
prerequsiteインターフェースを介して検証プロセスを関与させるためにpreparing状態を拡張する。- UI と API を介してジョブ準備の前提条件に関するジョブ準備のステータスをユーザーに示すために
preparing状態を修正する。- 非同期であるため、各前提条件リソースのジョブのステータスを個別に示す必要があります。
- 全体的な前提条件のステータスを示す必要があります。
preparing状態全体に 1 時間のタイムアウトを導入する。Gitlab::Ci::Build::Prerequisite::Factoryを介してpreparingステータス依存関係にAdmissionValidation前提条件を追加する。- 前提条件ファクトリと
preparingステータスを非同期で動作するように変換する。 PreparingBuildServiceを非同期で動作するように変換する。PreparingBuildServiceは検証の成功に応じてジョブを preparing から failed または pending に遷移させます。- AdmissionValidation はリクエスト送信時に合理的な回数のリトライを実行する。
- Webhook/アドミッションコントローラーレスポンスコールバックの API エンドポイントを追加する。
- パラメーターを受け入れる:
- 承認/拒否
- 理由文字列
- タグのミューテーション(承認された場合、それ以外は無視)
- コールバックはワンタイム認証トークンをエンコードする。
- パラメーターを受け入れる:
- バリデーション拒否に関する新しい失敗理由を導入する。
- ジョブに対するアドミッションコントローラーの影響は永続化される必要がある。
- アドミッションコントローラー(ミューテーティング Web フック)からのレスポンスの関数としてジョブごとの Runner 選択フィルタリングを追加する。
解決すべき技術的な Issue
| Issue | 解決策 |
|---|---|
| キュー Web フックのルール定義 | |
| アドミッションコントローラーに送信するデータは何か?定義済み変数のサブセットかすべてか? | |
キューイング Web フック は GitLab.com のスケールで実行できるか?GitLab.com では毎秒何百万もの Webhook をトリガーし、Sidekiq を過負荷にしたり、システムを悪用するために使用されるのではないかという懸念がある。 |
c955a93f)