コミットから完全ロールアウトへ:プログレッシブデリバリーパイプラインの構築
コードをメインブランチにマージした。CIパイプラインがグリーンになった。アーティファクトの準備はできた。さて、次はどうする?
多くのチームでは、次のステップは単純だ:本番にデプロイする。しかし、本番トラフィックがかかって初めて現れるバグ、静かに忍び寄るパフォーマンス低下、ユーザーを混乱させる機能——そういったデプロイの失敗を経験したことがあるなら、「全ユーザーに一度にデプロイする」というのは、決して取る必要のないギャンブルだと分かっているはずだ。
プログレッシブデリバリーはそれを変える。一度に大きなスイッチを入れる代わりに、トラフィックルーティング、フィーチャーフラグ、自動監視を使って段階的に変更をロールアウトし、各ステップで進めるか引き戻すかを判断する。この記事では、コードがマージされた瞬間から機能が完全に稼働して安定するまでの、完全なプログレッシブデリバリーパイプラインの組み立て方を解説する。
以下のフローチャートは、パイプラインの各ステージを一目で示している:
ビルドと基本テスト
パイプラインは他のものと同じように始まる。開発者がコードをメインブランチにマージすると、パイプラインはユニットテスト、統合テスト、セキュリティスキャンを実行する。これらはすでに実施しているチェックと同じもので、まだ特別なことは何もない。
すべてが合格すれば、パイプラインはデプロイ可能なアーティファクトを生成する。ここからが、プログレッシブデリバリーが標準的なパイプラインと異なる点だ。アーティファクトはすべてのユーザーに直接送られるわけではない。最初のデプロイリング、通常はカナリーリングと呼ばれるものに入る。
カナリーリング:最初の本番テスト
カナリーリングは、トラフィックのごく一部(たとえば全リクエストの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に基づいており、恣意的な推測ではないか?
- プロモーションゲート:各リングに進むための明確な条件があるか?重要な拡大には手動承認ステップがあるか?
- ロールバック:メトリクスがしきい値を超えた場合、ロールバックは自動化されているか?トラフィックルーティングの切り戻しとフラグの無効化の両方を含んでいるか?
- フラグクリーンアップ:完全ロールアウト後にフラグを削除するプロセスはあるか?パイプラインがプルリクエストを作成するか、チームに通知するか?
まとめ
プログレッシブデリバリーパイプラインは、デプロイを二値的なイベントから段階的なプロセスへと変える。変更をリリースしてうまくいくことを願うのではない。小さなグループにリリースし、何が起こるかを観察し、続行するかどうかを判断する。何か問題が発生しても、影響を受けるのはごく一部のユーザーだけで、システムは自動的に回復する。
このパイプラインは複雑さを追加するためのものではない。制御を追加するためのものだ。すべてのリング、すべてのゲート、すべてのメトリクスチェックは、問題がインシデントになる前にキャッチするチャンスである。そのことを念頭に置いてパイプラインを構築すれば、マージのたびにぐっすり眠れるようになるだろう。