This page contains information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned on this page are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.
StatusAuthorsCoachDRIsOwning StageCreated
proposed @furkanayhan @ayufan @jreporter @cheryl.li devops verify 2023-05-15

CIパイプライン処理の未来

要約

GitLab CIはGitLabで最も古く、最も複雑な機能の一つです。長年にわたり、そのYAML構文はかなり大きく複雑になってきました。長年にわたって構文を高度に安定させるために、私たちは主に既存のデザインとパターンの上に追加的な変更を加えてきました。私たちのユーザー・ベースは過去数年間で飛躍的に成長しました。それに伴い、彼らのユースケースやワークフローのカスタマイズをサポートする必要が出てきました。

長年にわたって大きな価値を提供する一方で、構文へのさまざまな追加的な変更によって、パイプライン処理ロジックに驚くべき動作が発生しました。いくつかのキーワードは多くの責任を蓄積し、キーワードのあいまいな重複が発見され、動作の微妙な違いが時間の経過とともに導入されました。現在の実装とYAMLの構文は新しい機能を実装することを難しくしています。

この設計書では、問題点を議論し、パイプライン処理のための新しいアーキテクチャを提案します。これらの問題の大半は「キーワード時のCIジョブの再構築」エピックで以前に議論されています。

目標

  • パイプラインの処理をより理解しやすく、予測しやすく、一貫性のあるものにしたいと考えています。
  • DAGとSTAGEの動作を統一したいと思います。STAGEはDAGと書くことができ、その逆も可能です。
  • 手動ジョブのブロック動作をallow_failure キーワードから切り離したいです。
  • when キーワードの責任を明確にしたいです。

非ゴール

変更を壊さないようにする方法については、今は触れません。

動機

問題点のリストが、この設計書の主な動機です。

問題1:when キーワードの責任

現在、when キーワードには多くの責任があります;

  • on_success (デフォルト):ジョブを実行するのは、それ以前のステージのジョブが失敗しないか、allow_failure: true
  • on_failure:以前のステージのジョブが少なくとも1つ失敗した場合にのみジョブを実行します。以前のステージのジョブでallow_failure: true 、常に成功と見なされます。
  • never:以前のステージのジョブのステータスに関係なく、ジョブを実行しません。rules セクションまたはworkflow: rules でのみ使用できます。
  • always:前のステージのジョブのステータスに関係なくジョブを実行します。また、workflow:rules.
  • manual:手動でトリガーされたときのみジョブを実行します。
  • delayed:指定した時間だけジョブの実行を遅らせます

3つの質問に答えます;

  • 実行するには何が必要ですか? =>on_success,on_failurealways
  • manual実行するには?delayed
  • パイプラインに追加しますか?never

その結果、例えば、when: on_failuremanual ジョブを作成することはできません。これは、personaが失敗時にのみ利用可能で、手動で再生する必要があるジョブを作成したい場合に便利です。例えば、専用のページや専用の外部サービスに失敗を公開する場合などです。

問題2:allow_failure キーワードの乱用

手動ジョブのブロッカー動作をallow_failure キーワードで制御します。実は、このキーワードには他にも責務があります。_「ジョブが失敗したときにパイプラインの実行を継続するかどうかを決定する」_のです。

現在、手動ジョブは

  • は、allow_failure: true (デフォルト)の場合はブロッカーではありません。
  • allow_failure: false の場合はブロッカーです。

その結果、例えば、allow_failure: false 、ブロッカーではないmanual ジョブを作成することはできません。

job1:
  stage: test
  when: manual
  allow_failure: true # default

job2:
  stage: deploy

現在は;

  • job1 はスキップされます。
  • job2 が実行されるのは、job1allow_failure: true を持っているため無視されるからです。
  • job1
    • を実行すると、失敗した場合は “success with warning “と表示されます。

allow_failure とともにrules

allow_failure を使うと、rules がよりわかりにくくなります。

docsより

allow_failure のデフォルトの動作は、when: manual. when: manualtrue を指定すると true に変わります。when: manualただし when: manualruleswhen: manual併用 when: manualした場合、allow_failure のデフォルトはfalseになります。

docsより

allow_failure のデフォルト値は次のとおりです:

  • true 手動ジョブの場合。
  • false rules 内部でwhen: manual を使用するジョブの場合。
  • false それ以外の場合は

例えば

job1:
  script: ls
  when: manual

job2:
  script: ls
  rules:
    - if: $ALWAYS_TRUE
      when: manual

job1job2 では挙動が異なります;

  • job1 はデフォルトでallow_failure: true 、ブロッカーではありません。
  • job2 はブロッカーですrules: when: manual はデフォルトでallow_failure: true を返しません。

問題3:DAG/ニーズで異なる動作

DAGとSTAGEの主な動作の違いは、「スキップ」と「無視」の状態についてです。

