テクノロジー&エンジニアリングセンター Lerna チームの杉本です。 クラウド環境で高い可用性とスループットを実現するための検証や開発を担当しています。

本エントリではAmazon Elastic Container Service(以降、ECS)上でAkka Clusterを稼働させるために実施した検証結果を公開します。


想定読者


背景 (再掲)

私たちは高可用ソフトウェアスタック「Lerna」を開発しています。LernaではAmazon Elastic Compute Cloud (Amazon EC2)上にAkka Clusterを用いたアプリケーションをデプロイする方法を提供しています。しかし、EC2の運用にかかるコストを削減する目的で、ECS上にアプリケーションをデプロイしたいという声がありました。

Lernaはプラットフォームに依存せず、高い可用性を実現するシステムを構築できることを価値のひとつとしています。ECSをLernaのサポートプラットフォームに加えるため、私たちはAkka Clusterを用いたアプリケーションをECS上で稼働させられるのか検証することにしました。

私たちのこの活動を次の2回に分けて紹介します。本エントリはその2回目です。

  1. ECSでAkka Clusterを用いたアプリケーションを稼働させる方式 (方式編のエントリ)
  2. Akka Clusterを用いたアプリケーションを実際にECSへデプロイし検証した結果(本エントリで紹介する内容

運用コストを削減するという目的に沿うよう、できるだけAWS(Amazon Web Services)のマネージドサービスを用いて検証環境を構成します。


検証環境のシステム構成

環境やアプリケーションは 方式編のエントリ にもとづき構成しています。

3つのAvailability Zone (AZ) でそれぞれコンテナを動かしClusterを組む構成です。


検証シナリオ

高い可用性を目標としているため、次の観点で検証します。

  • 無停止でローリングアップデートできるか
  • レジリエンス(障害から立ち直る能力)があるか

具体的には、検証シナリオを次のように設定します。

  • ローリングアップデート中に発生するサーバーエラーを確認する
  • コンテナを強制停止した後、自動で元の状態に回復するか確認する
  • AZ間の通信ができなくなったときに一貫性を維持しながら機能が回復するか確認する

ローリングアップデート中に発生するサーバーエラーを確認する

ローリングアップデート中にサーバーエラーが発生するのか、それともエラーが発生せずにゼロダウンタイムのローリングアップデートが実現できているのかを確認します。

5xx系のサーバーエラーがレスポンスされた場合に加え、レスポンスがタイムアウトしたケースもサーバーエラーとみなします。

ローリングアップデートは、サービス使用するタスク定義のリビジョンを更新して実施します。 新しいバージョンのアプリケーションを1台追加し、追加し終わった後に1台シャットダウンするという処理を繰り返すように設定します。

  • タスクの数: 3
  • 最小ヘルス率: 100
  • 最大率(最大ヘルス率): 134

最大率(最大ヘルス率)は、タスク数を N として 100 * (N + 1) / N の切り上げで計算すると、ローリングアップデート時に通常時よりも最大で1台だけ追加することを許可できます。 N = 3 の場合、 100 * 4 / 3 = 133.3・・・ の切り上げで 134 になります。

参考: サービスの更新 – Amazon Elastic Container Service

コンテナを強制停止した後、自動で元の状態に回復するか確認する

コンテナ内のアプリケーションに異常が生じたケースを模擬し、コンテナが突然停止してしまった場合に自動的に元の状態に戻るかを確認します。

一箇所でのみ障害発生することを想定し、同時多発的に異常が生じることは想定しません。

通常のタスク停止だとCoordinated ShutdownによってGracefulに停止します。 そこで、タスク定義の停止タイムアウト(ECS_CONTAINER_STOP_TIMEOUT)を短い値(3 秒)に設定して強制終了となるようにします。

AZ 間の通信ができなくなったときに一貫性を維持しながら機能が回復するか確認する

マルチAZで構成したECSのAZ間のネットワークを模擬的に遮断し、Split Brain Resolverが働くことで一貫性を維持しながら機能を回復できるかを確認します。

3つあるAZのうち、1AZが他の2AZと通信できなくなることを想定します。

ネットワーク分断障害は ネットワーク ACL を使って特定のサブネット(AZ) が他のサブネットと通信できないようにして模擬します。

検証結果

検証環境で検証シナリオを実施した結果、どのような結果が確認できたのかを示します。

これらの検証では秒間2回のリクエストを送り続けることでサーバーエラーが発生する時間帯の有無を確認しています。

ローリングアップデート中に発生するサーバーエラーを確認する

ローリングアップデート中にはエラーは発生しませんでした。

これは アプリケーションの起動・停止シーケンスの調整 によって実現されています。

アプリケーションがHTTPの受付を停止する前にNetwork Load Balancerから切り離すことでエラーになることを回避しています。 また、Akka Clusterからの離脱時はCoordinated Shutdownによって処理が途中で止まることを回避しています。

コンテナを強制停止した後、自動で元の状態に回復するか確認する

コンテナを強制停止しても自動復旧しました。 コンテナの停止から再起動までの所要時間は約30秒でした。

1秒間に2リクエストを送信し続けている状況で十数件 503 レスポンスとなりました。 これは、 Network Load Balancerがヘルスチェック失敗によりアプリコンテナを切り離すまでに時間がかかっているからだと考えられます。

AZ 間の通信ができなくなったときに一貫性を維持しながら機能が回復するか確認する

Split Brain Resolverが発動し、孤立したnodeは除外され、一貫性は維持されました。

1秒間に2リクエストを送信し続けている状況で数件 503 レスポンスとなりました。 これは、 Network Load Balancerがヘルスチェック失敗によりアプリコンテナを切り離すまでに時間がかかっているからだと考えられます。

また、通常100msオーダーのレスポンスが数秒に増えるケースがありました。 これは以下の理由からAWS内部の通信経路のどこかでネットワーク障害起因のリトライが発生していると考えています。

  • Split Brain Resolverが発動した後(※ ネットワーク障害は継続中)も発生している
  • API Gatewayにリクエストが届いた時刻とアプリケーションにリクエストが届いた時刻に数秒の差が存在する

検証結果まとめ

ローリングアップデートではゼロダウンタイムを実現できました。

障害発生時のダウンタイムは、机上の計算によると仕組み上Lernaのベンチマークほどの性能を達成できないことがわかりました。 なお、今回の検証では障害発生時のダウンタイム計測をスコープに含めなかったため、実測はできていません。

2021/10/15現在、Lernaは150 TPS下で次の停止時間(リクエストが再び全て成功するまでにかかる時間)を達成しています。

  • 単一AZ障害による停止時間: 8.02秒
  • 単一アプリケーションプロセスの障害による停止時間: 5.92秒

しかし、本エントリで紹介している検証環境下においては単一アプリケーションプロセスの障害によりエラーになる可能性のある時間は最低でも10秒を超えることがわかっています。これは Network Load Balancerのヘルスチェック仕様 によるものです。

  • ヘルスチェック間隔は最小で10秒ごと
  • 2回以上失敗しないとunhealthy判定にならない