個人的な備忘録：Lambda関数でのいろいろな処理について


## 全体コード
```py
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.'
    }
```

このプログラムは、AWS Lambda関数を使用して、指定したEC2インスタンスの状態を確認し、その状態に応じてNetwork Load Balancer (NLB) の設定を変更する処理を行っています。簡単に説明すると、以下のような流れになっています。

## プログラムの部品紹介



## プログラムの処理内容
EC2クライアントとNLBクライアントの作成:

boto3 ライブラリを使用して、AWSのEC2とElastic Load Balancing (ELB) のクライアントを作成します。

監視対象のEC2インスタンスの状態確認:

指定されたEC2インスタンスのIDリスト（例: ['i-0123456789abcdef0', 'i-0abcdef1234567890']）を使って、インスタンスの状態を取得します。

インスタンスの停止状態を確認:

インスタンスの状態をチェックし、停止 (stopped) 状態のインスタンスがいくつあるかをカウントします。

ターゲットグループの切り替え:

2台のインスタンスがすべて停止している場合にのみ、NLBのターゲットグループをメンテナンス用に切り替えます。
具体的には、NLBのリスナー設定を更新し、メンテナンス用のターゲットグループにリクエストをルーティングするように設定します。

処理の結果を返す:

処理が完了したことを示すステータスコードとメッセージを返します。


このプログラムは、指定したEC2インスタンスがすべて停止している場合に、NLBのターゲットグループをメンテナンスモードに切り替えるための処理を行います。もし少なくとも1台のインスタンスが稼働している場合は、何も変更せずにそのままにしておきます。


## 個人メモ
AWS Systems Manager (SSM) Run Commandを使用して、EC2インスタンス上でhttpdサービスの状態を確認する方法を詳細に説明します。このプロセスでは、Lambda関数を利用して、EC2インスタンスの状態を監視し、必要に応じてELBのリスナールールを変更します。

手順概要
1. EC2インスタンスの設定
1.1 SSMエージェントのインストール
AWS Systems Manager (SSM)を使用するには、SSMエージェントがEC2インスタンスにインストールされている必要があります。多くのAmazon Linux 2や最新のAmazon Linux AMIには、デフォルトでSSMエージェントがインストールされています。もしエージェントがインストールされていない場合は、以下のコマンドを実行してインストールします。

sudo yum install -y amazon-ssm-agent
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
これにより、SSMエージェントが自動的に起動し、AWS Systems Managerとの通信が可能になります。

1.2 IAMロールの設定
次に、EC2インスタンスにアタッチするIAMロールを作成します。このロールには以下のポリシーを追加します：

AmazonEC2RoleforSSM
AmazonSSMManagedInstanceCore
これにより、SSMエージェントがAWS Systems Managerと通信できるようになります。IAMロールを作成したら、そのロールをEC2インスタンスにアタッチします。

2. Lambda関数の作成
2.1 IAMロールの設定（Lambda用）
Lambda関数がSSMにアクセスできるようにするため、LambdaにアタッチするIAMロールに以下のポリシーを追加します：

AmazonSSMFullAccess（必要に応じて最小権限のポリシーに変更）

```py
import boto3
import time

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

    # 監視するEC2インスタンスのID
    instance_ids = ["i-0458be1d8c980e8ec", "i-0b27db6a9b067f345"]
    httpd_stopped_count = 0

    for instance_id in instance_ids:
        # SSM Run Commandを使用してhttpdサービスの状態を確認
        response = ssm_client.send_command(
            InstanceIds=[instance_id],
            DocumentName='AWS-RunShellScript',
            Parameters={'commands': ['systemctl is-active httpd']}
        )

        # コマンドの実行結果を取得するために少し待機
        time.sleep(2)

        command_id = response['Command']['CommandId']
        output = ssm_client.get_command_invocation(
            CommandId=command_id,
            InstanceId=instance_id
        )

        # httpdサービスが停止している場合にカウント
        if output['Status'] == 'Success' and 'inactive' in output['StandardOutputContent']:
            httpd_stopped_count += 1

    # 2台ともhttpdサービスが停止している場合、リスナールールを切り替え
    if httpd_stopped_count == len(instance_ids):
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 2
                },
            ]
        )
    else:
        # 通常ページに戻す（1台でもサービスが動いている場合）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 2
                },
            ]
        )

    return {
        'statusCode': 200,
        'body': 'Lambda execution completed successfully'
    }
```

SSMコマンドの実行
send_command メソッドを使用して、EC2インスタンス上で systemctl is-active httpd コマンドを実行します。このコマンドはhttpdサービスの状態を確認します。

