ちょっと話題の記事

スティッキーセッションを使っていなければApplication Load Balancer障害に耐えれたかも??? Amazon EC2をステートレスにする為にやるべきこと

セッション管理が必要なWebアプリケーションを使う場合でも、スティッキーセッションを利用しない方法を説明します。また、ログをインスタンス内に保持しない方法やAuto Scaling化についても触れています。
2019.08.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

おはようございます、加藤です。煽り気味なタイトルで申し訳ございません、念の為より詳細に記載しますが、スティッキーセッションを使っていなければApplication Load Balancer障害の影響を受けるのを防げたかもしれないという内容です。
今後同様の障害への対処として、このブログの対応は行う価値がありますが、これだけやっておけばOKという事では無い事をご理解ください。

2019年8月23日にAWSの東京リージョンで障害で特にAmazon EC2とAmazon EBSに、また特定条件下の環境のApplication Load Balancerの一部に障害が発生しました。
事象概要を読むと、気になる所がありました。

複数のアベイラビリティゾーンで稼働していたお客様のアプリケーションにも、予期せぬ影響(例えば、 Application Load Balancer を AWS Web Application Firewall やスティッキーセッションと組み合わせてご利用しているお客様の一部で、想定されるより高い割合でリクエストが Internal Server Error を返す)があったことを AWS では確認しております。
Summary of the Amazon EC2 Issues in the Asia Pacific (Tokyo) Region (AP-NORTHEAST-1)

スティッキーセッションを利用している場合は、Multi-AZ構成だとしてもApplication Load Balancer(以降、ALB)からHTTPステータスコード5XXが返却される事があった模様です。

本ブログでは、スティッキーセッションを利用しない為にはどうすれば良いのか、更に踏み込んでEC2をステートレスにするにはどうしたら良いか解説します。
流し読みしたい方は、画像とここまでの内容をまとめます。という箇所の箇条書きを見て頂ければ大まかには理解できると思います。

ステートレスとは何か

ステートレスとは、現在の状態を持たない事を意味します。
私達は普段、逆に現在の状態を持つステーフルな世界で生活しています。下図のハンバーガーショップで注文をする例をご確認ください。

店員は客からの注文を記憶し、更に必要な情報を確認する事で注文に必要な情報を完成させます。

店員がステートレスならば、下図の様になります。

店員は直前の客の発言、つまりは状態を記憶していません。不足している情報が何かは教えてくれますが、一度の発言で客が注文に必要な情報を伝える必要があります。

HTTPはステートレスなプロトコルなので、コンテンツが更新されない限りは、いつ誰がアクセスしても同じ内容が表示されます。

ここまでの内容をまとめます。

  • ステートレスとは現在の状態を持たない事
  • 状態を持たないので継ぎ足しで要求を受ける事ができず一度に必須の情報を全て受け取る必要がある
  • HTTPはステートレスプロトコルである

スティッキーセッションとは何か

ALB配下にあるEC2インスタンスに対してリクエストを行うと、ラウンドロビンで割り振りが行われます。2つのインスタンスの場合は交互にレスポンスを担当するという事です。

ALBのスティッキーセッションは、アクセスするユーザー毎に割り振りを固定する機能です。

Cookieに割り振り先を記録する事でALBはユーザー毎に同じ対象にアクセスさせ続ける事ができます。Cookieはブラウザに保存されるので正確にはブラウザ毎です、同じユーザーでもChromeとFirefoxを起動してアクセスすれば別々に割り振られる可能性があります。

また、Cookieは暗号化されており、ユーザーは内容の確認・改変を行えません。

割り振りを固定する期限を1秒〜7日の範囲で設定する事ができます。期限が切れた後は初回アクセスと同様に処理が行われます。この期限はアクセスがあるたびに更新されます。(最後のアクセスからN時間有効という設定となる)

スティッキーセッションはAWS専門用語では無く、ロードバランサー持つ一般的な機能の名称です。

ここまでの内容をまとめます。

  • スティッキーセッションを使うとユーザー(ブラウザ)毎に割り振りを固定する事ができる
  • 1秒〜7日の範囲で有効期限を設定できる
  • 割り振り先はブラウザのCookieに暗号化された状態で保存される

Application Load Balancer のターゲットグループ - Elastic Load Balancing #スティッキーセッション

HTTPをステートフルに扱う為には

買い物カゴがあるようなECサイトはまさにステートフルですね、HTTPをステートフルに扱う為の方法として、セッション管理があります。

セッション管理とは、複数のHTTP通信を1連の処理として管理する事です。
初回アクセス時にセッションIDを発行し、Cookieへ格納してレスポンスします。Cookieは自動的に送信されるので以降のアクセスではセッションIDが自動的に伝えられます。
Webアプリケーションがセッションストアを持ち、その中にセッションと買い物カゴに入れた商品を記録する事で誰が、何の商品を買物カゴに追加しているかという状態を持つ事ができます。