背景情報

  • スキップ
    • ジョブがwhen: on_success 、その前のステータスが失敗であった場合、そのジョブはスキップされます。
    • ジョブがwhen: on_failure 、その前のステータスが「失敗」でない場合、そのジョブはスキップされます。
  • 無視されます:
    • ジョブがallow_failure: truewhen: manual になると無視されます。

問題があります:

skippedignored の状態は、STAGE 処理では成功とみなされますが、DAG 処理では成功とみなされません。

問題3.1.手動ジョブで無視されたステータスの処理

例 1:

build:
  stage: build
  script: exit 0
  when: manual
  allow_failure: true # by default

test:
  stage: test
  script: exit 0
  needs: [build]
  • build が無視(スキップ)されるのは、when: manualallow_failure: true であるためです。
  • test がスキップされるのは、「無視」がDAGの処理で成功した状態ではないからです。

例 2:

build:
  stage: build
  script: exit 0
  when: manual
  allow_failure: true # by default

test:
  stage: test
  script: exit 0
  • build が無視(スキップ)されるのは、when: manualallow_failure: true であるためです。
  • test2 が実行され、成功します。

問題 3.2.when:on_failureによるステータスのスキップ処理

例 1:

build_job:
  stage: build
  script: exit 1

test_job:
  stage: test
  script: exit 0

rollback_job:
  stage: deploy
  needs: [build_job, test_job]
  script: exit 0
  when: on_failure
  • build_job が実行され、失敗します。
  • test_job はスキップされます。
  • rollback_jobwhen: on_failure で、失敗したジョブがあっても、needs リストには「スキップされた」ジョブがあるため、スキップされます。

例 2:

build_job:
  stage: build
  script: exit 1

test_job:
  stage: test
  script: exit 0

rollback_job:
  stage: deploy
  script: exit 0
  when: on_failure
  • build_job が実行され、失敗します。
  • test_job はスキップされます。
  • rollback_job の前に失敗したジョブがあるために実行されます。

問題4:スキップされた状態と無視された状態

問題3を解決し、DAGとステージで「スキップされた状態」と「無視された状態」に違いがないと仮定しましょう。一般的にどのように振る舞うべきでしょうか?成功するのかしないのか?スキップ」と「無視」は異なるべきでしょうか?いくつかの例を見てみましょう;

例 4.1.手動ジョブでの無視ステータス

build:
  stage: build
  script: exit 0
  when: manual
  allow_failure: true # by default

test:
  stage: test
  script: exit 0
  • build は “manual” 状態ですが、パイプライン処理では “skip” (無視) とみなされます。
  • test は、”skipped” が成功した状態であるため、実行されます。

あるいは

build1:
  stage: build
  script: exit 0
  when: manual
  allow_failure: true # by default

build2:
  stage: build
  script: exit 0

test:
  stage: test
  script: exit 0
  • build1 は “manual” 状態ですが、パイプライン処理では “skip” (無視) とみなされます。
  • build2 が実行され、成功します。
  • test success “+”skipped “が成功状態なので、”run “が実行されます。

例4.2.when: on_failureによるスキップ状態

build:
  stage: build
  script: exit 0
  when: on_failure

test:
  stage: test
  script: exit 0
  • build は、when: on_failure 、以前のステータスが “failed “でないため、スキップされます。
  • test は、”skipped” が成功した状態であるため、実行されます。

あるいは

build1:
  stage: build
  script: exit 0
  when: on_failure

build2:
  stage: build
  script: exit 0

test:
  stage: test
  script: exit 0
  • build1 は、when: on_failure 、以前のステータスが “failed “でないため、スキップされます。
  • build2 が実行され、成功します。
  • test success “+”skipped “が成功状態なので、”run “が実行されます。

問題5:dependencies キーワード

dependencies キーワードは、アーティファクトをフェッチするジョブのリストを定義するために使用されます。これはneeds キーワードと共有されます。さらに、これらは同じジョブで一緒に使うことができます。すべての可能なシナリオを議論する必要はないかもしれませんが、混乱を示すにはこの例で十分です;

test2:
  script: exit 0
  dependencies: [test1]
  needs:
    - job: test1
      artifacts: false

情報1: キャンセルされたジョブ

キャンセルされたジョブと失敗したジョブは同じですか?両者には多くの違いがあります。しかし、1つだけ共通点があります。