結果の取得
コマンドの実行結果を取得するために、get_command_invocation メソッドを使用します。StandardOutputContent に inactive が含まれているかを確認することで、httpdサービスが停止しているかどうかを判断します。

リスナールールの切り替え
サービスが停止している場合は、ELBのリスナールールをSorryページに変更し、少なくとも1台のインスタンスでサービスが動作している場合は通常のリスナールールに戻すように設定します。

追加設定
リトライとエラーハンドリング: 実際の運用環境では、コマンド実行や結果取得が失敗する場合もあるため、リトライ処理やエラーハンドリングを追加することをお勧めします。
コマンド結果のポーリング間隔: time.sleep(2) で少し待機していますが、必要に応じてポーリング間隔を調整してください。
IAMポリシーの設定
Lambda関数に以下のポリシーをアタッチすることを確認してください：

ssm:SendCommand
ssm:GetCommandInvocation
elasticloadbalancing:SetRulePriorities
elasticloadbalancing:DescribeRules
この手順に従うことで、Lambda関数からSSM Run Commandを使用してhttpdサービスの状態を確認し、サービスが停止している場合にリスナールールを変更するプロセスを実装できます。

```py
import json
import boto3

# EC2インスタンスIDとALBのARNを定義
INSTANCE_IDS = ['i-xxxxxxxxxxxx', 'i-yyyyyyyyyyyy']  # EC2インスタンスのID
LOAD_BALANCER_ARN = 'arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:loadbalancer/app/your-load-balancer-name/xxxx'
MAINTENANCE_RULE_ARN = 'arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:listener-rule/app/your-load-balancer-name/xxxx/rule/xxxx'
DEFAULT_RULE_ARN = 'arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:listener-rule/app/your-load-balancer-name/xxxx/rule/xxxx'

ssm_client = boto3.client('ssm')
elbv2_client = boto3.client('elbv2')

def lambda_handler(event, context):
    # Run Commandでhttpdサービスの状態を確認する
    commands = ["systemctl is-active httpd"]
    
    response = ssm_client.send_command(
        InstanceIds=INSTANCE_IDS,
        DocumentName="AWS-RunShellScript",
        Parameters={'commands': commands},
    )
    
    command_id = response['Command']['CommandId']
    
    # コマンドの出力を取得するまで待機
    output = []
    for instance_id in INSTANCE_IDS:
        response = ssm_client.list_command_invocations(
            CommandId=command_id,
            Details=True,
            Filters=[{'Key': 'InstanceId', 'Value': instance_id}]
        )
        output.append(response['CommandInvocations'][0]['CommandPlugins'][0]['Output'])
    
    # サービスの状態を確認
    httpd_statuses = [line.strip() for line in output if line.strip()]
    stopped_count = httpd_statuses.count("inactive")  # httpdが停止しているインスタンスの数

    # リスナールールの優先度を変更する
    if stopped_count == len(INSTANCE_IDS):
        # 両方のインスタンスが停止している場合、優先度を4から2に変更
        elbv2_client.modify_rule(
            RuleArn=MAINTENANCE_RULE_ARN,
            Actions=[{
                'Type': 'forward',
                'TargetGroupArn': 'arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:targetgroup/your-target-group-name/xxxx',
                'Order': 2
            }]
        )
    else:
        # どちらかのインスタンスが稼働中の場合、優先度を変更しない
        pass

    return {
        'statusCode': 200,
        'body': json.dumps('Lambda executed successfully')
    }
```

以下のコードは、あなたの要件に基づいて修正したものです。httpdサービスの停止状態を確認するために、AWS Systems Manager Run Commandを使用し、EC2インスタンスでのサービスの状態に応じてALBリスナールールの優先度を変更します。記述は、提供された書き方をベースに修正しています。