Cookieに情報を保存できるなら、買い物カゴの情報もCookieに保存すれば良いと思われるかもしれません。しかし、セキュリティ上の問題を除いたとしても、以下の問題があります。

  • Cookie1つのサイズ上限が厳しい(Chromeの場合、4096byte)
  • 1サイトで複数のCookieを渡す事も可能だが上限があり結合処理が必要など現実的ではない

ここまでの内容をまとめます。

  • セッション管理を行う事でHTTPをステートフルに扱える
  • CookieにはセッションIDのみ保持し内容はサーバーサイドで保持する

なぜスティッキーセッションが必要なのか

前述のHTTPをステートフルに扱う為にはではセッションストアはサーバーサイドのインスタンスの中に保持されていました。
この構成のまま、スティッキーセッション無しにALBを利用すると問題が発生します。セッションID発行後に、リクエストした際にセッションIDに紐づく情報をインスタンスが持っていない可能性があるのです。

ここでスティッキーセッションが、あればアクセス先が固定されるので、紐づく情報が無いという問題は発生しません。

セッションには期限を持たせる事が多く、スティッキーセッションの期限をこれより短くするとセッションの途中でアクセス先がランダムになってしまいます。よってスティッキーセッションの期限はセッションの期限より長くする必要があります。

また、スティッキーセッションを使ったMulti-AZ構成に信頼性向上を期待する場合、ひとつ落とし穴があります。それはセッションレベルでは信頼性が向上しない事です。
セッション有効期限内にEC2インスタンスに障害が発生した場合、ALBはヘルスチェックにより検知して障害中のインスタンスへは振り分けを行いません。よって別のインスタンスへ振り分けが行われます。この場合、セッションストア内に情報は存在しないので、確定していない操作は全て失われます。

Auto Scalingを設定している場合に、スケールアウトしたとしてもそれ以前からアクセスしているユーザーはスティッキーセッションによって同じインスタンスにアクセスし続け、新しくアクセスしたユーザーもラウンドロビンによって割り当てが行われるので、負荷が掛かっているインスタンスにアクセスする可能性があります。この為、スケールアウトが発生しているのに負荷が偏ったままという問題が発生します。

ここまでの内容をまとめます。

  • セッションストアをインスタンス内に持つ場合はALBを使うと正常にセッション管理を行えない
  • スティッキーセッションを使うことでセッション管理されたWebアプリケーションでもALBを使える
  • セッション期限 < スティッキーセッション期限 とする必要がある
  • スティッキーセッションを使っている場合Multi-AZしてもセッションレベルの信頼性は向上しない

スティッキーセッションを使わずセッション管理を行うには

スティッキーセッションを使わずにセッション管理を行うにはセッションストアをEC2インスタンスの外に保持すれば良いです。
AWSにはこの為のサービスとして、Amazon ElastiCacheがあります。ElastiCacheはマネージド型、Redis、またはMemcached互換のインメモリデータストアを提供するサービスです。

Webアプリケーションに変更が必要な事に抵抗を感じる方が居るかも知れません。しかし、モダンなWebアプリケーションフレームワークであれば、セッションストアとしてRedisやMemcachedをサポートしている事が多いのでまずは確認して見てください。設定変更だけで対応できる可能性があります。

マネージドサービスとはいえ、構成リソースが増えるので、いきなり導入するのではなくコスト影響やどの様に運用していくか、障害検知の方法などはしっかりと検討しておきましょう。
ElastiCacheに障害が発生するとEC2インスタンスが正常であったとしてもセッションストアが使えずシステム障害となる事をしっかりと認識しておいてください。

ここまでの内容をまとめます。

  • セッションストアをEC2インスタンス外に持てばEC2インスタンスはセッション管理が必要でもステートレス化できる
  • AWSにはElastiCacheというサービスがありセッションストアの用途に適している
  • セッションストアを担当するElastiCacheに障害が起きるとシステム全体の障害となるので対策はしっかり行う

ログを外部に出力する

セッションストアをEC2インスタンス外に持てれば、Webアプリケーションの機能としてはステートレスになります。しかし、インスタンスの中にOS・ミドルウェア・アプリケーションのログが残ったままです。
1つセッションの中でインスタンスがリクエスト毎に変わるので、インスタンス内に保持したままだと全てのインスタンスにアクセスしてログをDLして結合といった処理を行わないといけません。
そもそも、ログという状態を各インスタンスが個別に持ってしまい、運用を行う為にはログは必須です。よって、このままでは、インスタンスという単位で考えたときにはステートレスではありません。

