デプロイ戦略はアプリケーションの種類で決まる
ソフトウェア開発を始めたばかりの頃は、どのアプリケーションもだいたい同じに見える。コードを書き、実行し、誰かが使う。しかし、実際のユーザーが依存する本番環境にアプリケーションを届ける段階になると、アプリケーションごとにまったく異なる性質があることに気づく。そして、その違いがデプロイのすべてを決定づける。
ステートレスアプリケーション:シンプルで置き換え可能
天気予報APIを想像してほしい。都市名を受け取り、気温と湿度のデータを返す。各リクエストは独立している。アプリケーションは毎回新しいデータを取得し、ローカルに何も保存せず、誰が何を尋ねたかも覚えていない。
このようなアプリケーションはステートレスと呼ばれる。以前のリクエストを気にしない。各呼び出しはまっさらな状態から始まる。
ステートレスアプリケーションは最もデプロイが簡単だ。新しいバージョンで問題が起きたら、古いバージョンと入れ替えればよい。データを気にする必要はなく、ロールバックのためのマイグレーションも互換性の問題もない。ロールバックは数秒で完了する。インスタンスを増やして水平スケールもできる。インスタンスを停止するのも簡単だ。
リスクは限定的だ。何か問題が発生しても、影響は処理中のリクエストだけに留まる。データが失われることはなく、ユーザーのセッションが壊れることもない。
ステートフルアプリケーション:慎重で複雑
次に、映画館のチケット予約システムを考えてみよう。このアプリケーションは、どの座席が予約済みか、誰が購入したか、どのように支払われたかを保存する。すべてのトランザクションがシステムの状態を変更する。データベースが真実を保持し、アプリケーションコードはその真実と同期し続けなければならない。
これがステートフルアプリケーションだ。永続化されたデータに依存する。そして、そのことがデプロイのすべてを変える。
単純にバージョンを入れ替えることはできない。新しいバージョンがデータの読み書き方法を変更する場合、まずデータベーススキーマのマイグレーションが必要かもしれない。マイグレーションを実行した後に新しいコードにバグが見つかっても、ロールバックは簡単ではない。古いコードは新しいスキーマを理解できないかもしれない。データはすでに変換されているかもしれない。バックアップからの復元が必要になる可能性もあり、それには時間がかかり、最近のトランザクションが失われるリスクもある。
ステートフルアプリケーションには注意深い順序が必要だ。データベースのマイグレーション、新しいコードのデプロイ、すべてが正常に動作することを確認し、もし動作しなかった場合の計画を用意する。ロールバックが単一のコマンドで済むことはほとんどない。多くの場合、調整が必要な複数ステップの手順となる。
影響範囲のスペクトラム:内部ツール vs 公開サービス
アプリケーションは、障害が発生したときに引き起こす損害の大きさも異なる。
社内の5人だけが使う管理ダッシュボードを考えてみよう。10分間ダウンしても、誰かがイライラするかもしれないが、金銭的な損失はない。デプロイは単純で構わない。ある程度のダウンタイムは許容できる。変更をプッシュして後で問題を修正すればよい。
一方、決済ゲートウェイやECサイトのチェックアウトページを考えてみよう。毎秒何千ものユーザーがアクセスする。これが停止すると、トランザクションが失敗し、ユーザーからクレームが来て、収益が減少する。影響は即座に、かつ測定可能だ。
この影響の大きさの違いが、デプロイ時にどれだけ慎重になるべきかを決定する。影響が小さいアプリケーションは、最小限の手順で直接デプロイできる。影響が大きいアプリケーションには、段階的なロールアウト、監視、フィーチャーフラグ、明確なロールバック計画が必要だ。まず1台のサーバーにデプロイしてエラーを監視し、徐々にトラフィックを増やす。新しいバージョンを古いバージョンと並行してしばらく稼働させる。カナリアデプロイやブルーグリーン戦略を使うこともある。
次のフローチャートは、アプリケーションの性質とリスクレベルに適したデプロイ戦略を判断するのに役立つ。
プロセスのレベルはリスクのレベルに一致させるべきだ。すべてのアプリケーションに、承認やランブックを伴う何時間ものデプロイパイプラインが必要なわけではない。しかし、実際に損害を引き起こす可能性のあるアプリケーションには、それに見合った注意を払う価値がある。
依存関係:隠れた結合
一部のアプリケーションは自己完結型だ。他に何も必要とせずに動作する。デプロイすれば、すぐに動く。
ほとんどのアプリケーションはそうではない。データベース、外部API、メッセージキュー、その他の内部サービスに依存している。新しいバージョンのアプリケーションが、まだ更新されていないAPIの新しいエンドポイントに依存するかもしれない。古いAPIが提供しない特定のレスポンス形式を期待するかもしれない。まだ実行されていないデータベースマイグレーションが必要かもしれない。
依存関係は結合を生み出す。新しいバージョンをデプロイするときは、依存するすべてのものが利用可能で互換性があることを確認しなければならない。これは単なる稼働時間の問題ではない。契約の互換性の問題だ。新しいコードは単体では完璧に動作しても、データベーススキーマが変更されたり、APIのレスポンスが変わったり、メッセージ形式が変更されたりすると、本番環境で失敗する可能性がある。
これがデプロイの順序が重要である理由だ。アプリケーションがデータベースに依存する場合、通常はデータベースのマイグレーションが最初に行われる。別のサービスに依存する場合、そのサービスを先にデプロイするか、少なくとも後方互換性を確保する必要がある。互換性を保証できない場合は、新旧両方のバージョンが同時に動作することを想定したデプロイを設計する必要がある。
これがCI/CDパイプラインに与える意味
これらすべての違い——ステートレス vs ステートフル、影響が小さい vs 大きい、独立 vs 依存——が、CI/CDパイプラインの構築方法を形作る。
すべてのアプリケーションに有効な単一のデプロイ戦略は存在しない。ステートレスなマイクロサービス向けに設計されたパイプラインは、ステートフルなモノリスでは失敗する。内部ツール向けに構築されたデプロイプロセスは、顧客向けの決済システムにはリスクが高すぎる。自己完結型アプリケーションで機能するロールバック計画は、マイグレーション済みのデータベースに依存するアプリケーションでは破綻する。
パイプラインは、それが提供するアプリケーションの性質を反映しなければならない。つまり:
- ステートレスアプリケーションは、高速ロールバックが可能なシンプルなパイプラインを使用できる。
- ステートフルアプリケーションには、注意深いマイグレーション手順とテスト済みのロールバック手順が必要。
- 影響が大きいアプリケーションには、段階的なデプロイと監視ゲートが必要。
- 依存関係のあるアプリケーションには、互換性チェックと順序付けられたデプロイが必要。
アプリケーションのデプロイ要件を評価するための実践的チェックリスト
デプロイパイプラインを設計または調整する前に、このチェックリストを確認しよう。
- アプリケーションはデプロイ後も存続すべきデータを保存するか?「はい」の場合、データベースマイグレーションとリバーシブルなスキーマ変更を計画する。
- アプリケーションがダウンした場合、影響を受けるユーザーは何人か?数が多い、または金銭的影響が大きい場合は、段階的ロールアウトと監視を追加する。
- アプリケーションは他のサービスやデータベースに依存しているか?「はい」の場合、互換性を確認し、デプロイ順序を計画する。
- ロールバックは古いバージョンに入れ替えるだけで済むか、それともデータ変更を元に戻す必要があるか?後者の場合、必要になる前にロールバック手順をテストする。
- アプリケーションは2つのバージョンを同時に実行できるか?できない場合、ダウンタイムまたはブルーグリーンデプロイパターンが必要になるかもしれない。
具体的な結論
アプリケーションのデプロイ方法は、プログラミング言語やフレームワーク、使用するツールによって決まるわけではない。アプリケーションの性質——状態を保持するかどうか、障害が引き起こす損害の大きさ、何に依存しているか——によって決まる。まずその3つを理解しよう。それからパイプラインを設計する。それ以外はすべて実装の詳細だ。