Pythonで学ぶAWS Lambdaを活用したEC2インスタンス監視とNLBターゲットルールの自動切り替え

## はじめに
AWS Lambda関数を用いて、特定のEC2インスタンスの状態を監視し、両方のインスタンスが停止した際にNLBのリスナーをメンテナンスモードに変更する方法について解説します。

AWS SDK for Python（boto3）を活用して、EC2インスタンスの状態確認とNLBのターゲットグループの変更を行います。

本記事では、各コードの部品を順を追って説明し、全体の流れを把握できるようにします。

## コードの部品紹介
AWS Lambda関数を使用して特定のEC2インスタンスの状態を監視し、両方のインスタンスが停止している場合にNLB（Network Load Balancer）のリスナーをメンテナンスモードに変更する処理を行います。各部分を解説します。

### 1. インポートとクライアントの初期化
```python
import boto3

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    elb_client = boto3.client('elbv2')
```

**解説:**
AWS SDK for Python（boto3）をインポートし、EC2とELBv2（Elastic Load Balancing v2）のクライアントオブジェクトを作成します。これにより、Lambda関数内でEC2インスタンスとALBのルール設定を操作できるようになります。

### 2. 監視対象EC2インスタンスの設定
```python
# 監視対象のEC2インスタンスのID
instance_ids = ['i-0123456789abcdef0', 'i-0abcdef1234567890']

```

**解説:**
監視対象のEC2インスタンスのIDをリストとして設定します。このリストに含まれるインスタンスの状態を後でチェックし、サービスの可用性を確保するために使用されます。

### 3. EC2インスタンスの状態確認
```python
# インスタンスの状態を確認
response = ec2_client.describe_instances(InstanceIds=instance_ids)

instances_stopped_count = sum(
    1 for reservation in response['Reservations']
    for instance in reservation['Instances']
    if instance['State']['Name'] == 'stopped'
)

```

**解説:**
指定したEC2インスタンスの状態を取得し、stopped 状態のインスタンスをカウントします。describe_instances メソッドを使用して、インスタンスの状態情報を取得し、リスト内包表記を用いて停止しているインスタンスの数を数えます。


### 4. NLBターゲットグループの更新条件

```python
# 2台とも停止している場合のみNLBのターゲットグループをメンテナンスモードに変更
if instances_stopped_count == len(instance_ids):
```

**解説:**
カウントした停止インスタンスの数が監視対象のインスタンス数と一致する場合、すなわち全てのインスタンスが停止している場合に、次の処理を実行します。

### 5. NLBのターゲットグループ変更
```python
# NLBのターゲットグループをメンテナンス用に変更
target_group_arn_maintenance = 'arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-maintenance-tg/123456789012'

# NLBのリスナーを更新
response = elb_client.modify_listener(
    ListenerArn='arn:aws:elasticloadbalancing:region:account-id:listener/net/my-nlb-listener/123456789012',
    DefaultActions=[
        {
            'Type': 'forward',
            'TargetGroupArn': target_group_arn_maintenance
        }
    ]
)
```

**解説:**
全てのEC2インスタンスが停止している場合、NLBのターゲットグループをメンテナンスモードに変更します。

modify_listener メソッドを使用して、指定したリスナーのデフォルトアクションを変更し、メンテナンス用のターゲットグループにルーティングするように設定します。


## 全体のコード
```python
import boto3

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    elb_client = boto3.client('elbv2')
    
    # 監視対象のEC2インスタンスのID
    instance_ids = ['i-0123456789abcdef0', 'i-0abcdef1234567890']
    
    # インスタンスの状態を確認
    response = ec2_client.describe_instances(InstanceIds=instance_ids)

    instances_stopped_count = sum(
        1 for reservation in response['Reservations']
        for instance in reservation['Instances']
        if instance['State']['Name'] == 'stopped'
    )
    
    # 2台とも停止している場合のみNLBのターゲットグループをメンテナンスモードに変更
    if instances_stopped_count == len(instance_ids):
        # NLBのターゲットグループをメンテナンス用に変更
        target_group_arn_maintenance = 'arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-maintenance-tg/123456789012'
        
        # NLBのリスナーを更新
        elb_client.modify_listener(
            ListenerArn='arn:aws:elasticloadbalancing:region:account-id:listener/net/my-nlb-listener/123456789012',
            DefaultActions=[
                {
                    'Type': 'forward',
                    'TargetGroupArn': target_group_arn_maintenance
                }
            ]
        )

    return {
        'statusCode': 200,
        'body': 'Processing completed.'
    }
```

