ターゲットグループ障害発生時：Lambda関数でALBルールを制御し、障害時にメンテナンスページを表示する方法

## はじめに
この記事では、AWSのApplication Load Balancer (ALB) を使用し、ターゲットグループに紐づくすべてのEC2インスタンスが正常でない場合に、Lambda関数を利用して動的にメンテナンスページ（固定レスポンスページ）を表示させる手順を紹介します。

この設定により、アプリケーションの可用性を高めつつ、ユーザーに適切なメンテナンス情報を提供することが可能になります。

本記事が、誰かの技術の支えとなることを願っています！

## 前提：本記事は前回の検証の続きになります。

今回の内容は、以下の記事に続く検証内容です。

前回は、Application Load Balancer (ALB) を使用して複数のEC2インスタンスへの負荷分散を行い、CloudWatchの「**HealthyHostCount**」とEventBridgeを組み合わせてEC2障害を検知するシステムを構築しました。

**ALBで複数EC2インスタンスを負荷分散する手順**

https://qiita.com/free-honda/items/d8fbcd649a711e8f283a

**CloudWatchの「HealthyHostCount」とEventBridgeを使ったEC2障害検知システムの作成手順**

https://qiita.com/free-honda/items/95e969d3473bff492198

今回は、続きの検証として、**すべてのEC2インスタンスが正常でない場合に、Lambda関数を利用してメンテナンスページを表示する手順を実装します。**

## 事前準備
### ALBでの固定レスポンス機能の設定

AWS Management Consoleにログインし、EC2ダッシュボードに移動し、左側のメニューからLoad Balancersを選択し、対象のALBを選択します。

![スクリーンショット 2024-10-27 12.47.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/2a4f5d8c-81e9-aac4-6e1b-a316c27e5caa.png)

リスナーとルールのタブを開き、リスナーの設定を確認し、ルールを管理画面からルールを追加します。

![スクリーンショット 2024-10-27 12.48.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/cbd426de-a5c5-1a1b-3c9e-c4f57c5a5d04.png)

ルール名を「``Sorry``」とし、新規ルールを追加します。

![スクリーンショット 2024-10-27 12.49.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/8618006f-9362-631a-ed39-0bfb6fd530a0.png)

パスパターンには「``*``」を入力し、条件を追加します。

![スクリーンショット 2024-10-27 12.52.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/ac212565-2e7a-d37c-9a72-0463a53788c9.png)

ルールアクションの設定画面で「**固定レスポンスを返す**」を選択します。

![スクリーンショット 2024-10-27 13.02.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/4a6488c7-d4cd-8e7a-eb2d-417a2bf04469.png)


以下の``HTMLファイル``をレスポンス本文として入力します。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>障害発生中</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8d7da;
            color: #721c24;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 2em;
        }
        p {
            font-size: 1.2em;
        }
    </style>
</head>
<body>
    <h1>障害が発生中です</h1>
    <p>ご迷惑をおかけしております。復旧作業を行っておりますので、しばらくお待ちください。</p>
