CI/CDパイプラインを実際に起動するものは何か

開発者がバグ修正を終え、git push と入力してその場を離れる。数分後、チームのチャットが「ビルド失敗」と騒ぎ始める。誰もパイプライン設定を触っていない。誰もボタンを押していない。それなのにパイプラインが勝手に動いた。

これは多くのチームで日常的に起こることだ。しかし、なぜパイプラインが動いたのかを尋ねると、返ってくる答えは曖昧なことが多い。「コードをプッシュしたから」。それは正しいが、重要な詳細が抜けている。パイプラインは自分自身では起動しない。何かがトリガーしなければならない。そして、どのトリガーを選ぶかによって、チームの働き方、テスト対象、本番リリースのタイミングが変わる。

ここでは、パイプラインが実際に起動する状況と、各トリガーが日常のワークフローに与える影響について見ていこう。

開発者がコードをプッシュしたとき

最も一般的なトリガーは、共有リポジトリにプッシュされたコミットだ。開発者が変更を終え、ローカルでコミットし、GitHub、GitLab、Bitbucket にプッシュする。リポジトリが CI/CD システムに Webhook を送信し、パイプラインが起動する。

単純に聞こえるが、細部が重要だ。コミットにはメタデータが含まれている。どのファイルが変更されたか、誰が変更したか、コミットメッセージは何か。優れたパイプラインはこのメタデータを使って何をすべきかを判断する。コミットが README ファイルだけを変更しているなら、おそらくフルテストスイートを実行してステージングにデプロイする必要はない。しかし、コミットがアプリケーションコードを変更しているなら、パイプラインはすべてを実行すべきだ。

以下の図は、プッシュトリガーがコミットメタデータに基づいて分岐する様子を示している。

GitHub Actions ワークフローでこのようなプッシュトリガーを定義する方法は次のとおりだ。

name: CI Pipeline
on:
  push:
    branches:
      - main
      - 'feature/**'
    paths-ignore:
      - 'README.md'
      - 'docs/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: make test
flowchart TD Push[Git Push] --> Webhook[CI/CDへのWebhook] Webhook --> Inspect[コミットメタデータの検査] Inspect --> Branch{ブランチ?} Branch -- feature --> Unit[単体テストとLintを実行] Branch -- main --> Full[フルテストスイートを実行] Inspect --> Files{変更されたファイル?} Files -- コードのみ --> Unit Files -- ドキュメントを含む --> Skip[テストをスキップ] Files -- 混合 --> Full

すべてのコミットを同じように扱うチームは、時間と計算リソースを無駄にしている。よりスマートなアプローチは、パイプラインにコミットとブランチを検査させてから次のステップを決定させることだ。たとえば、フィーチャーブランチへのコミットでは単体テストと Lint だけを実行し、メインブランチへのコミットでは結合テストとステージングへのデプロイをトリガーする。

プルリクエストがマージされたとき

多くのチームは、プルリクエストまたはマージリクエストをレビューゲートとして使用する。開発者が PR を開き、チームメイトがコードをレビューし、承認されると誰かがマージする。マージ自体がトリガーになり得る。

これは通常のコミットトリガーとは異なる。マージは通常、変更が人間によるレビューを通過し、より安定したブランチに入る準備ができたことを示す。マージによってトリガーされるパイプラインは、より厳格なチェックを実行することが多い。より長いテストスイート、セキュリティスキャン、データベースマイグレーションテストなどだ。この変更はまもなく本番環境にリリースされるという前提で、より高い確信を得たい。

マージ前にプルリクエスト自体に対してパイプラインを実行するチームもある。これにより早期フィードバックが得られる。しかし、マージトリガーが真実の瞬間だ。コードがメインブランチに入ると、共有履歴の一部になる。この時点でパイプラインが失敗すると、チームは迅速に修正する必要がある。なぜなら、他の開発者がその壊れたコードをプルするからだ。

時計がそう告げるとき

すべてのパイプラインがコード変更を必要とするわけではない。スケジュールトリガーは、誰もコードをプッシュしていなくても、特定の時間にパイプラインを起動する。