## プログラムのフローチャート
フロー処理の流れは以下になります。

```python
+--------------------------------------------------+
|                     開始                         |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|          AWS SDK for Python (boto3)をインポート   |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|           EC2とELBのクライアントを作成          |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|       監視対象のEC2インスタンスのIDを設定       |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|           インスタンスの状態を確認               |
|     (describe_instances APIを使用)               |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|        停止しているインスタンスの数をカウント     |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|  停止しているインスタンスの数は2台か？           |
+---------------------+----------------------------+
|                     |                            |
|        はい         |            いいえ         |
|                     |                            |
|                     v                            |
|      +-------------------------------------------+ |
|      | NLBのターゲットグループをメンテナンス用に  | |
|      | 変更 (modify_listener APIを使用)        | |
|      +-------------------------------------------+ |
|                     |                            |
|                     v                            |
+--------------------------------------------------+
|             処理が完了したことを返す             |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|                       終了                       |
+--------------------------------------------------+
```

## まとめ
本記事では、AWS Lambdaを利用してEC2インスタンスの状態に応じたNLBの設定変更処理を紹介しました。

具体的なコード例とフローチャートを通じて、実装の流れを明確にしました。この方法を活用することで、インフラの可用性を向上させることができ、システムの運用効率を改善できます。

引き続きAWSの機能を利用して、さらなる自動化を目指していきましょう。

## おまけ

### 障害復旧時の全体コード
```python
import boto3

def lambda_handler(event, context):
    elb_client = boto3.client('elbv2')
    
    # 通常のターゲットグループARN
    target_group_arn_normal = 'arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-normal-tg/123456789012'
    
    # NLBのリスナーを更新して通常モードに戻す
    response = elb_client.modify_listener(
        ListenerArn='arn:aws:elasticloadbalancing:region:account-id:listener/net/my-nlb-listener/123456789012',
        DefaultActions=[
            {
                'Type': 'forward',
                'TargetGroupArn': target_group_arn_normal
            }
        ]
    )
    
    return {
        'statusCode': 200,
        'body': 'NLB listener updated to normal mode.'
    }
```

### 1. AWSクライアントの初期化とターゲットグループARNの設定
```python
import boto3

def lambda_handler(event, context):
    elb_client = boto3.client('elbv2')
    
    # 通常のターゲットグループARN
    target_group_arn_normal = 'arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-normal-tg/123456789012'
```

**解説:**
boto3のインポート: AWS SDK for Pythonをインポートし、AWSサービスにアクセスする準備をします。
ELBクライアントの初期化: elb_clientを作成し、Elastic Load Balancing v2（ELBv2）を操作できるようにします。
ターゲットグループARNの設定: 通常モードに戻すためのターゲットグループのARNを設定します。

### 2. NLBリスナーの更新とレスポンスの返却
```python
    # NLBのリスナーを更新して通常モードに戻す
    response = elb_client.modify_listener(
        ListenerArn='arn:aws:elasticloadbalancing:region:account-id:listener/net/my-nlb-listener/123456789012',
        DefaultActions=[
            {
                'Type': 'forward',
                'TargetGroupArn': target_group_arn_normal
            }
        ]
    )
    
    return {
        'statusCode': 200,
        'body': 'NLB listener updated to normal mode.'
    }
```

**解説:**
リスナーの更新: modify_listenerメソッドを使って、NLBのリスナーを通常モードに戻す設定を行います。この時、指定されたターゲットグループにリクエストを転送するように設定します。
レスポンスの返却: 更新が完了したことを示す200ステータスコードとメッセージを返します。


### 障害復旧時のフローチャート
```python
+--------------------------------------------------+
|                     開始                         |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|          AWS SDK for Python (boto3)をインポート   |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|           ELBのクライアントを作成               |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|       通常のターゲットグループARNを設定         |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|      NLBのリスナーを更新して通常モードに戻す    |
|         (modify_listener APIを使用)            |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|             処理が完了したことを返す             |
+--------------------------------------------------+
                          |
                          v
+--------------------------------------------------+
|                       終了                       |
+--------------------------------------------------+
```

## 参考記事

https://qiita.com/free-honda/items/54ec8a62a3b3bf0b25de