EC2インスタンス停止時にALBをSorryページに切り替える設定手順

## はじめに
AWSで単一のEC2インスタンスを使用している場合、もしそのインスタンスが停止した際には、ALB（Application Load Balancer）の固定レスポンス機能を利用して「Sorryページ」を表示し、起動している場合には通常のページを表示する仕組みを作ることができます。

今回は、Amazon EventBridge、Lambda、ALBを組み合わせて、この自動化を行う手順をご紹介します。

## 1. IAMロールの作成
Lambda関数がELBのリスナールールの変更を行うために必要なIAMロールを設定します。

**手順:**
・AWSマネジメントコンソールにログインします。
・IAMに移動し、ロールの作成をクリックします。
・Lambdaサービス用のロールを作成します。

以下のポリシーをアタッチします。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:SetRulePriorities",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:CreateRule",
                "elasticloadbalancing:DeleteRule"
            ],
            "Resource": "*"
        }
    ]
}
```

IAMロールに名前（例: LambdaELBRuleChangeRole）を付けて作成します。

## 2. ALBとリスナールールの設定
Amazon ALBに固定レスポンスのルールを追加し、EC2インスタンスの状態に応じてレスポンスを切り替える仕組みを作ります。

**手順:**
```
AWSマネジメントコンソールでEC2サービスに移動し、ロードバランサー(ALB)を設定します。

リスナー設定で、80番ポートまたは443番ポートのリスナーを選択します。

リスナールールを作成し、以下の2つのルールを設定します。

・通常レスポンスルール:
　優先度: 1
　アクション: 転送（正常時のターゲットグループに転送）

・障害時レスポンスルール:
　優先度: 10
　　アクション: 固定レスポンス
　　ステータスコード: 503
　　内容: "Sorry, the service is currently unavailable."
```

**固定レスポンスの設定手順:**

```
AWSマネジメントコンソールでEC2に移動し、ロードバランサーを選択します。

リスナーを選択し、リスナー設定をクリックします。

アクションを追加ボタンをクリックし、固定レスポンスを送信を選択します。

ステータスコードを503に設定し、レスポンスのメッセージを"Sorry, the service is currently unavailable."と入力します。

このアクションに対して優先度を10に設定し、ルールを保存します。
```

## 3. EventBridgeでEC2インスタンスの状態を監視
EventBridgeを利用して、EC2インスタンスがstoppedまたはrunning状態になったときにLambda関数をトリガーします。


**手順:**
・AWSマネジメントコンソールでEventBridgeに移動します。
・ルールの作成をクリックします。
・イベントソースとして「EC2 Instance State-change Notification」を選択します。

**ルールの設定:
停止時にトリガーされるルール（EC2インスタンスが停止した場合）**

```json
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["stopped"],
    "instance-id": ["i-0458be1d8c980e8ec"]
  }
}
```

**起動時にトリガーされるルール（EC2インスタンスが起動した場合）**

```json
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["running"],
    "instance-id": ["i-0458be1d8c980e8ec"]
  }
}
```

ルールのターゲットに、後述のLambda関数を設定します。

## 4. Lambda関数の作成
Lambda関数を作成し、インスタンスの状態に基づいてALBのリスナールールの優先順位を変更します。

### インスタンスが停止している場合のLambda関数
EC2インスタンスが停止した場合に、障害時ページの優先度を10に、通常ページの優先度を1に設定します。

```python
import boto3

def lambda_handler(event, context):
    client = boto3.client('elbv2')

    # インスタンス停止時のリスナールールの優先順位変更
    response = client.set_rule_priorities(
        RulePriorities=[
            {
                # 障害レスポンスのルール (Sorryページ)
                'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener-rule/app/my-alb/1234567890abcdef/1234567890abcdef/9876543210abcdef',
                'Priority': 10
            },
            {
                # 通常レスポンスのルール
                'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener-rule/app/my-alb/1234567890abcdef/1234567890abcdef/1234567890abcdef',
                'Priority': 1
            },
        ]
    )
    return response
```
    
### インスタンスが起動している場合のLambda関数
EC2インスタンスが起動した場合に、通常ページの優先度を1に、障害ページの優先度を10に戻します。

```python
import boto3

def lambda_handler(event, context):
    client = boto3.client('elbv2')

    # インスタンス起動時のリスナールールの優先順位変更
    response = client.set_rule_priorities(
        RulePriorities=[
            {
                # 通常レスポンスのルール
                'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener-rule/app/my-alb/1234567890abcdef/1234567890abcdef/1234567890abcdef',
                'Priority': 1
            },
            {
                # 障害レスポンスのルール (Sorryページ)
                'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener-rule/app/my-alb/1234567890abcdef/1234567890abcdef/9876543210abcdef',
                'Priority': 10
            },
        ]
    )
    return response
```

**Lambda関数の設定手順:**
AWSマネジメントコンソールでLambdaに移動し、新しい関数を作成します。
上記のコードをそれぞれ停止用関数と起動用関数にコピーペーストします。
IAMロールとして、先ほど作成したLambdaELBRuleChangeRoleを割り当てます。

## 5. 統合の流れ
### 1. EventBridgeで監視
```
**インスタンスがstopped**になると、停止用のLambda関数がトリガーされ、ALBがSorryページに切り替わります。
**インスタンスがrunning**になると、起動用のLambda関数がトリガーされ、通常ページに戻ります。
```

### 2. ALBのリスナールール設定
```
通常ページのルールの優先度を1に設定。
Sorryページのルールの優先度を10に設定。
```

## まとめ
:::note
・停止時: Sorryページの優先度を10に、通常ページの優先度を1に設定。
・起動時: 通常ページの優先度を1に、Sorryページの優先度を10に戻す。
:::

この手順により、1台のEC2インスタンスの停止・起動に応じてALBが自動的にレスポンスを切り替えることができます。

## おまけ
以下エラーの対応方法についての紹介になります。

```
"errorMessage": "An error occurred (AccessDenied) when calling the SetRulePriorities operation: User: 
```

このエラーは、AWS Lambda関数が実行される際に、指定されたアクション（elasticloadbalancing:SetRulePriorities）を実行する権限が不足しているために発生しています。

具体的には、Lambda関数が使用しているIAMロールに、SetRulePriorities操作を実行するための適切なポリシーが割り当てられていないことが原因です。

### 解決策
Lambda関数が使用しているIAMロールに、elasticloadbalancing:SetRulePrioritiesアクションを許可するポリシーを追加する必要があります。

ポリシーの検索バーに「Elastic Load Balancing」と入力し、以下のようなカスタムポリシーを作成してアタッチします。
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "elasticloadbalancing:SetRulePriorities",
      "Resource": "arn:aws:elasticloadbalancing:ap-northeast-1:767397967219:listener-rule/app/test/887042017c378ee6/faf1f7c7d6713087/79fef8c304204955"
    }
  ]
}
```

このポリシーは、特定のリスナールールに対してSetRulePrioritiesアクションを許可するものです。Resourceフィールドには、操作対象となるリスナールールのARNが指定されています。