デプロイにおける不安の正体
かつて、デプロイの日は私たちのチームにとって悪夢でした。Kubernetes標準のRollingUpdateを使用していても、頻繁に壁にぶつかっていました。新しいバージョンがすべてのCI/CDテストをパスしても、実際のトラフィックが流入した瞬間に崩壊するのです。移行中にエラー率は15%も急上昇し、ロールバックの作業はまるで「一度焼いたケーキを元に戻そうとする」ようなものでした。時間はかかり、手順は煩雑で、たいていはマネージャーが後ろで目を光らせている中で行われました。
多くの場合、コードそのものが悪者なのではなく、トラフィック制御の欠如が原因です。破壊的な変更を一度に100%のユーザーにリリースすることは、アップライトを賭けたハイリスクなギャンブルです。これを解決するために、私たちは基本的なアップデートから、ブルー/グリーン(Blue/Green)およびカナリア(Canary)パターンへと移行しました。この半年間で、この移行により200回以上のリリースを経ても本番環境を安定して維持できています。
ブルー/グリーンデプロイ:瞬時の切り替え
ブルー/グリーンデプロイは、2つの同一の本番環境を同時に実行することでリスクを排除します。「ブルー」は現在のライブバージョンであり、「グリーン」は新しいバージョンです。グリーンの環境が健全であると確信したときにのみ、スイッチを切り替えます。
Kubernetesでの仕組み
これを実現するために2つの独立したクラスターを用意する必要はありません。代わりに、Labels(ラベル)とSelectors(セレクター)を活用します。KubernetesのServiceがトラフィックコントローラーとして機能します。Serviceのセレクターを更新するだけで、ユーザーをブルーのPodからグリーンのPodへと瞬時にルーティングし直すことができます。
実践:ブルー/グリーンの実装
具体的な例を見てみましょう。現在、ブルー(v1)のデプロイメントがトラフィックを処理しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-v1
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: v1
template:
metadata:
labels:
app: my-app
version: v1
spec:
containers:
- name: app
image: my-registry/app:v1
ports:
- containerPort: 8080
Serviceは現在、version: v1をターゲットにしています。
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: my-app
version: v1 # これがトラフィックの切り替えスイッチになります
ports:
- protocol: TCP
port: 80
targetPort: 8080
バージョン2の準備ができたら、新しいデプロイメント(app-v2)を立ち上げます。それらのPodがReadiness Probe(レディネスプローブ)をパスしたら、次のコマンド1つでServiceのセレクターを更新します。
kubectl patch service app-service -p '{"spec":{"selector":{"version":"v2"}}}'
もし新バージョンでエラーが発生し始めたら、5秒以内にv1に戻すことができます。これは、標準のローリングアップデートでは決して真似できないセーフティネットとなります。
カナリアデプロイ:実環境でのテスト
ブルー/グリーンは大きな切り替えに適していますが、カナリアデプロイは段階的な露出を目的としています。トラフィックのごく一部(例えば2%や5%)を新しいバージョンにルーティングします。メトリクスを監視し、すべてが正常であれば、徐々に水門を開いていきます。
カナリアデプロイの論理
カナリアは「サイレント」なバグを捕まえるために不可欠です。例えば、1,000人の同時接続ユーザーが発生して初めてトリガーされるような、緩やかなメモリリークやデータベース接続プールの枯渇といった問題です。「影響範囲(ブラストライジアス)」を制限することで、チームが調査している間、不具合を経験するのは一握りのユーザーだけで済みます。
実践:Nginx Ingressによるカナリア
単一のService内でPod의 比率を調整してカナリア構成を作ることもできますが、Nginx Ingressを使用する方がはるかにスマートです。アノテーションを介してトラフィックの割合を正確に制御できます。
まず、カナリアバージョンとそれ専用のサービス(app-v2-service)をデプロイします。次に、カナリア用のIngressオブジェクトを作成します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-v2-service
port:
number: 80
この設定では、Nginxはリクエストのちょうど10%を新しいコードに送信します。私たちは通常、10%の状態で1時間維持し、p99レイテンシを監視します。レイテンシがしきい値の200ms以下に保たれていれば、重みを50%、そして100%へと引き上げていきます。
本番環境から得た教訓
これらの戦略を6か月間運用して、ドキュメントにはあまり書かれていない3つの教訓を学びました。1つ目は、データベースのスキーマ変更が最大のボトルネックになるということです。もしバージョン2がバージョン1を壊すような形でテーブルを移行してしまったら、ブルー/グリーンのロールバック計画は無意味になります。マイグレーションは常に後方互換性を持つように設計してください。
2つ目は、オブザーバビリティ(可観測性)は妥協できないということです。カナリアデプロイを「目隠し」状態で進めることはできません。私たちはGrafanaダッシュボードを使用して、v1とv2の5xxエラー率を並べて比較しています。カナリア側のエラー率が0.1%を超えたら、即座にデプロイを中止します。
3つ目は、スティッキーセッション(Sticky Sessions)に注意することです。Ingressでセッションアフィニティを使用している場合、重みの設定に関係なく、ユーザーが特定のバージョンに固定されてしまうことがあります。成功を測定する方法を慎重に検討しないと、データが歪む可能性があります。
どちらを選択すべきか?
選択は目的によります:
- ブルー/グリーン:大規模なアーキテクチャの変更や、瞬時の「オール・オア・ナッシング」のリリースが必要な場合に使用します。一時的にリソース使用量が2倍になるためコストはかかりますが、非常に信頼性が高いです。
- カナリア:日常的な機能アップデートに使用します。全ユーザーをリスクにさらすことなく、インフラが実際の負荷にどう耐えるかを確認する最適な方法です。
まとめ
ブルー/グリーンおよびカナリア戦略への移行は、私たちのチーム文化を変えました。もはや金曜日の午後のデプロイを恐れることはありません。KubernetesのラベルとIngressのアノテーションを活用することで、「サイトメンテナンス中」のページが過去の遺物となるような成熟度に達することができます。初期設定には労力がかかりますが、それによって得られる安心感は、YAMLの一行一行に見合う価値があります。

