コミットから完全ロールアウトへ:プログレッシブデリバリーパイプラインの構築

コードをメインブランチにマージした。CIパイプラインがグリーンになった。アーティファクトの準備はできた。さて、次はどうする?

多くのチームでは、次のステップは単純だ:本番にデプロイする。しかし、本番トラフィックがかかって初めて現れるバグ、静かに忍び寄るパフォーマンス低下、ユーザーを混乱させる機能——そういったデプロイの失敗を経験したことがあるなら、「全ユーザーに一度にデプロイする」というのは、決して取る必要のないギャンブルだと分かっているはずだ。

プログレッシブデリバリーはそれを変える。一度に大きなスイッチを入れる代わりに、トラフィックルーティング、フィーチャーフラグ、自動監視を使って段階的に変更をロールアウトし、各ステップで進めるか引き戻すかを判断する。この記事では、コードがマージされた瞬間から機能が完全に稼働して安定するまでの、完全なプログレッシブデリバリーパイプラインの組み立て方を解説する。

以下のフローチャートは、パイプラインの各ステージを一目で示している:

flowchart TD A[コミット/マージ] --> B[ビルド & テスト] B --> C[カナリーリング<br>1% トラフィック] C --> D{メトリクス監視} D -- 合格 --> E[段階的ロールアウト<br>10% → 25% → 50%] D -- 失敗 --> F[自動ロールバック] E --> G{監視 & ゲート} G -- 合格 --> H[完全ロールアウト<br>100%] G -- 失敗 --> F H --> I[フラグクリーンアップ] F --> J[チームに通知]

ビルドと基本テスト

パイプラインは他のものと同じように始まる。開発者がコードをメインブランチにマージすると、パイプラインはユニットテスト、統合テスト、セキュリティスキャンを実行する。これらはすでに実施しているチェックと同じもので、まだ特別なことは何もない。

すべてが合格すれば、パイプラインはデプロイ可能なアーティファクトを生成する。ここからが、プログレッシブデリバリーが標準的なパイプラインと異なる点だ。アーティファクトはすべてのユーザーに直接送られるわけではない。最初のデプロイリング、通常はカナリーリングと呼ばれるものに入る。

カナリーリング:最初の本番テスト

カナリーリングは、トラフィックのごく一部(たとえば全リクエストの1%)を受け取る。パイプラインは新しいバージョンをこのリングを担当するサーバーにデプロイする。そして、そのリングに割り当てられたユーザーに対して、関連するフィーチャーフラグを有効にする。

以下は、カナリーリングと段階的ロールアウトのステップを定義したGitLab CIの例である:

stages:
  - canary
  - staged-rollout
  - full-rollout

canary-deploy:
  stage: canary
  script:
    - kubectl set image deployment/my-app canary=my-app:$CI_COMMIT_SHA
    - kubectl scale deployment/my-app --replicas=1
  environment:
    name: production/canary
    url: https://canary.example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

auto-promote:
  stage: staged-rollout
  script:
    - sleep 600  # 10分間の監視ウィンドウ
    - ./check-metrics.sh  # エラーレート、レイテンシがSLO内であることを確認
    - kubectl set image deployment/my-app-10pct my-app=my-app:$CI_COMMIT_SHA
    - kubectl scale deployment/my-app-10pct --replicas=2
  needs: ["canary-deploy"]
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

rollout-50pct:
  stage: staged-rollout
  script:
    - ./check-metrics.sh
    - kubectl set image deployment/my-app-50pct my-app=my-app:$CI_COMMIT_SHA
  needs: ["auto-promote"]
  when: manual  # 手動承認が必要

rollout-100pct:
  stage: full-rollout
  script:
    - ./check-metrics.sh
    - kubectl set image deployment/my-app my-app=$CI_COMMIT_SHA
  needs: ["rollout-50pct"]
  when: manual

フィーチャーフラグはここでさらに制御の層を追加する。新しいバージョンが動作していても、特定の機能はカナリーリング内の特定のユーザーに対してのみ有効にできる。これにより、新しいバージョンの安定性だけでなく、特定の機能の振る舞いを実際のユーザーでテストでき、全員に公開する必要はない。