</body>
</html>
```

「``Sorry``」ルールの優先度を「``3``」に設定し、内容を確認したら作成をクリックします。

![スクリーンショット 2024-10-27 12.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/9279a876-9b69-5e5d-2703-01ccf4a25a63.png)

これで、ALBが特定の条件下でメンテナンスページを表示できるようになります。

### ALBの通常ルール作成

現在のALBのリスナーとルールの設定は以下のようになっています。

![スクリーンショット 2024-10-27 13.05.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/33225d35-1357-7d7e-39a6-5a5cfd78174b.png)

この設定では、すべてのアクセスに対して「``*``」が指定されているため、「``Sorry``」ルールが適用されてしまいます。

ALBのロードバランサーのDNS名にブラウザでアクセスしてみると、以下の通り、先ほど作成したメンテナンスページが表示されることを確認できます。

![スクリーンショット 2024-10-27 13.07.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/571e820f-8372-aa14-3622-2d94e0afffa4.png)

次に、優先度「``2``」で新規の「``通常パターン``」ルールを作成します。

リスナーとルールのタブから「ルールを追加」を選択し、ルール名を「``Normal``」として作成します。

![スクリーンショット 2024-10-27 13.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/a68bd6f5-24d6-d04d-c084-9b678a85cf26.png)

最終的に、以下のようにパスパターン設定を「``*``」とし、優先度を「``2``」に設定します。ターゲット先としては、EC2インスタンスが紐づいているターゲットグループを選択します。

![スクリーンショット 2024-10-27 13.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/2a9de83b-ea93-2633-d020-e21453f0e89a.png)

これにより、優先度が高い「``Normal``」ルールが適用される仕組みとなります。

![スクリーンショット 2024-10-27 13.15.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/a7061cd0-b7fe-ce8c-9389-8fc5f9b588cd.png)

## 実装手順
### Lambda関数でALBの優先度変更プログラムを作成

次に、CloudWatchの「``HealthyHostCount``」とEventBridgeを使用したEC2障害検知システムの一部として、Lambda関数を作成します。

:::note warn
``[変数]``として設定する値は、ご自身の環境に合わせて適宜修正してください。
:::


```py
import json
import boto3

def lambda_handler(event, context):
    alb_arn = "arn:aws:xxx"
    alb_listener_arn = "arn:aws:xxx"
    target_group_rule_arn = "arn:aws:xxx"
    sorry_rule_arn = "arn:aws:xxx"

    cloudwatch = boto3.resource('cloudwatch')
    alarm = cloudwatch.Alarm("HealthlyHostCount")
    
    client = boto3.client('elbv2')
    current_rule_arn = client.describe_rules(ListenerArn=alb_listener_arn)['Rules'][0]['RuleArn']

    if alarm.state_value == "ALARM" and current_rule_arn == sorry_rule_arn:
        return {
            'statusCode': 200,
            'body': 'Sorryページはすでに表示されています'
        }
    elif alarm.state_value == "OK" and current_rule_arn == target_group_rule_arn:
        return {
            'statusCode': 200,
            'body': '通常ページはすでに表示されています'
        }

    target_group_rule_priority = 2
    sorry_rule_priority = 3
    if current_rule_arn == target_group_rule_arn:
        target_group_rule_priority = 3
        sorry_rule_priority = 2

    response = client.set_rule_priorities(
        RulePriorities=[
            {
                'RuleArn': target_group_rule_arn,
                'Priority': target_group_rule_priority
            },
            {
                'RuleArn': sorry_rule_arn,
                'Priority': sorry_rule_priority
            },
        ])

    return {
        'statusCode': 200,
        'body': json.dumps(response)
    }
```

この関数は、ALBのルールを動的に切り替え、ヘルスチェックの結果に基づいて「``Sorry``」ルールと「``Normal``」ルールを切り替えます。

:::note alert
作成したLambda関数のデフォルトタイムアウトは「``3秒``」になっているため、「``30秒``」などに変更することをおすすめします。
:::

![スクリーンショット 2024-10-27 13.42.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/12ff1d98-0377-b6cb-26e4-cd4427ba0d10.png)

テスト実行を行ったところ、以下のエラーが発生しました。調査したところ、Lambda関数に関連付けられているIAMロールの権限が不足していました。

```bash
[ERROR] ClientError: An error occurred (AccessDenied) when calling the DescribeRules operation: User: arn:aws:sts::xxx:assumed-role/HealthlyHostCount-Lambda-role-lsp6fcvh/HealthlyHostCount-Lambda is not authorized to perform: elasticloadbalancing:DescribeRules because no identity-based policy allows the elasticloadbalancing:DescribeRules action
Traceback (most recent call last):
```

この問題を解決するために、カスタムIAMポリシーを作成し、IAMロールにアタッチしました。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:SetRulePriorities",
                "cloudwatch:DescribeAlarms"
            ],
            "Resource": "*"
        }
    ]
}
```