一般的なユースケースは次のとおりだ。

  • 誰も結果を待っていない夜間に長時間の結合テストを実行する。
  • ステージング環境を本番データのスナップショットでリフレッシュする。
  • 依存関係のバージョンを更新したり、脆弱性スキャンを実行したりする。
  • バックアップやクリーンアップタスクを実行する。

スケジュールトリガーは、定期的に実行する必要があるが開発者のアクティビティに依存しない作業に役立つ。また、テスト環境の徐々の劣化や期限切れ間近の証明書など、時間の経過とともに現れる問題もキャッチする。

欠点は、何も変更されていないときにスケジュールされたパイプラインが実行される可能性があることだ。失敗した場合、誰かがその失敗が本物なのか、それとも単に不安定なテストなのかを調査する必要がある。チームはスケジュールされたパイプラインを、定期的な実行が本当に必要なタスクに限定し、コード変更でトリガーできるものには使用しないようにすべきだ。

誰かがボタンを押したとき

手動トリガーは、人間に最終決定権を与える。パイプラインが自動的に起動する代わりに、誰かが CI/CD システムにログインして「実行」または「デプロイ」をクリックする。

これは本番デプロイで一般的だ。すべての自動チェックが合格しても、多くのチームは誰かに明示的にデプロイを承認してもらいたい。手動トリガーは緊急時にも対応する。以前のバージョンへのロールバック、環境クラッシュ後の再デプロイ、1回限りのデータマイグレーションの実行などだ。

手動トリガーは制御だけではない。責任も伴う。誰かが手動でパイプラインを起動した場合、その理由を記録すべきだ。優れた CI/CD システムでは、メモを追加できる。「最新リリースにデータベース接続リークがあるため v2.1.3 にロールバック」。このメタデータは監査証跡の一部になる。

手動トリガーのリスクは、ボトルネックになることだ。すべてのデプロイに誰かがボタンをクリックする必要があり、その人が休暇中なら何もリリースされない。チームは手動トリガーを、本当に人間の判断が必要なアクションに限定し、自動化できるルーチンステップには使用しないようにすべきだ。

メタデータが思っている以上に重要な理由

すべてのトリガーは情報を運ぶ。コミットトリガーはコミットハッシュ、ブランチ名、作成者を運ぶ。マージトリガーはプルリクエスト番号とレビュアー名を運ぶ。手動トリガーはオペレーターの名前とその理由を運ぶ。

このメタデータはログのためだけではない。パイプラインはそれを使って判断を下す。フルテストスイートを実行すべきか、それともスモークテストだけか?ステージングにデプロイすべきか、本番にデプロイすべきか?オンコールエンジニアに通知すべきか、それともチームチャンネルにサマリーを投稿するだけか?

メタデータがなければ、パイプラインは盲目だ。何が変更されたかに関係なく、毎回同じステップを実行する。それは単純なプロジェクトでは機能するが、システムが成長するにつれて条件付きロジックが必要になる。トリガーはそのロジックを注入する最初の場所だ。

トリガー選択のためのクイックチェックリスト

新しいパイプラインを設定する場合、または既存のパイプラインを見直す場合、以下の質問がどのトリガーを使用するかを決めるのに役立つ。

  • すべてのコミットに同じパイプラインが必要か、それともドキュメントのみの変更ではステップをスキップできるか?
  • マージ前にプルリクエストでフィードバックを得たいか、それともマージ後だけでよいか?
  • 誰もコードをプッシュしなくても毎日実行すべきタスクはあるか?
  • どのステップに人間の承認が必要で、どのステップが自動実行できるか?
  • パイプラインは後で障害をデバッグするのに十分なメタデータを取得しているか?

まとめ

パイプライントリガーは単なるスタートボタンではない。作業がいつ開始されるか、どのようなコンテキストが利用可能か、どれだけの自動化が安全かを定義する決定点だ。習慣ではなく、各アクションのリスクと頻度に基づいてトリガーを選択しよう。適切にトリガーされたパイプラインは、誰かがボタンをクリックするのを待つことなく、適切なタイミングで適切なチェックを実行する。