新しいバージョンがカナリーリングで動作している間、可観測性が機能する。パイプラインは、サービスレベル目標(SLO)で定義されたエラーレート、レイテンシ、その他のメトリクスを監視する。すべてのメトリクスが設定された期間(たとえば10分間)許容範囲内に収まれば、パイプラインは自動的に次のステージに進む。いずれかのメトリクスがしきい値を超えた場合、パイプラインは自動ロールバックをトリガーする。トラフィックは古いバージョンに戻り、フィーチャーフラグはオフになり、チームに通知が行く。

段階的ロールアウト:対象を拡大する

次のステージはより広いリングで、おそらくユーザーの10%を対象とする。プロセスは繰り返される。デプロイ、フラグの有効化、監視、判断。しかし今度は、フィーチャーフラグを試す余地が広がっている。たとえば、ベータテスターとして登録したユーザーにのみ新機能を有効にすることができる。これにより、全員に影響を与えることなく、実際のユーザーから実際のデータを得られる。

パイプラインは対象を段階的に拡大していく。10%から25%、そして50%、最終的に100%へ。パイプラインが次のリングに移行するたびに、プロモーションゲートを通過する。このゲートはメトリクスに基づく自動チェックでもよいし、チームによる手動承認を待つこともできる。多くのチームは組み合わせて使用している。迅速な判断には自動ゲート、重要なステージ(たとえば全ユーザーへの展開前)には手動承認という具合だ。

フィーチャーフラグのクリーンアップ

新しいバージョンがすべてのユーザーに提供されたら、パイプラインは終わりではない。現在全員に対して有効になっているフィーチャーフラグはクリーンアップする必要がある。パイプラインは、フラグがまだ使用されているかどうかを確認するタスクを実行し、リポジトリからフラグコードを削除するプルリクエストを作成できる。これにより、誰も覚えていないデッドフラグがコードベースに蓄積されるのを防ぐ。

このパイプラインの何が特別か

プログレッシブデリバリーパイプラインは、単なるデプロイの連続ではない。トラフィック管理、機能制御、自動監視、データ駆動型の判断を組み合わせ、コミットから完全ロールアウトまでを一貫したフローにしたシステムである。各ステージは、変更が突然ユーザー体験を壊さないようにするための安全層である。

従来のパイプラインとの主な違いは、「ビルドは通ったか?」ではなく、「この変更はより多くのユーザーに公開しても安全か?」と問う点にある。その質問にはユニットテストだけでは答えられない。実際のトラフィック、実際のユーザー、実際のメトリクスが必要なのだ。

パイプライン構築のための実践的チェックリスト

プログレッシブデリバリーパイプラインを構築する場合、各ステージで確認すべきコンポーネントは以下の通りである:

  • カナリーリング:測定可能な少量のトラフィックを受け取っているか?同じユーザーが常に同じリングに留まるように一貫してルーティングできるか?
  • フィーチャーフラグ:フラグは特定のユーザーやグループにスコープされているか?デプロイとは独立してオン/オフを切り替えられるか?
  • 可観測性:エラーレート、レイテンシ、ビジネスメトリクスは監視されているか?しきい値はSLOに基づいており、恣意的な推測ではないか?
  • プロモーションゲート:各リングに進むための明確な条件があるか?重要な拡大には手動承認ステップがあるか?
  • ロールバック:メトリクスがしきい値を超えた場合、ロールバックは自動化されているか?トラフィックルーティングの切り戻しとフラグの無効化の両方を含んでいるか?
  • フラグクリーンアップ:完全ロールアウト後にフラグを削除するプロセスはあるか?パイプラインがプルリクエストを作成するか、チームに通知するか?

まとめ

プログレッシブデリバリーパイプラインは、デプロイを二値的なイベントから段階的なプロセスへと変える。変更をリリースしてうまくいくことを願うのではない。小さなグループにリリースし、何が起こるかを観察し、続行するかどうかを判断する。何か問題が発生しても、影響を受けるのはごく一部のユーザーだけで、システムは自動的に回復する。

このパイプラインは複雑さを追加するためのものではない。制御を追加するためのものだ。すべてのリング、すべてのゲート、すべてのメトリクスチェックは、問題がインシデントになる前にキャッチするチャンスである。そのことを念頭に置いてパイプラインを構築すれば、マージのたびにぐっすり眠れるようになるだろう。