Lambda 関数を利用して、SSM Ping ステータス監視による EC2 Auto Scaling グループのインスタンス自動置換を実装してみた

Lambda 関数を利用して、SSM Ping ステータス監視による EC2 Auto Scaling グループのインスタンス自動置換を実装してみた

Clock Icon2025.03.07

はじめに

テクニカルサポートの 片方 です。
今回は、Lambda 関数を利用して、何らかの EC2 Auto Scaling グループに所属する EC2 インスタンスが、SSM フリートマネージャーコンソール画面の Ping ステータスが "接続が失われました(Connection Lost)" の場合に、対象グループからデタッチし新規に EC2 インスタンスが起動して入れ替えを行うように自動処理を実装してみました。
なお、こちらは実際に当サポート窓口へ寄せられたお問い合わせをヒントに、カスタムソリューションとして考えました。

背景

EC2 Auto Scaling におけるデフォルトの EC2 ステータスチェック (ヘルスステータス) が "Unhealthy" と判断されるには、インスタンスの状態が runnning 以外の場合、もしくはステータス impaired の場合に判断されます。
そのため、SSM フリートマネージャーコンソール画面で Ping ステータスが "接続が失われました(Connection Lost)" の状態であっても、EC2 Auto Scaling グループ側では "Unhealthy" と判断されません。
このような状況に対応するため、カスタムソリューションを考えてみました。

https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/health-checks-overview.html

ステータスチェック
Amazon EC2 Auto Scaling は、Amazon EC2 インスタンスのステータスチェックとシステムステータスチェックの結果を使用して、インスタンスのヘルスステータスを判断します。インスタンスが running 以外の Amazon EC2 状態である場合、またはステータスチェックのステータスが impaired になった場合、Amazon EC2 Auto Scaling はインスタンスを異常であると見なし、そのインスタンスを置き換えます。

実装してみた

以下の順番で実装します。

  • 実行ロール作成
  • Lambda 関数作成

EC2 Auto Scaling の作成については省略させていただきます。弊社ブログを参考に作成してください。
https://dev.classmethod.jp/articles/how-to-create-ec2-auto-scaling-group/

実行ロール

※ 適宜修正してください。

信頼関係
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
アタッチするポリシー例
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:DescribeInstanceInformation",
                "ssm:DeregisterManagedInstance"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:TerminateInstances",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DetachInstances"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup"
            ],
            "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/lambda/${FUNCTION_NAME}:*"
            ]
        }
    ]
}

Lambda 関数

Python 3.13 で作成しました。
実行ロールでは、既存のロールを使用するを選択し、先ほど作成したロールを指定します。

※ 適宜修正してください。

Lambda 関数例
import boto3
import logging
from datetime import datetime, timezone

# ロガーの設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handle_managed_instance(ssm_client, instance_id):
    """ハイブリッドインスタンス(mi-)の処理"""
    try:
        logger.info(f"Deregistering managed instance: {instance_id}")
        ssm_client.deregister_managed_instance(
            InstanceId=instance_id
        )
        logger.info(f"Successfully deregistered managed instance: {instance_id}")
    except Exception as e:
        logger.error(f"Error deregistering managed instance {instance_id}: {str(e)}")

def handle_ec2_instance(instance_id, asg_client, ec2_client):
    """EC2インスタンス(i-)の処理"""
    try:
        # インスタンスの存在確認
        ec2_response = ec2_client.describe_instances(
            InstanceIds=[instance_id]
        )
        logger.info(f"EC2 instance details: {str(ec2_response)}")

        # AutoScalingGroup情報の取得
        asg_response = asg_client.describe_auto_scaling_instances(
            InstanceIds=[instance_id]
        )

        if not asg_response['AutoScalingInstances']:
            logger.info(f"Instance {instance_id} is not part of an ASG")
            return

        asg_name = asg_response['AutoScalingInstances'][0]['AutoScalingGroupName']

        # インスタンスのデタッチと終了
        logger.info(f"Detaching instance {instance_id} from ASG {asg_name}")
        asg_client.detach_instances(
            InstanceIds=[instance_id],
            AutoScalingGroupName=asg_name,
            ShouldDecrementDesiredCapacity=False
        )

        logger.info(f"Terminating instance {instance_id}")
        ec2_client.terminate_instances(
            InstanceIds=[instance_id]
        )
    except Exception as e:
        logger.error(f"Error processing EC2 instance {instance_id}: {str(e)}")

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

    try:
        ssm_instances = ssm_client.describe_instance_information()

        for instance in ssm_instances['InstanceInformationList']:
            instance_id = instance['InstanceId']
            ping_status = instance['PingStatus']
            resource_type = instance.get('ResourceType', '')

            logger.info(f"Processing instance: {instance_id}")
            logger.info(f"Resource type: {resource_type}")
            logger.info(f"Ping status: {ping_status}")

            if ping_status != 'ConnectionLost':
                continue

            if resource_type == 'ManagedInstance' or instance_id.startswith('mi-'):
                handle_managed_instance(ssm_client, instance_id)
            elif resource_type == 'EC2Instance' or instance_id.startswith('i-'):
                handle_ec2_instance(instance_id, asg_client, ec2_client)
            else:
                logger.warning(f"Unknown instance type: {instance_id}")

        return {
            'statusCode': 200,
            'body': 'Successfully processed instances'
        }

    except Exception as e:
        logger.error(f"Error in lambda execution: {str(e)}")
        raise

検証してみた

EC2 Auto Scaling グループに所属かつ、マネージドノードとして登録されるように起動します。

01

02

それでは、SSM フリートマネージャーコンソール画面で Ping ステータスが "接続が失われました(Connection Lost)" 状態となるように、検証として "インスタンスメタデータサービスの無効化" をします。

03

暫く待つと、Ping ステータスが "接続が失われました(Connection Lost)"になりました。

04

では 作成した Lambda 関数を実行(テスト)します。
成功しました!

05

入れ替えも行われてます!

06

まとめ

EventBridge のスケジュール機能を利用して、当該 Lambda 関数を定期実行させるなど合わせてご検討ください。
本ブログが誰かの参考になれば幸いです。

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-create-rule-schedule.html

ルールは、イベントに応じて実行したり、一定の時間間隔で実行したりすることができます。例えば、AWS Lambda 関数を定期的に実行するには、スケジュールに従って実行するルールを作成します。

参考資料

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.