はじめに
MackerelによるELBの死活監視を導入した際、AutoScalingによりEC2がスケールアウトされる度に一時的にUnhealthy判定されてしまい都度アラート発報がされてしまう状態にあった。
このままだと対応が必要な通知が埋もれてしまう状態にあったため、一度調査してみることにした。
ELBのヘルスチェック
ひとまずELBのヘルスチェックについて整理する。
ELBのヘルスチェックとは、ELBが提供するテストに基づいて判断される。
対象となるものは、ALBとNLBのターゲットグループならびにCLB。
今回はALBのヘルスチェックにのみ触れることとする。
ALBのヘルスチェックについてはキャプチャのようなメニューの設定が可能。
Protocol
ヘルスチェックに使用するプロトコル。
余談だが現場ではHTTPSになっているのだが、ALBにSSL証明書を適用させておりSSL Terminationしているため、本来であればALB→EC2の通信はHTTPであるべきだと思っている。
そもそもALBのリスナーも443ポートで待ち受けているため、クライアント→ALB間もALB→EC2間もどちらもHTTPS通信になっており、ALBにSSL証明書を設定している意味がないような、、、
ちなみにSSL Terminationについては以下記事がわかりやすかったので載せておく。
Path
ヘルスチェックに使用するパス。
TOPページであったりヘルスチェック用の静的ファイルを使用することが多い。
ALBがヘルスチェックに使用するだけなので、200 OKが返せれば中身は何でも良い。(ユーザーの目には触れない)
Port
ヘルスチェックに使用するポート。
Protocolの部分で記載したように、今の現場の構成(ALBでSSL Terminationしている)であれば本来であれば80で良いと思っている。
Healthy threshold
インスタンスのヘルス状態を変更する前に、ヘルスチェックに合格しなければならない回数。
指定した回数分ヘルスチェックに成功するとHealthy判定される。
Unhealthy threshold
インスタンスのヘルス状態を変更する前に、ヘルスチェックに失敗しなければならない回数。
指定した回数分ヘルスチェックに失敗するとUnhealthy判定される。
Timeout
ヘルスチェックが失敗したとみなされる、応答がない状態が続く時間(秒単位)。
例えば、ターゲット側のセキュリティグループのホワイトリストにALBが登録されていない場合にタイムアウトとなる。
Interval
ヘルスチェックを行う間隔(秒)。
間隔はタイムアウトより大きくする必要がある。
Success codes
ヘルスチェック成功と判定するHTTPステータスコード。
先程以下のように記載したが、正確にいうとここで設定したステータスコードが返せれば良い。
200 OKが返せれば中身は何でも良い
例えば、ヘルスチェックパスにアクセスするとリダイレクトされる仕様である場合には301を設定する必要がある。
以下記事のような場合が該当する。
他にも、Apacheの設定でRewriteRuleを使用している場合なども該当。
発生した問題は?
ALBのヘルスチェックにおける各設定項目について整理した上で、改めて問題について考えてみる。
今回発生していた問題は、AutoScalingグループによるスケールアウト時にALBのヘルスチェックによりターゲットがUhealthyと判定されてしまうというもの。
まず考えられるのは、EC2インスタンスがトラフィックを受ける準備が整う前にトラフィックが送信されてしまっているということ。
ただし、AutoScalingグループ側の設定で、「ヘルスチェックの猶予期間※」を300秒としており、インスタンスの起動までに300秒以上もかかるとは思えなかった。
※ヘルスチェックの猶予期間
AutoScalingグループのヘルスチェックの対象に含めるまでの猶予期間。
例えば起動テンプレートのユーザーデータによる初期化には数分要するため、
この期間はヘルスチェックの対象外にすることでUnhealthy判定されることを回避できる。
実際、起動テンプレートを用いてスケールアウトしており、ユーザーデータで実行している処理が重たいのかとも考えたが、ただディレクトリの削除を実施しているのみのため関係ないと判断した。
そこで何気なくインスタンスがUnhealthy判定されたタイミングを確認してみると、スケールアウト開始から60秒後にUnhealthy判定されていることに気づいた。(以下キャプチャ参照)
AutoScalingグループの「ヘルスチェックの猶予期間」により、スケールアウト開始から300秒はヘルスチェック対象外となるはずなのになぜ、、、?
CloudWatchを確認すると「2023-07-21 09:01」にUnhealthyとなっている。
AutoScalingグループ側のアクティビティ履歴を確認すると、スケールアウトされたのは「2023-07-21-09:00:21」。
AutoScalingグループの「ヘルスチェックの猶予期間」だけでは制御できていないのでは?と思い調べてみた。
AutoScalingグループの「ヘルスチェックの猶予期間」
どうやらAutoScalingグループの「ヘルスチェック猶予期間」はあくまでAutoScalingグループの中だけで完結しており、ELBからのヘルスチェックは猶予期間内であっても実行されるようだ。
AutoScalingグループは、インスタンスのヘルスステータスをチェックする前に、ヘルスチェックの猶予期間が終了するまで待機する。
一方インスタンスやELBのヘルスチェックは、AutoScalingグループのヘルスチェックの猶予期間が終了する前に完了することができる。
そのため、今回のように猶予期間を待たずしてELBのヘルスチェックが走り、まだトラフィックを受ける準備が整っていない状態のインスタンスへのヘルスチェックが走り、Unhealthyと判定されていたのである。
対応策
対応策は至って簡単で、ALB側のヘルスチェックの設定を1箇所変えるのみ。
今回は上述の「Unhealthy threshold」の値を「2」から「5」に変更しただけで解決した(細かい検証やチューニングはしていない)。
何をしたかというと、元々「Unhealthy threshold」が「2」で「Interval」が「30」となっていたので、ターゲットの起動から30秒ごとにヘルスチェックを実行し、連続で2回ヘルスチェックに失敗したらUnhealthyと判定するようになっていたところを、5回連続で失敗したらUnhealthyと判定するように変更した。
これで毎日無駄な通知が来なくはなったが、果たしてこれがベストだったかと言われるとそうではない気がする、、、
そもそもどこの処理に時間がかかりヘルスチェックが通らなかったのかの調査ができていなかったり、「Unhealthy threshold」の値を「5」とする根拠がないこと、何らかの異常が発生してヘルスチェックに失敗した場合でも、この変更により4回目の失敗までは検知できないということになった。
他に良い策がある方は是非コメントいただけると幸いです。
あと、今回の件とは直接関係はないが、ヘルスチェックに関する小ネタを見つけたので最後に貼っておく。