SSM State Manager の各ノードに紐づいている関連付け数を監視する仕組みを、EventBridge + Lambda 関数 を使って実装してみた

2024.05.01

はじめに

テクニカルサポートの 片方 です。
コツコツと積み重ね続け、ついにブログ執筆数が 200 本に迫りました。そのため 199 本目の本ブログと、記念すべき 200 本目、201 本目のブログは Lambda 関数を利用した内容を紹介します。
こちらは、私の所属するテクニカルサポートチームへ「〇〇〇 を実現するサービスや機能はないか」と実際にお客様より頂いたお問い合わせを参考 (ヒント) にしてカスタムソリューションを作成してみました。

本ブログでは、SSM State Manager の各ノードに紐づいている関連付け数を、Lambda 関数を使って CloudWatch カスタムメトリクスとして表示させてみました。

背景

SSM の State Manager で各マネージドノードに対しての関連付け数は 20 が上限となっています。この上限に抵触している場合は、ステータスが "Failed" 、詳細ステータスが "LimitExceeded" になり関連付けが失敗します。

各マネージドノードは、最大 20 件の関連付けの対象にすることができます。

こちらの数を確認したい場合はマネジメントコンソール画面から確認する方法が存在せず、現時点では AWS CLI コマンドを実行して確認する手段になります。
そのため、SSM State Manager の各ノードに紐づいている関連付け数を、Lambda 関数 を使って CloudWatch カスタムメトリクスとして表示させてみました。
カスタムメトリクスとして可視化できれば、EventBridge で Lambda 関数 を定期的に呼び出し CloudWatch アラームなどを利用して、上限に抵触しそうなことを早期に検知可能です。

構成

EventBridge で Lambda 関数 を定期的に呼び出します。Lambda 関数内の "DescribeInstanceAssociationsStatus" より各ノードに紐づいている関連付けの一覧を取得して、こちらの結果から関連付けられている総数を取得する流れです。

以下はイメージです。

やってみた

ロール

Lambda 関数にアタッチするロールを作成します。信頼関係は以下です。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

ロール名: LambdaFunctionCheckStateManagerStatusRole

アタッチするポリシー例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ec2:DescribeInstances",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ssm:DescribeInstanceAssociationsStatus",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "cloudwatch:PutMetricData",
            "Resource": "*"
        }
    ]
}

Lambda 関数

ランタイムは Python 3.12 を使用しました。
アタッチする実行ロールは、先ほど作成した "LambdaFunctionCheckStateManagerStatusRole" を選択します。

関数名: LambdaFunctionCheckStateManagerStatus

冒頭でお伝えした通り、この Lambda 関数の目的は、SSM State Manager の マネージドノード (EC2 インスタンス) の関連付け数を取得し、その情報を CloudWatch カスタムメトリクスとして送信することです。
これにより、各マネージドノード毎にどれだけ関連付けられているかを監視できます。

サンプルコード
import boto3

def get_association_count(instance_id):
    ssm_client = boto3.client('ssm')
    response = ssm_client.describe_instance_associations_status(InstanceId=instance_id)

    association_count = 0
    if 'InstanceAssociationStatusInfos' in response:
        association_count = len(response['InstanceAssociationStatusInfos'])

    return association_count

def send_cw_metric(metric_dimension_value, metric_value):
    cw_client = boto3.client('cloudwatch')

    try:
        cw_client.put_metric_data(
            Namespace='SSM State Manager',
            MetricData=[
                {
                    'MetricName': 'State Count',
                    'Dimensions': [
                        {
                            'Name': 'Instance',
                            'Value': metric_dimension_value
                        }
                    ],
                    'Value': metric_value,
                    'Unit': 'Count'
                }
            ]
        )
    except Exception as e:
        print(e)

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    instances = ec2_client.describe_instances()

    for reservation in instances['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            association_count = get_association_count(instance_id)
            send_cw_metric(instance_id, association_count)

EventBridge

作成した Lambda 関数と同じリージョンで実装してください。

ルール名: LambdaFunctionCheckStateManagerStatusRule

イベントバスは default で、ルールタイプはスケジュールを選択します。

通常のレートで実行されるスケジュール (10 分ごとなど)。を選択します。
rate 式は、今回 6 時間 に設定しました。こちらはお好みで設定してください。

ターゲットのセクションで、ターゲットタイプを AWS のサービスを選択し、ターゲットを選択では Lambda 関数 を選択します。
先ほど作成した関数名 "LambdaFunctionCheckStateManagerStatus" を選択してルール作成を行えば定期実行の設定は終了です。

以上で実装は終了です。お疲れさまでした。

検証してみた

検証用に EC2 インスタンスを 3 台起動させてマネージドノードにします。マネージドノードにする方法は割愛させて頂きます。

State Manager より、AWS-RunPatchBaseline を検証用マネージドノードに全て関連付けしました。

AWS-UpdateSSMAgent を SSM State Manager Server-2 と SSM State Manager Server-3 にのみ関連付けしました。

AWS-UpdateEC2Config をSSM State Manager Server-3 にのみ関連付けしました。

現在では、以下の関連付け数が想定されます。

  • i-038f4f0ae83c89ba0 Test-StateManager-1 合計 1
  • i-02ca225730294b253 Test-StateManager-2 合計 2
  • i-01388f492127f08ae Test-StateManager-3 合計 3

念のため、AWS CLI コマンドで関連付け数を確認します。結果、想定通りでした。
"aws ssm describe-instance-associations-status --instance-id i-xxxxxxxxxxxx | jq '.InstanceAssociationStatusInfos | length'"

作成した、Lambda 関数をテストします。
遅延して上手く処理がされないといった状況であれば、Lambda 関数の一般設定より "タイムアウト値" を伸ばす、"メモリ" を増やすなどのご対応をしてください。

成功したので CloudWatch カスタムメトリクスとして表示されているか確認します。
問題なく表示されていることを確認しました。なお、統計は最小でご確認ください。

次は、関連付けを削除してみます。
AWS-RunPatchBaseline の関連付けのみ残しているので、どのマネージドノードも関連付け数は 1 が想定されます。

念のため、AWS CLI コマンドで関連付け数を確認します。

想定通り、どのマネージドノードも関連付け数は 1 でした。改めて CloudWatch のカスタムメトリクスを確認します。 結果、全て 1 であることを確認しました。成功です!

関連付けを全て削除しました。

再度 CloudWatch のカスタムメトリクスを確認します。こちらも成功です!

まとめ

通知する場合は CloudWatch アラームの作成をご検討ください。本ブログが誰かの参考となれば幸いです。

参考資料

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

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