### Lambda関数とEventBridgeの統合

Lambda関数をEventBridgeと統合することで、ALBのターゲットグループの状態がすべて正常でない場合に自動でメンテナンスページを表示することができます。

先ほど作成したLambda関数の画面からトリガーを追加します。

![スクリーンショット 2024-10-27 13.46.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/ba93dd01-8fa8-a229-98d4-0c70a59d3c63.png)

一つのLambda関数の処理に対して、EventBridge (CloudWatch Events)のルールを2つ設定します。

![スクリーンショット 2024-10-27 13.47.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/e7a4e34a-dcf6-25f9-9e2d-ba530bbe5ce7.png)

## 検証確認

### 障害発生時のパターン
ALBに紐づくターゲットグループがすべてNGとなった場合に、メンテナンスページ（``503エラー``）が表示されるかどうかを検証します。

まず、``sudo systemctl stop httpd.service``コマンドを使用してhttpdサービスを明示的に停止します。

![スクリーンショット 2024-10-27 13.56.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/898e5e26-f066-1773-dfb8-e0671b917b38.png)

ターゲットグループのヘルスチェックが全台NGになったことがCloudWatchで検知されました。

![スクリーンショット 2024-10-27 14.18.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/a7571d57-6695-c3fe-3aa4-6bc88b6e74ee.png)

その後、EventBridgeのルールが正常に動作していることが確認できました。

![スクリーンショット 2024-10-27 14.20.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/b04c976b-220b-8a55-8ab0-de56ce46f2f5.png)

Lambda関数内でリスナーとルールの設定(**優先度**)が変更されていることも正常に確認できました。

![スクリーンショット 2024-10-27 14.22.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/f7d08b54-4b61-19c2-140c-e0e2b1349f22.png)

最後に、ブラウザでアクセスした際にメンテナンスページが表示されることも確認できたため、障害発生時のパターンは成功です！

![スクリーンショット 2024-10-27 14.22.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/c99ea990-3293-55f0-63fe-a3235353c038.png)

### 障害復旧時のパターン
ALBのターゲットグループが正常に戻った際に、通常ページが表示されるかどうかを確認します。

今回は、あえてApacheサービスが1台のみ起動している場合でも動的に切り替えが行われるかを検証します（**2台復旧時については動作確認済みです**）。

次に、``sudo systemctl start httpd.service``コマンドを使用してhttpdサービスを明示的に起動します。

![スクリーンショット 2024-10-27 14.27.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/79bb8721-b21a-1825-9980-e2b21f94fb9a.png)

ターゲットグループのヘルスチェックで1台のみOKになったことがCloudWatchで検知され、アラート状態が復旧したことが確認できました。

![スクリーンショット 2024-10-27 14.30.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/9c55f51b-64fd-df6d-c2c1-8748b53ddc85.png)

その後、EventBridgeのルールが正常に動作し、Lambda関数内でリスナーとルールが変更(**優先度**)されていることも確認できました。

![スクリーンショット 2024-10-27 15.23.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/9eb41139-1583-3db4-5fc7-3c150500e88e.png)

最後に、ブラウザでアクセスした際に通常ページが表示されることも確認できたため、障害復旧時のパターンも成功です！

![スクリーンショット 2024-10-27 15.23.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/5f350bc2-f569-0b73-3829-dd97ee732e73.png)

## まとめ
ここまで読んでいただきありがとうございました。

今回は、ターゲットグループ全台NG時にメンテナンスページが表示され、復旧後に通常ページへ自動切り替えできるかを検証しました。

ヘルスチェック、EventBridgeルール、Lambda関数の動作を確認する工程が多く難易度が高かったものの、すべて正常に動作し、検証をやり切ることができました。

## 過去の関連記事

https://qiita.com/free-honda/items/be9a9de2c7e3564c0b5c