ALB登録解除の遅延を検証してみた

記事タイトルとURLをコピーする

はじめに

サメ映画をこよなく愛する梅木です。

先日、Application Load Balancer (ALB) に紐づけている EC2のインスタンスサイズを変更する際に、 登録遅延の解除(Deregistration delay) について検証する機会がありましたので、検証結果をまとめます。

登録解除の遅延(Deregistration delay) とは?

EC2のインスタンスサイズ変更など、ALB配下にあるターゲットをメンテナンスする際に、一時的にターゲット設定から除外することがあります。 その際、ターゲット設定から除外した後、即座に切り離しが実行された場合、既存のリクエスト(処理中のリクエスト)が正常に処理されない可能性があります。

それを防ぐための機能が 登録解除の遅延(Deregistration delay) です。

既存のリクエストを処理するために、既存の接続についてはすぐに切断せず、一定時間(デフォルトでは300秒)待機します。この「待機時間」が登録解除遅延です。

これにより、処理中のリクエストが中断されることなく完了できます。

検証してみた

構成

検証は、下記の構成で行いました。

  • ネットワーク環境:VPC、サブネット、ルートテーブル等
  • ALB:2台のEC2インスタンスにトラフィックを分散
  • ターゲットグループ:登録解除遅延を60秒に設定(検証のため短く設定しましたが、アプリケーションの処理所要時間に応じて調整してください)
  • EC2インスタンス(2台):Webサーバーが動作。HTTPレスポンスにて、どちらのEC2からのレスポンスか識別できるよう設定 (EC2-A or EC2-B)

検証内容

No フェーズ 内容
1 登録解除前 ALBに1秒ごとにリクエストを送信し、ログに記録
2 登録解除後 EC2-Aをターゲットグループから登録解除
3 再登録後 EC2-Aをターゲットグループに再登録

これらの検証を、スクリプトを作成し検証しました。

検証内容

なお、スクリプトは本記事の末尾に記載しておりますので、ご自分で検証されたい方は参考にどうぞ!

検証結果

サマリ

No フェーズ レスポンス EC2-Aのステータス EC2-Bのステータス
1 登録解除前 両方のターゲットから返却された healthy healthy
2 登録解除後 EC2-B からのみ返却された draining
→(登録解除遅延の設定時間経過後)
→なし(ターゲットから削除)
healthy
3 再登録後 両方のターゲットから返却された initial
→ healthy
healthy

検証結果詳細

検証用スクリプトにて、HTTPステータス、レスポンスボディ(どのEC2から返却されたか)をログに記録し結果を確認しました。

1. 登録解除前

--- ターゲットヘルスステータス (2025-08-26T07:36:54.661134) ---
Target ID: i-xxxxxxxxxxxxxxxxa, Health State: healthy
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

[2025-08-26T07:36:54.679596] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:36:55.692783] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:36:56.701496] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:36:57.718295] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:36:58.730244] Status: 200, Body: 'Hello from EC2-B'
(省略)

リクエストが両ターゲットに正常に割り振られていることが分かります。

2. 登録解除

--- ターゲットヘルスステータス (2025-08-26T07:37:06.340671) ---
Target ID: i-xxxxxxxxxxxxxxxxa, Health State: draining
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

[2025-08-26T07:37:06.355114] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:37:07.368498] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:08.379836] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:09.391630] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:10.403205] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:11.416895] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:12.429128] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:13.443154] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:14.454719] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:37:15.467377] Status: 200, Body: 'Hello from EC2-B'
--- ターゲットヘルスステータス (2025-08-26T07:37:17.156394) ---
Target ID: i-xxxxxxxxxxxxxxxxa, Health State: draining
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

--- ターゲットヘルスステータス (2025-08-26T07:38:22.943647) ---
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

登録解除後、EC2-B(登録解除していないEC2)からのみレスポンスが返却されるようになりました。 EC2-A(登録解除したEC2)のステータスは draining となっており、その後 healthy に変わりました。

なお、登録解除後の直後に、EC2-A(登録解除したEC2)からレスポンスが1件記録されましたが、登録解除の数ミリ秒後であるため、大きな問題にはならないと考えてよいと思います。

3. 再登録

--- ターゲットヘルスステータス (2025-08-26T07:38:24.498820) ---
Target ID: i-xxxxxxxxxxxxxxxxa, Health State: initial
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

[2025-08-26T07:38:24.515628] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:25.529155] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:26.541054] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:27.554449] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:28.562859] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:38:29.579015] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:38:30.587072] Status: 200, Body: 'Hello from EC2-A'
[2025-08-26T07:38:31.600378] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:32.612871] Status: 200, Body: 'Hello from EC2-B'
[2025-08-26T07:38:33.629119] Status: 200, Body: 'Hello from EC2-A'
--- ターゲットヘルスステータス (2025-08-26T07:38:35.326651) ---
Target ID: i-xxxxxxxxxxxxxxxxa, Health State: healthy
Target ID: i-xxxxxxxxxxxxxxxxb, Health State: healthy

再登録後、直後のステータスは initial となり、その後ルーティング対象となっていることが分かりました。

まとめ

ALBの登録解除、登録解除遅延について検証を行いました。 ユーザーに影響を与えることなくバックエンドのメンテナンスを行いたい場合に非常に役立つ機能ですので、ALBを使用されている場合は本機能の活用をご検討ください。

