デプロイ戦略が復旧の難易度を決める

多くのチームは、何かが壊れた後で復旧方法を考え始める。ロールバックスクリプトを書き、旧バージョンのアーティファクトをバックアップとして保存し、使わないで済むことを願う。しかし実際には、復旧で最も難しいのはロールバックそのものではない。最初のデプロイの仕方が原因で、クリーンにロールバックできない状況に陥ったときこそが本当の難所だ。

次のシナリオを考えてみよう。チームが新しいバージョンを本番環境にプッシュする。すべてのサーバが一度に置き換えられる。旧バージョンは停止し、新バージョンが起動し、すべてのユーザが新しいコードを使うことになる。その後、モニタリングアラートが鳴り始める。何かがおかしい。選択肢はフルロールバックだけだ。すべてのサーバ、すべてのユーザ、すべての接続を元に戻す。中間状態は存在しない。壊れたバージョンに完全に乗るか、旧バージョンに完全に戻るかの二択だ。そしてロールバックを実行している間、すべてのユーザが問題を経験し続ける。

これと比較して、ブルーグリーンデプロイメントを採用しているチームを見てみよう。彼らは2つの同一環境を持っている。一方は稼働中でユーザにサービスを提供している。もう一方には新しいバージョンが準備されている。リリースのタイミングで、ブルー環境からグリーン環境へトラフィックを切り替えるだけだ。問題が発生したら、トラフィックを元に戻す。旧環境はまだ動いている。ロールバックは数秒で完了する。コード変更も、設定変更も、パイプラインの完了待ちも不要だ。

違いは、より優れたロールバックスクリプトを持っているかどうかではない。違いは、問題が発生する前に爆発半径を制限するデプロイ戦略を選択しているかどうかにある。

ブルーグリーンデプロイメントで復旧がほぼ瞬時に

ブルーグリーンデプロイメントは、単なるゼロダウンタイムリリースのための派手なパターンではない。それは、デプロイ戦略に偽装した復旧メカニズムだ。重要な洞察は、新しい環境が正常に動作することを確認できるまで、旧環境を稼働させ続けることにある。新しいバージョンが失敗した場合、何かを再構築する必要はない。トラフィックを旧環境に戻すだけでよい。

これは、環境が単なるコンピュートと設定で構成されるステートレスアプリケーションにはうまく機能する。しかし、データベーススキーマの変更が伴うと、話は複雑になる。新しいバージョンがデータベース構造を変更するマイグレーションを実行した場合、旧バージョンは新しいスキーマに対して動作しない可能性がある。その場合、トラフィックを戻すだけでは不十分だ。データベースのマイグレーションも元に戻す必要があり、それには時間がかかり、独自のリスクも伴う。

以下は、切り替えを可能にする最小限のKubernetes Service設定だ。セレクタはアクティブな環境を指しており、それをblueからgreen(またはその逆)に変更することで、すべてのトラフィックが即座にルーティングされる。

apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  selector:
    app: my-app
    environment: blue   # ロールバックするには 'green' に切り替え
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

新しいバージョンが失敗した場合、セレクタを environment: green に更新して適用する。再ビルドもパイプライン待ちも不要で、単一フィールドの更新だけで完了する。

実用的な教訓は、ブルーグリーンデプロイメントは高速なロールバックパスを提供するが、それは旧環境を現在のデータベース状態と互換性のある状態に保っている場合に限られる、ということだ。それが保証できない場合、ロールバックは瞬時にはならない。ゼロから再構築するよりは速いが、それだけだ。

カナリアデプロイメントで被害を受けるユーザ数を制限する

カナリアデプロイメントは異なるアプローチを取る。すべてのトラフィックを一度に切り替える代わりに、新しいバージョンにユーザのごく一部(例えば5%)を送る。そのグループに問題がなければ、徐々に割合を増やし、最終的に全ユーザが新しいバージョンを使うようにする。

ここでの復旧上の利点は明らかだ。新しいバージョンに問題があっても、影響を受けるのは5%のユーザだけだ。カナリアを停止し、そのユーザを旧バージョンに戻し、すべてを即座に修正しなければならないというプレッシャーなしに問題を調査できる。爆発半径は設計上小さく抑えられている。

カナリアデプロイメントは、小さなグループで問題を検出できる十分なトラフィックがあり、インフラが2つのバージョンを並行して実行できる場合にうまく機能する。また、優れた可観測性も必要だ。カナリアグループとコントロールグループの間で、エラー率、レイテンシ、ユーザ行動を比較できなければならない。それがなければ、新しいバージョンをさらに展開しても安全かどうかは推測でしかない。

