CI/CD開発ドキュメント
CI/CDに特化した開発者向けガイドを掲載しています。
新しいCI/CDテンプレートを作成する場合は、GitLab CI/CDテンプレートの開発ガイドをお読みください。
CIアーキテクチャの概要
以下は、CIアーキテクチャーの簡略図です。 主要なコンポーネントに焦点を当てるため、いくつかの詳細は省略しています。
左側には、様々なイベント(ユーザーまたはオートメーションによってトリガーされる)に基づいてパイプラインをトリガーできるイベントがあります:
-
git push
、パイプラインのトリガーとなる最も一般的なイベントです。 - ウェブAPI。
- UIの「パイプラインの実行」ボタンをクリックするユーザー。
- マージリクエストが作成または更新されたとき。
- MR がマージトレインに追加された場合。
- 予定されているパイプライン。
- プロジェクトが上流プロジェクトにサブスクライブされている場合。
- Auto DevOpsが有効な場合。
- 外部プルリクエストでGitHubインテグレーションを使用する場合。
- 上流のパイプラインに、下流のパイプラインをトリガーするブリッジジョブが含まれている場合。
これらのイベントのいずれかがトリガーされると、CreatePipelineService
が起動し、イベントデータとトリガーしたユーザーを入力として受け取り、パイプラインの作成を試みます。
CreatePipelineService
はYAML Processor
コンポーネントに大きく依存しています。このコンポーネントは入力として YAML blob を受け取り、パイプラインの抽象的なデータ構造 (ステージとすべてのジョブを含む) を返します。このコンポーネントはまた、処理中に YAML の構造を検証し、構文エラーまたはセマンティックエラーを返します。YAML Processor
コンポーネントはパイプラインを構造化するために利用可能なすべてのキーワードを定義する場所です。
CreatePipelineService
は、YAML Processor
によって返された抽象データ構造を受け取り、それを永続化されたモデル(パイプライン、ステージ、ジョブなど)に変換します。その後、パイプラインを処理する準備が整います。パイプラインの処理とは、ジョブを実行順(ステージまたはDAG)に次のいずれかになるまで実行することです:
- 期待されたジョブはすべて実行されました。
- 失敗するとパイプラインの実行が中断されます。
パイプラインを処理するコンポーネントはProcessPipelineService
で、パイプラインのすべてのジョブを完了状態に移行させる役割を担います。パイプラインが作成されると、そのすべてのジョブは最初のcreated
状態になります。このサービスは created
、パイプライン構造に基づいて、ステージcreated
内のどのジョブが created
処理される資格があるかを調べます。次に、それらをpending
の状態に移行させます。これは、Runnerによってピックアップできるようになることを意味します。ジョブが実行された後、成功または失敗することができます。パイプライン内のジョブの各ステータス遷移は、このサービスを再度トリガーし、完了に向けて移行される次のジョブを探します。その間に、ProcessPipelineService
、ジョブ、ステージ、パイプライン全体のステータスが更新されます。
図の右側には、GitLabインスタンスに接続しているRunnerのリストがあります。 これらは、共有Runner、グループRunner、プロジェクト固有のRunnerのいずれかになります。RunnerとRailsサーバーの間の通信は、APIエンドポイントのセット、Runner API Gateway
.
Runnerを登録、削除、検証することができ、データベースへの読み取り/書き込みクエリも発生します。 Runnerが接続された後、Runnerは次のジョブの実行を要求し続けます。これはRegisterJobService
、次のジョブを選択し、Runnerに割り当てます。この時点でジョブはrunning
、ステータスが変更されたため、再びProcessPipelineService
。詳細はジョブスケジューリング)を参照してください。
ジョブが実行されている間、Runnerは保存が必要なアーティファクトと同様にログをサーバーに送り返します。 また、ジョブは実行するために以前のジョブのアーティファクトに依存することがあります。 この場合、Runnerは専用のAPIエンドポイントを使用してそれらをダウンロードします。
アーティファクトはオブジェクトストレージに保存され、メタデータはデータベースに保存されます。 アーティファクトの重要な例として、マージリクエストで解析およびレンダリングされるレポート(JUnit、SAST、DASTなど)があります。
ジョブのステータス遷移はすべて自動化されているわけではありません。 ユーザーは手動でジョブを実行したり、パイプラインをキャンセルしたり、特定の失敗したジョブやパイプライン全体を再試行したりすることができます。 ジョブのステータスを変更させるものはすべて、パイプライン全体のステータスを追跡する責任があるため、ProcessPipelineService
がトリガーされます。
pending
このジョブは、マルチプロジェクトや子パイプラインなどの下流パイプラインの作成を担当します。 下流パイプラインがトリガーされるたびに、ワークフローのループはCreatePipelineService
から再スタートします。
ジョブスケジューリング
パイプラインが作成されると、すべてのジョブはすべてのステージで一度に作成され、初期状態はcreated
です。これにより、パイプラインの全内容を可視化することができます。
created
状態のジョブは、まだランナーには表示されません。ジョブをランナーに割り当てるには、ジョブが最初にpending
状態に移行する必要があります:
- ジョブはパイプラインの最初のステージで作成されます。
- このジョブには手動スタートが必要で、それが発動されました。
- この場合、次のステージのジョブはすべて
pending
に移行します。 - ジョブは
needs:
を使用して DAG 依存関係を指定し、依存するすべてのジョブが完了します。
Runnerが接続されると、サーバーを連続的にポーリングして次のpending
ジョブの実行を要求します。
lib/api/runner.rb
サーバーはリクエストを受信すると、Ci::RegisterJobService
アルゴリズムに基づいてpending
ジョブを選択し、ジョブを割り当ててRunnerに送信します。
現在のステージのジョブがすべて完了すると、サーバーは次のステージのすべてのジョブの状態をpending
に変更して「ロック解除」します。ランナーが新しいジョブを要求したときに、スケジューリングアルゴリズムがこれらのジョブを選択できるようになります。
RunnerとGitLabサーバー間の通信
登録トークンを使用してRunnerが登録されると、サーバーは実行可能なジョブの種類を知ることができます。 これは次のことに依存します:
- 登録されているランナーのタイプ:
- 共有ランナー
- グループランナー
- プロジェクト専用ランナー
- 関連するタグ
Runnerは、POST /api/v4/jobs/request
。このポーリングは通常数秒ごとに行われますが、ジョブキューが変更されない場合は、HTTPヘッダを介したキャッシュを活用してサーバー側の作業負荷を軽減します。
この API エンドポイントはCi::RegisterJobService
を実行します:
-
pending
ジョブのプールから次に実行するジョブを選択します。 - ランナーに割り当てます。
- API レスポンスを介して Runner に提示します。
Ci::RegisterJobService
このサービスでは、ジョブの大半を収集するために3つのトップレベルクエリが使用され、それらはランナーが登録されているレベルに基づいて選択されます:
- 共有Runner用のジョブの選択(インスタンスレベル)
- グループレベルランナーのジョブを選択します。
- プロジェクト・ランナーのためのジョブの選択
このジョブリストは、ジョブタグとランナータグのマッチングにより、さらにフィルタリングされます。
最後に、Runnerがタグ付けされたジョブのみを選択できる場合、タグ付けされていないジョブはすべてフィルタリングされます。
この時点で、残りのpending
ジョブをループし、追加ポリシーに基づいてランナーが “選択できる” 最初のジョブを割り当てようとします。 たとえば、protected
としてマークされたランナーは、保護されたブランチ (本番デプロイなど) に対して実行されるジョブのみを選択できます。
プール内のRunnerの数を増やすと、同じジョブを異なるRunnerに割り当てた場合に発生するコンフリクトの可能性も増えます。 これを防ぐために、コンフリクトエラーを潔くレスキューし、リストの次のジョブを割り当てます。