【参考情報】

検証用スクリプト

import requests
import time
import datetime
import subprocess
import json

#--- 設定 ---
TARGET_URL = "https://alb.example.com/"
# ターゲットグループのARN
TARGET_GROUP_ARN = "<ターゲットグループのARN>"
# 登録解除したいEC2インスタンスのID
EC2_ID_TO_DEREGISTER = "i-xxxxxxxxxxxx"
OUTPUT_FILE = "alb_test_log.txt"
#-----------

def send_request_and_log(log_file, num_requests):
    """指定された回数、ALBにリクエストを送信し、結果をログファイルに記録します。"""
    for i in range(num_requests):
        try:
            response = requests.get(TARGET_URL, timeout=5)
            status_code = response.status_code
            body = response.text.strip().split('\n')[0]
            log_line = f"[{datetime.datetime.now().isoformat()}] Status: {status_code}, Body: '{body}'"
            print(log_line)
            log_file.write(log_line + '\n')
        except requests.exceptions.RequestException as e:
            error_line = f"[{datetime.datetime.now().isoformat()}] ERROR: {e}"
            print(error_line)
            log_file.write(error_line + '\n')
        time.sleep(1)

def get_target_health(log_file):
    """AWS CLIでターゲットのヘルスステータスを取得し、ログに記録します。"""
    print(f"\n--- ターゲットヘルスステータスを取得します ---")
    command = [
        "aws", "elbv2", "describe-target-health",
        "--target-group-arn", TARGET_GROUP_ARN,
        "--output", "json"
    ]
    try:
        result = subprocess.run(command, check=True, text=True, capture_output=True)
        health_data = json.loads(result.stdout)
        
        log_file.write(f"--- ターゲットヘルスステータス ({datetime.datetime.now().isoformat()}) ---\n")
        
        for target in health_data['TargetHealthDescriptions']:
            target_id = target['Target']['Id']
            health_state = target['TargetHealth']['State']
            log_line = f"Target ID: {target_id}, Health State: {health_state}"
            print(log_line)
            log_file.write(log_line + "\n")
        log_file.write("\n")
        
    except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
        error_line = f"ターゲットヘルスステータス取得に失敗しました。エラー: {e}"
        print(error_line)
        log_file.write(error_line + '\n')

def deregister_ec2():
    """AWS CLIを使用してEC2をターゲットグループから登録解除します。"""
    print(f"\n--- EC2インスタンス '{EC2_ID_TO_DEREGISTER}' をターゲットグループから登録解除します ---")
    command = [
        "aws", "elbv2", "deregister-targets",
        "--target-group-arn", TARGET_GROUP_ARN,
        "--targets", f"Id={EC2_ID_TO_DEREGISTER}"
    ]
    try:
        subprocess.run(command, check=True, text=True, capture_output=True)
        print("登録解除コマンドが正常に実行されました。\n")
    except subprocess.CalledProcessError as e:
        print(f"登録解除に失敗しました。エラー: {e.stderr}")

def register_ec2():
    """AWS CLIを使用してEC2をターゲットグループに再登録します。"""
    print(f"\n--- EC2インスタンス '{EC2_ID_TO_DEREGISTER}' をターゲットグループに再登録します ---")
    command = [
        "aws", "elbv2", "register-targets",
        "--target-group-arn", TARGET_GROUP_ARN,
        "--targets", f"Id={EC2_ID_TO_DEREGISTER}"
    ]
    try:
        subprocess.run(command, check=True, text=True, capture_output=True)
        print("再登録コマンドが正常に実行されました。\n")
    except subprocess.CalledProcessError as e:
        print(f"再登録に失敗しました。エラー: {e.stderr}")

def main():
    """メインの実行フロー"""
    print(f"--- ログを '{OUTPUT_FILE}' に出力します ---")
    with open(OUTPUT_FILE, "w") as f:
        # 1. 登録解除前のリクエストとヘルスチェック
        print("\n--- フェーズ1: 登録解除前のリクエスト ---")
        get_target_health(f)
        send_request_and_log(f, 10)

        # 2. EC2インスタンスの登録解除とヘルスチェック
        deregister_ec2()
        get_target_health(f)

        # 3. 登録解除後のリクエスト
        print("\n--- フェーズ2: 登録解除後のリクエスト ---")
        send_request_and_log(f, 10)
        
        # 4. 登録解除後のヘルスチェック(Draining期間中)
        print("\n--- フェーズ2.5: Draining期間中のヘルスチェック ---")
        get_target_health(f)
        time.sleep(65) # 登録遅延が完了するまで待機
        get_target_health(f)

        # 5. EC2インスタンスの再登録
        register_ec2()
        
        # 6. 再登録後のリクエストとヘルスチェック
        print("\n--- フェーズ3: 再登録後のリクエスト ---")
        get_target_health(f)
        send_request_and_log(f, 10)
        get_target_health(f)


    print(f"\n--- スクリプトが完了しました。詳細は '{OUTPUT_FILE}' を確認してください ---")

if __name__ == "__main__":
    main()

梅木美香(記事一覧)

EC部SA課所属

2025 Japan All AWS Certifications Engineers

岩手県盛岡市出身。ソウルフードは、もちろん盛岡じゃじゃめんです