まずその違いを定義しましょう;

  • キャンセルされたジョブ;
    • 終了したジョブではありません。
    • Canceled はユーザーが要求したジョブの中断です。ジョブを中断するか、パイプライン処理をできるだけ早く停止することを意図しています。
    • 結果はわからず、アーティファクトなどもありません。
    • 実行されることはないので、after_script
    • 最終的な状態は “canceled “なので、それ以降のジョブは実行できません。
      • when: on_canceled はありません。
      • when: always もありません。
  • 失敗したジョブです;
    • ジョブ内容の実行に対するCIシステムの機械的な応答です。何らかの理由で実行に失敗したことを示します。
    • それは成功に対するシステムの答えと同じです。何かが失敗したという事実は相対的なものであり、CI実行の望ましい結果かもしれません。
    • 私たちは結果を知っていますし、アーティファクトもありえます
    • after_script が実行されます。
    • 最終的な状態は “failed “であるため、後続のジョブはwhen の値に応じて実行することができます。
      • when: on_failurewhen: always が実行されます。

1つだけ似ているのは、「失敗させる」ことができるということです。

build:
  stage: build
  script: sleep 10
  allow_failure: true

test:
  stage: test
  script: exit 0
  when: on_success
  • buildcanceledtest
  • buildfailedtest

場合によっては、failed の代わりにcanceled を使うというアイデアもあります。

別の側面もあります。failure_reason 例えば、名前空間がコンピュートクレジット(CI分)を使い果たした場合や、制限を超えた場合などです。failed の状態でジョブを落とすと、ユーザーにfailure_reason を伝えることができ、より良いフィードバックが得られるので便利です。様々な理由でジョブをキャンセルする場合、それを示す方法がありません。パイプラインの実行中にユーザーがコンピュートクレジットを使い果たしたり、パイプラインが他のパイプラインによって自動キャンセルされたり、その他の理由でジョブをキャンセルします。もし、failure_reason の代わりにstop_reason があれば、キャンセルされたジョブにも失敗したジョブにも使用することができます。また、canceled のステータスをより適切に使用することもできます。

情報2: 空の状態

最近のドキュメントを[更新](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117856)し、when キーワード を明確にしました;

  • on_success:ジョブの実行は、それ以前のステージのジョブが失敗しないか、allow_failure: true
  • on_failure:以前のステージのジョブが少なくとも1つ失敗した場合にのみジョブを実行します。

例えば

test1:
  when: on_success
  script: exit 0
  # needs: [] would lead to the same result

test2:
  when: on_failure
  script: exit 0
  # needs: [] would lead to the same result
  • test1 が実行されるのは、前のステージで失敗したジョブがないからです。
  • test2 前のステージで失敗したジョブがないため、実行されません。

on_success は「何も失敗しなかった」という意味であり、すべてが成功したという意味ではありません。on_failure も同様で、すべてが失敗したという意味ではなく、”何かが失敗した “という意味です。このセマンティックは、パイプラインが成功し、これが幸せな道であることを期待しています。パイプラインが失敗するという意味ではありません。

技術的な期待

すべての提案または将来の決定は、これらの目標に従わなければなりません;

  1. allow_failure キーワードは、失敗したジョブを “警告付き成功 “としてマークすることにのみ責任を負わなければなりません。
    • なぜかというと、手動ジョブがブロッカーかどうかを判断するような、別の責任を持つべきではありません。
    • どのように:手動ジョブのブロッカー動作を制御するために、別のキーワードが導入されます。
  2. allow_failure では、キャンセルされたジョブは “success with warning “としてマークされません。
    • なぜかというと、”キャンセル “は “失敗 “とは異なる状態だからです。
    • どのように:allow_failure: true でキャンセルされたジョブは “success with warning” としてマークされません。
  3. when キーワードは「実行に必要なものは何か?そして、それはジョブが実行されるべきか否かを決定するための唯一の真実の情報源でなければなりません。
  4. when キーワードは、ジョブがパイプラインに追加されるかどうかを制御してはなりません。
    • 理由: それはキーワードの責任ではありません。
    • どのように:ジョブがパイプラインに追加されるかどうかを制御するために、別のキーワードが導入されます。
  5. skipped “と “ignored “の状態を再考する必要があります。
    • TODO: もっと議論する必要があります。
  6. ジョブが “自動”、”手動”、または “遅延 “ジョブであるかどうかを指定するために、新しいキーワード構造を導入する必要があります。
    • 理由:これはwhen キーワードの責任ではありません。
    • どのように:ジョブの動作を制御するために新しいキーワードが導入されます。
  7. needs キーワードはジョブの順序のみを制御しなければなりません。ジョブの動作を制御したり、ジョブを実行するかどうかを決定したりするために使用してはなりません。DAG と STAGE の動作は同じでなければなりません。
    • 理由: 異なる動作になり、ユーザーを混乱させます。
    • How:needs キーワードは、ステージのように以前のジョブのみを定義します。
  8. needsdependencies キーワードは同じジョブ内で一緒に使用してはいけません。
    • 理由:混乱を招きます。
    • How:needsdependencies キーワードは相互に排他的になります。

提案

該当なし

デザインおよび実施内容

該当なし