トレードオフは複雑さだ。トラフィックルーティングロジック、グループを比較できるモニタリング、カナリアの割合をいつ増やすかを決定するプロセスが必要になる。しかし、頻繁にリリースし、フルロールバックを許容できないチームにとっては、その複雑さに見合う価値がある。

フィーチャーフラグでデプロイなしに復旧する

フィーチャーフラグは他の戦略とは異なる動作をする。誰が新しい機能を見るかを制御するために新しいバージョンをデプロイする代わりに、新しいコードを全員にデプロイするが、スイッチの背後に隠しておく。コードはすでに本番環境にある。ただ、まだユーザに対して有効になっていないだけだ。スイッチを小さなグループに対してオンにし、結果を監視し、徐々にオーディエンスを拡大する。

問題が発生したら、スイッチをオフにする。ロールバックも、ホットフィックスも、パイプライン待ちも不要だ。単一の設定変更で復旧は数秒で完了する。これは爆発半径を制限する最も外科的なアプローチだ。まず内部ユーザに対して機能を有効にし、次にベータユーザ、次に本番トラフィックの一部、そして最後に全員、というように進められる。どの時点でも、機能を即座に無効化できる。

フィーチャーフラグは強力だが、それなりのコストがかかる。フラグを管理するシステム、古いフラグをクリーンアップするプロセス、フラグの乱立を防ぐ規律が必要だ。コード内のすべてのフラグは条件ロジックを追加し、テストを難しくする。機能が安定した後にフラグを削除しなければ、コードベースはデッドブランチの迷宮と化す。

フィーチャーフラグの真の価値は、より速くリリースすることではない。デプロイをまったく必要としない復旧メカニズムを持てることにある。それがすべてのリリースのリスクプロファイルを変える。

デプロイ戦略こそが復旧計画である

ここで、すべてを結びつける核となる考え方を示す。デプロイ戦略と復旧計画は切り離せない。新しいバージョンをどのように展開するかという決定が、問題発生時にどのような復旧オプションを持てるかを決定する。

以下の図は、各戦略が障害を検出から復旧までどのように処理するかを示している。

flowchart TD A[新しいバージョンをデプロイ] --> B{デプロイ戦略は?} B -->|ブルーグリーン| C[新しい環境にトラフィックを切り替え] C --> D{新バージョンが失敗?} D -->|はい| E[旧環境にトラフィックを戻す] D -->|いいえ| F[新環境を稼働継続] B -->|カナリア| G[5%のトラフィックを新バージョンにルーティング] G --> H{カナリアでエラー?} H -->|はい| I[カナリアを停止、ユーザを戻す] H -->|いいえ| J[徐々にトラフィックを増加] B -->|フィーチャーフラグ| K[フラグで隠したコードをデプロイ] K --> L[小さなグループでフラグを有効化] L --> M{問題を検出?} M -->|はい| N[フラグを即座に無効化] M -->|いいえ| O[フラグのオーディエンスを拡大]

すべてのサーバを一度に置き換えるデプロイを行う場合、唯一の復旧オプションはフルロールバックだ。ブルーグリーンを使えば、瞬時に切り戻せる。カナリアを使えば、影響を受けるユーザ数を制限できる。フィーチャーフラグを使えば、デプロイパイプラインに触れることなく問題のある機能を無効化できる。

各戦略にはトレードオフがある。ブルーグリーンは2倍のインフラを必要とする。カナリアは優れたモニタリングとトラフィックルーティングを必要とする。フィーチャーフラグはフラグ管理システムとクリーンアップの規律を必要とする。しかし、これらはすべて、戦略がなくロールバックスクリプトが実際に動くことを祈るよりははるかに優れている。

最も速く復旧するチームは、最高のロールバックスクリプトを持っているチームではない。最初から復旧を容易にするようにデプロイプロセスを設計したチームなのだ。

次回のデプロイのためのクイックチェックリスト

  • コードや設定を変更せずにロールバックできますか?
  • 新しいバージョンが失敗した場合、何人のユーザが影響を受けますか?
  • すべてのユーザを公開せずに、本番環境で新しいバージョンをテストできますか?
  • 再デプロイせずに特定の機能を無効化する方法はありますか?
  • 旧環境はまだ稼働しており、現在のデータベースと互換性がありますか?

これらのほとんどに「いいえ」と答えた場合、次回の復旧は必要以上に困難なものになるだろう。

チームにとっての意味

次回デプロイを計画するときは、新しいバージョンをどうやって稼働させるかだけでなく、問題が発生した場合にどうやって停止させるかも考えよう。選択肢を与えてくれる戦略を選んでほしい。赤いアラートで埋め尽くされたダッシュボードの前に立っている未来の自分が、感謝することになる。