基本的なデプロイメント方式

Big Bang Deployment

昔のソフトウェア配布

  • アプリケーションの全体または一部を一度に更新する
  • 典型的なウォーターフォール型開発でのデプロイメント方法
  • ロールバックが不可能または非実用的
  • リリース前に大規模な開発とテストを実施する必要がある
  • クライアント側のインストールを更新するため、クライアント側のアクションが必要
  • デスクトップアプリケーションなどのベンダーパッケージソリューションに向け

Build Deployment

CI/CDパイプラインでビルドしてデプロイする方式

  • CI/CDパイプラインでアプリをビルドしてデプロイする
  • デプロイ時にサービス停止する

Basic Deployment

1つのバージョンでデプロイする方式

  • 全ノードに1バージョンで同時に更新する
  • Simple & Fast
  • ハイリスク
  • 停止が発生する
  • ロールバックが遅い

Multi-Service Deployment

複数のバージョンをデプロイする方式

  • 全ノードに複数バージョンを配置する
  • Simple & Fast
  • Basic Deploymentよりはリスクが低い
  • 停止が発生する
  • ロールバックが遅い

Zero Downtime Deploymentが可能なデプロイメント方式

Rolling Deployment, Rolling Update

インスタンスを順次入れ替えるシンプルな方式。
多くのImmutable Infrastructureの標準の(簡単な)デプロイ方式として実装されている。

  • 全ノードは1バージョン
  • one-by-one(あるいはN batches)で新しいサービスに置き換える
  • Simple
  • ロールバックが比較的簡単
  • 新旧混在するのでアプリケーションやデータベースが混在環境を許容できる必要がある
  • トランザクションの喪失、ログオフされたユーザの考慮が必要

Blue/Green Deployment, Red-Black Deployment, A/B Deployment

平行稼働した新旧インスタンス群を一度に切り替える方式。

  • Blue(Staging)とGreen(Production)の2つの環境を用意し、切り替える
  • ロードバランサーで切り替える
  • 展開が成功した後に旧環境を廃止する方式を一部ベンダーはRed/Black Deploymentと呼称している
  • ダウンタイムなし
  • Simple & Fast
  • ロールバックが早い(Rapid rollback/flip traffick back)
  • 失敗(SPOF or outage)した場合、ロールバックするまで影響は大きい
  • 既存のトランザクションとセッションは失われる
  • データベースの互換性

Canary Deployment, Linear Canary Deployment

平行稼働した新旧インスタンス群を一定割合先行して公開したのちに、残りを一度に切り替える方式。
Linear Canary Deploymentは新しいインスタンス群の割合を順次増加させていく方式。

  • 全ノードは1バージョン
  • 一部ノードを新バージョンにし徐々に割合を増やす
  • 現在、アプリケーション/サービスの展開で一般的な方法
  • 段階的(2%, 10%, 25%, 50%, 75%, 100%.. など)
  • 失敗時のリスクが低い
  • 実行環境を重複して用意する必要がないのでBlue/Greenより安価
  • 高速かつ安全なロールバック
  • スクリプトによるカナリア展開は複雑
  • データベースの互換性

A/B Testing

A/B Testingは行動監視の方式であって、デプロイメント方式ではない。

  • 異なるバージョンを一定期間実験的に本番環境で実行する
  • A/B Testingツールを使用する
  • ユーザトラフィックはルール/統計に基づいて異なるバージョン(実験)ごとにルーティング
  • 実験終了後、最適と判断されたサービスバージョンで更新する
  • 実験による不具合(アプリ、サービス、ユーザエクスペリエンス)
  • ABテストのスクリプトが難しい
  • データベースの互換性

Zero Downtime Deploymentのために考えること

下位互換性のあるデータベーススキーマ / メッセージスキーマ

平行稼働やロールバックのために互換性が必要。

  • アプリケーションのアップグレードからデータベースの移行を切り離すので下位互換性を考えないといけない
  • プロセス間でメッセージが送信されている場合、メッセージスキーマも考慮しなくてはならない
  • 下位互換のない変更を安全に行う方法(ParallelChange/expand and contract

wind-down period

切替タイミングでの稼働中のタスクの問題。

  • アプリケーションが実行時間の長いタスクを処理する場合、これ以上タスクを実行できないようにする

ユーザセッションの損失を軽減する

Immutable Infrastructureとして構成する必要がある。

  • セッションをデータストアに永続化する
  • アプリケーション間でセッションを共有する
  • ステートレスバックエンドにする
  • セッションアフィニティを使用して、すべての新しいセッションをアップグレードされたバックエンドに転送する

ロードバランサーを適切に利用する

接続中のコネクションの排出(Draining)を考慮する必要がある。

  • シャットダウンする前にトラフィックをバックエンドから排出(Draining)する
  • 新しく再起動したバックエンドに適切に再度有効にする

Long live the connection

とくにWebSocketやHTTP long pollingの場合は排出(Draining)のタイムアウトを超え強制終了されるかもしれない。
通知してアプリケーション側で正常な終了プロセスを行う方法もある。

This draining approach works great for the majority of cases. But every once in a while, you need a little more control over the shutdown process — especially when you have long-lived sessions, such as WebSockets or HTTP long polling.
The problem with long-lived WebSocket sessions is that the Application Load Balancer (ALB) starts to forcibly kill all “still active” connections once the draining period has elapsed. By the time the application receives a SIGTERM, the long-lived connections may have already been terminated by the ALB. This makes it impossible to close them gracefully on the application side.
This doesn’t have to be a big problem. The long-lived WebSockets will be closed abnormally — which would lead to 1006 Abnormal Closure code on the server and the client. But that’s all. Clients will reconnect to another healthy task and continue to operate normally.