AWSでこの問題を解決するには、Amazon CloudWatchのLogs機能を使うと良いでしょう。CloudWatchエージェントをインスタンスにインストールする事で簡単にログを外部に持つ事が可能です。
CloudWatchにはMetrics機能があり、CPU使用率などを記録しています、CloudWatchエージェントをインストールしている場合は、設定すればメモリ使用率やストレージ使用率などより詳細にメトリクスを収集する事が可能です。

CloudWatch Logsの利用方法については、下記のブログが参考になります。

CloudWatch AgentをEC2にログインせずに設定してみた

Cloudwatch Logsの複数ロググループ参照に便利なツール「utern」

EC2インスタンス内のログをCloudWatch LogsとS3バケットに保存してみた

Amazon CloudWatch Events のログを Amazon CloudWatch Logs Insights で検索してみる!

Amazon CloudWatchでWindowsのプロセス監視をしてみた

もちろん既存で、DatadogやSyslogサーバーを利用している場合は、そちらを使って頂き問題ありません。大事なのはEC2インスタンス内にしかログが無いという状態を作らない事です。

ここまでの内容をまとめます。

  • EC2インスタンスのステートレス化をすると1つのセッションでもログが複数インスタンスに分散してします
  • AWSのCloudWatch LogsやDatadogなどを使い外部にログを転送すべき
  • EC2インスタンス全体で考えるとログも状態なので完全にステートレス化するならログ転送する必要がある

その他ステートレス化に必要な対応

社内より指摘を貰い追記(2019/08/29)

セッションやログ以外にも、EC2インスタンスをステートフルにさせてしまう物があります。例えばファイルアップロードです。
クライアントからファイルをアップロードし、後ほどファイルを読み込んで使用する場合、アップロードされたファイルは1つのEC2インスタンスにしか存在しないので、別のEC2インスタンスにアクセスして場合は正常に動作しません。この場合は受け取ったファイルをAmazon S3にアップロードしS3バケット名・キー名を永続ストレージ(Amazon Aurora/RDSやAmazon DynamoDB)に保存すべきでしょう。

やりたい事次第ですが、非同期で問題が無い処理ならば、S3へのアップロード指示までをEC2インスタンスで担当して、以降の処理はAWS Lambdaなど他のサービスにオフロードする事も検討すべきです。オフロードすれば、開始後にEC2インスタンスで障害が発生しても処理は継続されます。

ここまでの内容をまとめます。

  • ファイルアップロード処理でEC2インスタンス内にデータを保持するとステートフルになってしまう
  • キーの保存場所としてAurora/RDSやDynamoDBをバリューの保存場所としてS3を利用するとEC2インスタンス内に保持することを回避しステートレスを維持できる
  • 非同期で問題無い処理ならばオフロードを検討すると良い

Auto Scaling化

ここまでの事が行えていれば、もう少し手を加える事でAuto Scalingを使う事が可能になります。
Webアプリケーションのデプロイ、ログの外部転送が設定済みのEC2インスタンスをAMI化すれば、そのAMIを使ってAuto Scalingが利用可能です。

AWS再入門2018 Amazon EC2 Auto Scaling編

脆弱性は日々発見され続けていますので、定期的にAMIを更新しAuto Scalingに設定変更を行う運用を必ず取り入れましょう。
テスト用の環境を作り、そこで更新したAMIベースにEC2インスタンスをデプロイし、E2Eテストを行いOKなら、本番環境へデプロイといった感じです。

EC2インスタンスがステートフルな状態からいきなりここまで目指すのは大変ですが、ステートレス化が出来た後には是非取り組んで頂きたいです。EC2インスタンスに障害が発生した際に自動復旧(Auto Healing)が行われますし、適切にトリガーを設定していれば、アクセスが増えたとしても自動でスケールアウトが行われエンドユーザーにレスポンス低下などの不満を与える事が回避できます。
ここまでの内容をまとめます。

  • EC2インスタンスのステートレス化ができればAuto Scalingを行う前提条件はほぼ満たしている
  • Auto Scalingを行う事でEC2インスタンス障害時に自動復旧や負荷に応じてスケールアウトなど恩恵を受けられる
  • 脆弱性は日々発見されるのでAMIの更新は怠らない

あとがき

脱スティッキーセッションから始まりAuto Scaling化する方法までを説明させて頂きました。
煽り気味なタイトルにしたからには、しっかりと内容を書こうと思い、ぎっちり詰め込んで見ました。
また、システムの信頼性を高める事は基本的には良い事ですが、エンジニアの人件費やAWSの利用費など何らかの形でコストも上がる場合が多いのでご注意ください。例えば特定部署の社員が月に1度しか使わないようなシステムで今回のような対応をするのは明らかにやりすぎですね。システムによって得られる利益・抑えられる支出をしっかりと考えた上で信頼性の高いシステムを構築しましょう、もしくは経営層と議論して求めるレベルを合意しましょう。
以上でした!