```python
import boto3

def lambda_handler(event, context):
    # AWSクライアントの初期化
    ssm_client = boto3.client('ssm')
    elbv2_client = boto3.client('elbv2')

    # 監視する2台のEC2インスタンスのID
    instance_ids = ["i-0458be1d8c980e8ec", "i-0b27db6a9b067f345"]
    
    # httpdサービスが停止しているインスタンスをカウント
    instances_stopped_count = 0
    
    for instance_id in instance_ids:
        try:
            # Run Commandを使用してhttpdサービスの状態を確認
            response = ssm_client.send_command(
                InstanceIds=[instance_id],
                DocumentName="AWS-RunShellScript",
                Parameters={'commands': ['systemctl is-active httpd']}
            )
            
            command_id = response['Command']['CommandId']
            output = ssm_client.get_command_invocation(
                CommandId=command_id,
                InstanceId=instance_id
            )
            
            # httpdサービスが停止しているかを確認
            if output['StandardOutputContent'].strip() != 'active':
                instances_stopped_count += 1
                
        except Exception as e:
            print(f"Error checking httpd service on instance {instance_id}: {e}")

    if instances_stopped_count == 2:  # 2台とも停止している場合
        # Sorryページに切り替え（メンテナンスモードへ）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 2
                },
            ]
        )
    else:
        # 通常ページに戻す（1台でも稼働中なら通常モードへ）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 2
                },
            ]
        )

    # 現在のルール設定を確認
    result = elbv2_client.describe_rules(
        ListenerArn='arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_LISTENER_ARN'
    )
    
    return result
```

修正内容:
httpdサービスの確認:

AWS Systems Manager Run Commandを使って、各EC2インスタンスでhttpdサービスの状態を確認します。
systemctl is-active httpdでサービスがactiveかどうかを確認し、停止している場合はカウントを増やします。
ALBリスナールールの優先度変更:

2台ともhttpdサービスが停止していた場合は、Sorryページに切り替えます（優先度を変更）。
1台でも稼働している場合は、通常ページを優先します。
エラーハンドリング:

try-exceptブロックで、Run Commandの実行エラーをキャッチし、エラーメッセージを表示するようにしました。
必要な設定:
Lambda関数には、ssm:SendCommand、elbv2:ModifyRuleなどの権限を付与してください。
EC2インスタンスには、Systems Managerエージェントがインストールされ、SSMドキュメントを実行できるIAMロールが設定されている必要があります。
これにより、2台のインスタンスの状態に応じた処理を実行できるようになります。


## 追記メモ

以下のコードは、EC2インスタンスの停止を確認していた処理を、httpdサービスの停止を確認する処理に変更し、SSMのRun Commandを使用してサービスのステータスを確認するように修正しました。

変更点：

EC2の停止状態ではなく、EC2内のhttpdサービスのステータスを確認します。
SSMのRun Commandを使用してサービスの状態を取得します。

```py
import boto3

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    ssm_client = boto3.client('ssm')
    elbv2_client = boto3.client('elbv2')

    # 監視する2台のEC2インスタンスのID
    instance_ids = ["i-0458be1d8c980e8ec", "i-0b27db6a9b067f345"]

    # SSMのRun Commandでhttpdのステータスを確認するコマンド
    command = "systemctl is-active httpd"

    # 各EC2インスタンスのhttpdサービスのステータスを取得
    instances_stopped_count = 0
    for instance_id in instance_ids:
        response = ssm_client.send_command(
            InstanceIds=[instance_id],
            DocumentName="AWS-RunShellScript",
            Parameters={'commands': [command]}
        )
        
        # コマンドの実行結果を取得
        command_id = response['Command']['CommandId']
        output = ssm_client.get_command_invocation(
            CommandId=command_id,
            InstanceId=instance_id
        )
        
        # サービスが停止している場合（`inactive`または`failed`）
        if output['StandardOutputContent'].strip() in ['inactive', 'failed']:
            instances_stopped_count += 1

    if instances_stopped_count == 2:  # 2台ともhttpdサービスが停止している場合
        # Sorryページに切り替え
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 2
                },
            ]
        )
    else:
        # 通常ページに戻す（1台でもhttpdが起動している場合）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 2
                },
            ]
        )

    # 現在のルール設定を確認
    result = elbv2_client.describe_rules(
        ListenerArn='arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_LISTENER_ARN'
    )
    return result
```

主な修正点
SSM Run Commandの使用: send_commandを使用して、指定したEC2インスタンスでhttpdサービスの状態を確認しています。
サービスの状態の確認: コマンドの実行結果をもとに、inactiveまたはfailedの状態であれば停止と見なしています。
負荷分散のルール変更: 2台ともhttpdが停止している場合、Sorryページへ切り替え、それ以外の場合は通常のページを表示します。
この変更により、OS全体の停止ではなく、サービスの状態を元に負荷分散のルールを変更できるようになります。

この処理では、EC2インスタンスの状態を確認してOSの停止に基づく処理を行っていますが、これを「httpdのサービスの停止」に変更し、SSMのRun Commandを使ってステータスを確認するように書き換えます。

以下が修正後のコードです：

