TT Lab
はじめる
学ぶ 学習パス コース

GitLab CI/CD

遅い e2e 一つのせいでドキュメント公開が 8 秒待たされた

TT Labで続きを見る

目標

遅いジョブが混ざったパイプラインを実際に動かして、ステージとneedsがジョブの出発時刻をどう変えるかを測り、アーティファクトの受け渡し・条件付きジョブの待機・失敗の許容の方式を、実行結果で確認します。

なぜ重要なのか

ステージ方式は理解しやすいですが、最も遅いジョブが、後ろのすべてのジョブの出発を引き止めます。needsで必要なものだけを待たせればパイプラインは速くなりますが、その代わり、何を待ち何を受け取るのかを、ジョブごとに正確に書く必要があります。rulesで抜けうるジョブを待つとパイプラインがそもそも作られず、失敗を広く許容しすぎると、本当の事故が緑色のまま通り過ぎます。これらの選択は、ドキュメントを読むときよりも、時刻と結果を見たときのほうがはっきりします。

ステップ

  1. /root/glci-dagをgitリポジトリにし、.gitlab-ci.ymlにstages[build, test, deploy]とジョブ4つを置いてください。compile(build、sleep 3のあとでbin/appファイルを作り、artifactsでbin/をアップロードする)、unit(test、test -f bin/appのあとでecho unit-ok)、e2e(test、sleep 8のあとでecho e2e-ok)、publish-docs(deploy、test -f bin/appのあとでecho docs-published)です。gitlab-ci-local --shell-isolation --no-artifacts-to-source --timestampsで実行して、publish-docsが遅いe2eが終わってからようやく始まることを見てください。
  2. publish-docsにneeds: [compile]を追加してください。実行すると、publish-docsがe2eの終了前に始まり、それでもcompileのアーティファクト(bin/app)は受け取れなければなりません。
  3. ジョブlint(stageはtest、sleep 1のあとでecho lint-ok)をneeds: []で追加してください。実行すると、lintがcompileの終了前に始まるはずです。
  4. ジョブaudit(stageはtest)をneedsの長い形式でjob: compile、artifacts: falseとし、scriptをtest ! -e bin/app && echo no-artifactにしてください。実行すると、auditは成功し(bin/appがない)、unitはそれでもbin/appを受け取れなければなりません。
  5. ジョブintegration(stageはtest)には、$RUN_INTEGRATION == "yes"のときだけ作られるようにrulesを置き、sleep 2のあとでecho integration-okを実行します。ジョブrelease(stageはdeploy)は、needsにunitとjob: integration, optional: trueを置いて、echo releaseを実行します。変数なしで実行するとintegrationなしでreleaseが動き、--variable RUN_INTEGRATION=yesで実行するとintegrationが終わったあとにreleaseが始まるはずです。
  6. ジョブを3つ追加してください。flaky(test)はecho flaky-runのあとでexit 1しますが、allow_failure: trueです。cleanup(deploy)はwhen: alwaysでecho cleanupを、notify-failure(deploy)はwhen: on_failureでecho notifyを実行します。実行すると、flakyは警告で終わってパイプラインは成功し、cleanupは動き、notify-failureは動かないはずです。採点ツールは、コピーでflakyのallow_failureを外した場合でも実行して確認します。
  7. ジョブcheck-configをstage.preに追加してください(stagesのリストには書きません)。sleep 2のあとにtest ! -e STOPで、リポジトリにSTOPファイルがあれば失敗し、なければecho config-okを実行します。実行すると、check-configが終わってからcompileが始まるはずです。採点ツールは、コピーにSTOPファイルを入れて、事前検査が失敗したらcompileがそもそも動かないかも確認します。

参考

ステージは前のステージのすべてを待つ

/root/glci-dagをgitリポジトリにし、.gitlab-ci.ymlにstages[build, test, deploy]とジョブ4つを置いてください。compile(build、sleep 3のあとでbin/appファイルを作り、artifactsでbin/をアップロードする)、unit(test、test -f bin/appのあとでecho unit-ok)、e2e(test、sleep 8のあとでecho e2e-ok)、publish-docs(deploy、test -f bin/appのあとでecho docs-published)です。gitlab-ci-local --shell-isolation --no-artifacts-to-source --timestampsで実行して、publish-docsが遅いe2eが終わってからようやく始まることを見てください。

