ランナーが実際にやっていること
一言でいうと
GitLabは設定を読んでジョブの一覧を作るだけで、そのジョブを実際に実行するのは別のプロセスであるランナーです。そのため、ランナーがなければ、パイプラインはエラーなしで永遠に待機状態のまま残ります。
なぜ必要なのか
パイプラインを初めて扱う人が最も頻繁に経験する行き詰まりは、文法エラーではなく、「ジョブは作られたのに始まらない」という状況です。画面に赤いエラーはなく、ジョブはpendingとだけ書かれています。この状態は、設定が間違っているのではなく、そのジョブを持っていくランナーがいないという意味ですが、サーバーと実行者が分かれた構造を知らないと、設定ファイルばかりを見続けることになります。
分離そのものは、意図された設計です。ビルドは重くて危険です。任意のコードを実行し、ディスクを埋め、ネットワークに出ていきます。そうしたことをサーバーと同じプロセスで行うと、サーバーも一緒に落ちます。実行を外に出せば、ランナーを必要なだけ増やせ、チームごとに異なる環境を与えられ、ランナー1つが壊れてもGitLabは無事です。
どう動くのか
ランナーはGitLabに登録されたあと、定期的に「持っていくジョブはありますか」と尋ねます。サーバーが押し付けるのではなくランナーが引き取る構造なので、ランナーがファイアウォールの内側にあっても、インバウンドポートを開ける必要がありません。
何を持っていくかは、タグが決めます。ジョブにtagsを書くと、そのタグを持つランナーだけがそのジョブを拾っていきます。タグが必要なジョブにタグを書かなかったり、そのタグを持つランナーが1つもなかったりすると、ジョブは待機し続けます。これが、先ほど言ったpendingの代表的な原因です。
拾っていったあとの実行方式は、executorが決めます。shellはランナーが入っているマシンでそのまま実行し、dockerはジョブごとにコンテナを新しく起動し、kubernetesはジョブごとにPodを作ります。後ろの2つは毎回きれいな環境を与えるので再現性が高い反面、ジョブの間には何も残りません。先に学んだartifactsとcacheが必要な理由が、まさにこれです。分離を保ちながら、必要なものだけを選んで渡すための仕組みです。
実行中は、GitLabがCI_で始まる変数をたくさん入れてくれます。CI_COMMIT_BRANCH、CI_COMMIT_SHA、CI_PIPELINE_ID、CI_JOB_IDのようなものです。rulesの条件もこの変数で書き、アーティファクトの名前やイメージのタグもこの変数で作ります。コミットSHAをイメージのタグに入れる習慣は、ここから生まれます。
現場での姿
ランナーの運用で最もよく争点になる数字は、同時実行数です。低すぎるとジョブが列を作り、高すぎるとマシンがスワップを始めてすべてのジョブが一緒に遅くなります。そして遅くなったパイプラインはリトライを呼び、リトライはまた負荷を生みます。
もう1つは、共有ランナーと専用ランナーの境界です。誰でも使える共有ランナーでデプロイジョブを動かすと、そのランナーを使うほかのプロジェクトが、デプロイの認証情報が通った跡を見られる可能性が生まれます。そのため、デプロイジョブは保護ブランチ専用のランナーに分けるのが基本です。
ちなみに、このラボ環境にはGitLabサーバーもランナーもありません。前の2つのラボは、ジョブがどう作られてどんな順序で並ぶかを、設定と自分で組んだ解釈器で扱いました。そのあとのラボは、GitLabと同じルールで設定を解釈し、ジョブをシェルで実際に実行するgitlab-ci-localで動かします。ランナーの登録・タグのマッチング・保護変数のように、サーバーがなければできない動作は再現せず、各ラボにその限界を書きました。
続けて見ること
パイプラインが事故を起こす場所を見ます。秘密の値が漏れる経路、他人の設定を引っ張ってきて使うときの危険、そしてデプロイを元に戻せるようにする最小限の仕組みを扱います。