```py
import boto3

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

    # 監視する2台のEC2インスタンスのID
    instance_ids = ["i-0458be1d8c980e8ec", "i-0b27db6a9b067f345"]
    
    # SSM Run Commandを使用して各インスタンスでhttpdサービスのステータスを取得
    command_id = ssm_client.send_command(
        InstanceIds=instance_ids,
        DocumentName='AWS-RunShellScript',
        Parameters={'commands': ['sudo systemctl is-active httpd']}
    )['Command']['CommandId']

    # コマンドの実行結果を確認
    response = ssm_client.list_command_invocations(
        CommandId=command_id,
        InstanceId=instance_ids[0],  # 1つ目のインスタンス
        Details=True
    )
    
    # httpdサービスが停止しているインスタンスをカウント
    instances_httpd_stopped_count = sum(
        1 for invocation in response['CommandInvocations']
        for result in invocation['CommandPlugins']
        if 'stopped' in result['Output']
    )

    if instances_httpd_stopped_count == 2:  # 2台ともhttpdが停止している場合
        # Sorryページに切り替え
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 2
                },
            ]
        )
    else:
        # 通常ページに戻す（1台でもhttpdが起動している場合）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 2
                },
            ]
        )

    # 現在のルール設定を確認
    result = elbv2_client.describe_rules(
        ListenerArn='arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_LISTENER_ARN'
    )
    return result
```

変更点:
EC2インスタンスのOS状態ではなく、httpdサービスの状態を確認するために、SSM Run Command を利用しています。
systemctl is-active httpd コマンドを実行し、その出力からサービスが stopped かどうかを判断しています。
2台のインスタンスでhttpdサービスが停止している場合はSorryページに切り替え、1台でも動いている場合は通常ページに戻します。
このコードは、Lambda関数がEC2インスタンスでhttpdサービスの状態を監視し、必要に応じてロードバランサーのルールを変更するシナリオに対応しています。

## 追記メモ②
以下のように、OSの停止処理をhttpdサービスの停止に変更し、SSMのRun Commandを使用してhttpdサービスのステータスを確認するように修正しました。

```py
import boto3

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

    # 監視する2台のEC2インスタンスのID
    instance_ids = ["i-0458be1d8c980e8ec", "i-0b27db6a9b067f345"]
    
    # httpdサービスの状態を確認するためのSSMコマンド
    command = "systemctl is-active httpd"

    instances_stopped_count = 0

    for instance_id in instance_ids:
        # SSM Run Commandを実行
        response = ssm_client.send_command(
            InstanceIds=[instance_id],
            DocumentName="AWS-RunShellScript",
            Parameters={"commands": [command]},
        )

        # コマンド実行結果の取得
        command_id = response['Command']['CommandId']
        output = ssm_client.get_command_invocation(
            CommandId=command_id,
            InstanceId=instance_id
        )

        # httpdサービスが停止しているかを確認
        if 'inactive' in output['StandardOutputContent']:
            instances_stopped_count += 1

    if instances_stopped_count == 2:  # 2台ともhttpdサービスが停止している場合
        # Sorryページに切り替え
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 2
                },
            ]
        )
    else:
        # 通常ページに戻す（1台でもhttpdが起動している場合）
        response = elbv2_client.set_rule_priorities(
            RulePriorities=[
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_NORMAL_RULE_ARN',
                    'Priority': 1
                },
                {
                    'RuleArn': 'arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_SORRY_PAGE_RULE_ARN',
                    'Priority': 2
                },
            ]
        )

    # 現在のルール設定を確認
    result = elbv2_client.describe_rules(
        ListenerArn='arn:aws:elasticloadbalancing:ap-northeast-1:YOUR_LISTENER_ARN'
    )
    return result
```


はい、SSMエージェントがインストールされていれば問題ありません。

具体的には、以下の条件を満たしている必要があります：

SSMエージェントのインストール:

監視対象のEC2インスタンスにSSMエージェントがインストールされている必要があります。多くのAmazon LinuxやUbuntuの最新AMIには、SSMエージェントがデフォルトでインストールされていますが、手動でインストールが必要な場合もあります。
SSMエージェントの実行:

インスタンスでSSMエージェントが正常に実行されていることを確認してください。エージェントが停止していると、Run Commandが実行できません。
IAMロールの設定:

EC2インスタンスにSSMアクセス権限が付与されたIAMロールをアタッチしている必要があります。AmazonSSMManagedInstanceCoreというポリシーが含まれたIAMロールを付与すると、SSMと正常に通信できます。
この設定が整っていれば、SSM Run Commandを使用して、httpdサービスの状態を確認することができます。