needsのないジョブは、前のステージのすべてのジョブが終わって初めて出発し、前のステージすべてのアーティファクトを受け取ります。--timestampsを付けると各行の前に時刻が出力されるので、開始(starting shell)と終了(finished in)を比較できます。

ドキュメントの公開はコンパイルだけを待てばよい

publish-docsにneeds: [compile]を追加してください。実行すると、publish-docsがe2eの終了前に始まり、それでもcompileのアーティファクト(bin/app)は受け取れなければなりません。

needsは、待つジョブを直接指定します。書いたジョブのアーティファクトだけを受け取り、ステージの順序はもう出発時刻を決めません。一度needsを使うと、書いていないジョブは待ちもせず、アーティファクトも受け取りません。

needs: []はパイプラインが始まるとすぐに出発する

ジョブlint(stageはtest、sleep 1のあとでecho lint-ok)をneeds: []で追加してください。実行すると、lintがcompileの終了前に始まるはずです。

空のneedsは「誰も待たない」という意味です。ステージがtestでも、buildが終わるのを待ちません。ソースだけ見ればよい検査をこのように前倒しすると、失敗を数分早く知ることができます。

順序は待つが、アーティファクトは受け取らない

ジョブaudit(stageはtest)をneedsの長い形式でjob: compile、artifacts: falseとし、scriptをtest ! -e bin/app && echo no-artifactにしてください。実行すると、auditは成功し(bin/appがない)、unitはそれでもbin/appを受け取れなければなりません。

needsの長い形式では、ジョブごとにアーティファクトを受け取るかどうかを切れます。順序だけが必要でファイルは要らないジョブが、大きなアーティファクトをダウンロードするせいで遅くなるのを防ぎます。

あるかもしれないし、ないかもしれないジョブを待つ

ジョブintegration(stageはtest)には、$RUN_INTEGRATION == "yes"のときだけ作られるようにrulesを置き、sleep 2のあとでecho integration-okを実行します。ジョブrelease(stageはdeploy)は、needsにunitとjob: integration, optional: trueを置いて、echo releaseを実行します。変数なしで実行するとintegrationなしでreleaseが動き、--variable RUN_INTEGRATION=yesで実行するとintegrationが終わったあとにreleaseが始まるはずです。

rulesで抜けうるジョブをそのままneedsに書くと、GitLabはそのジョブがないとき、パイプライン自体を作りません。optional: trueは「あれば待ち、なければ先へ進む」という意味です。

失敗を許容するジョブ、失敗しても動くジョブ、失敗したときに動くジョブ

ジョブを3つ追加してください。flaky(test)はecho flaky-runのあとでexit 1しますが、allow_failure: trueです。cleanup(deploy)はwhen: alwaysでecho cleanupを、notify-failure(deploy)はwhen: on_failureでecho notifyを実行します。実行すると、flakyは警告で終わってパイプラインは成功し、cleanupは動き、notify-failureは動かないはずです。採点ツールは、コピーでflakyのallow_failureを外した場合でも実行して確認します。

allow_failureは、失敗を「警告」に変えて、後ろのステージを止めないようにします。when: on_failureは前に失敗したジョブがあるときだけ、alwaysは結果に関係なく動きます。許容された失敗は、on_failureを呼びません。

すべてのステージより先に動く事前検査

ジョブcheck-configをstage.preに追加してください(stagesのリストには書きません)。sleep 2のあとにtest ! -e STOPで、リポジトリにSTOPファイルがあれば失敗し、なければecho config-okを実行します。実行すると、check-configが終わってからcompileが始まるはずです。採点ツールは、コピーにSTOPファイルを入れて、事前検査が失敗したらcompileがそもそも動かないかも確認します。

.preは、stagesに書かなくても常に一番前にある予約ステージです(一番後ろには.post)。ステージの一覧に手を触れずに、パイプライン全体の事前検査を付けるときに使います。前のステージが失敗すると、後ろのステージの通常のジョブは、作られるだけで動きません。