CI/CD開発ドキュメント

CI/CDに特化した開発者向けガイドを掲載しています。

新しいCI/CDテンプレートを作成する場合は、GitLab CI/CDテンプレートの開発ガイドをお読みください。

CIアーキテクチャの概要

以下は、CIアーキテクチャーの簡略図です。 主要なコンポーネントに焦点を当てるため、いくつかの詳細は省略しています。

CI software architecture

左側には、様々なイベント(ユーザーまたはオートメーションによってトリガーされる)に基づいてパイプラインをトリガーできるイベントがあります:

これらのイベントのいずれかがトリガーされると、CreatePipelineServiceが起動し、イベントデータとトリガーしたユーザーを入力として受け取り、パイプラインの作成を試みます。

CreatePipelineServiceYAML 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 状態に移行する必要があります:

  1. ジョブはパイプラインの最初のステージで作成されます。
  2. このジョブには手動スタートが必要で、それが発動されました。
  3. この場合、次のステージのジョブはすべてpendingに移行します。
  4. ジョブはneeds: を使用して DAG 依存関係を指定し、依存するすべてのジョブが完了します。

Runnerが接続されると、サーバーを連続的にポーリングして次のpending ジョブの実行を要求します。

注:RunnerがGitLabとやりとりするために使用するAPIエンドポイントは、次の場所で定義されています。 lib/api/runner.rb

サーバーはリクエストを受信すると、Ci::RegisterJobService アルゴリズムに基づいてpending ジョブを選択し、ジョブを割り当ててRunnerに送信します。

現在のステージのジョブがすべて完了すると、サーバーは次のステージのすべてのジョブの状態をpendingに変更して「ロック解除」します。ランナーが新しいジョブを要求したときに、スケジューリングアルゴリズムがこれらのジョブを選択できるようになります。

RunnerとGitLabサーバー間の通信

登録トークンを使用してRunnerが登録されると、サーバーは実行可能なジョブの種類を知ることができます。 これは次のことに依存します:

  • 登録されているランナーのタイプ:
    • 共有ランナー
    • グループランナー
    • プロジェクト専用ランナー
  • 関連するタグ

Runnerは、POST /api/v4/jobs/request。このポーリングは通常数秒ごとに行われますが、ジョブキューが変更されない場合は、HTTPヘッダを介したキャッシュを活用してサーバー側の作業負荷を軽減します。

この API エンドポイントはCi::RegisterJobServiceを実行します:

  1. pending ジョブのプールから次に実行するジョブを選択します。
  2. ランナーに割り当てます。
  3. API レスポンスを介して Runner に提示します。

Ci::RegisterJobService

このサービスでは、ジョブの大半を収集するために3つのトップレベルクエリが使用され、それらはランナーが登録されているレベルに基づいて選択されます:

  • 共有Runner用のジョブの選択(インスタンスレベル)
  • グループレベルランナーのジョブを選択します。
  • プロジェクト・ランナーのためのジョブの選択

このジョブリストは、ジョブタグとランナータグのマッチングにより、さらにフィルタリングされます。

注:ジョブにタグが含まれている場合、すべてのタグに一致しない場合、Runnerはジョブを選択しません。 Runnerはジョブに対して定義されたよりも多くのタグを持つことができますが、その逆はできません。

最後に、Runnerがタグ付けされたジョブのみを選択できる場合、タグ付けされていないジョブはすべてフィルタリングされます。

この時点で、残りのpending ジョブをループし、追加ポリシーに基づいてランナーが “選択できる” 最初のジョブを割り当てようとします。 たとえば、protected としてマークされたランナーは、保護されたブランチ (本番デプロイなど) に対して実行されるジョブのみを選択できます。

プール内のRunnerの数を増やすと、同じジョブを異なるRunnerに割り当てた場合に発生するコンフリクトの可能性も増えます。 これを防ぐために、コンフリクトエラーを潔くレスキューし、リストの次のジョブを割り当てます。