EC2のシステムアラート作成・削除をEventBridge+Lambdaで自動化してみた

EC2のシステムアラート作成・削除をEventBridge+Lambdaで自動化してみた

Clock Icon2025.07.09

【はじめに】

EC2の死活監視を入れる際は、システムステータスを示す以下のCloudWatchメトリクスからアラートを作成しますよね。

・StatusCheckFailed
インスタンス内のシステムエラー(メモリ、破損ファイル、起動設定など)とAWS側で管理するハードウェアのエラー(ネットワークやシステム電源、物理マシンに搭載するソフトウェアなど)を検知してくれるメトリクス

メトリクスの詳細は公式ドキュメントを見てください。保守の際に、サーバが増えて同じ条件でアラートを手動設定する必要があったり、Autoscalingを使った自動増減の際にアラートを個々に設定するのが困難だったりします。そこで、今回はアラート設定を「自動化」してみたいと思います。

今回は下記作業を自動化する仕組みを作ってみようと思います。
・インスタンスを作成したときに指定したアラートが付与される
・インスタンスが削除されたときに指定したアラートが削除される

【構成の概要】

構成図は下記のとおりです。
スクリーンショット 2025-07-09 092548

赤枠部分の検証を主に行います。
簡単に説明すると、以下の流れになります。

①EC2の起動・停止・削除のイベントをEventBridgeのルールでキャッチ
②イベント処理のターゲットにはLambdaを設定し、CloudWatchアラームを自動作成・削除

EventBridgeから数秒以内にシームレス実行できるので、自動化はLambdaでやります。
言語はPythonです。(エラーキャッチ用にDLQとかSNSとかも一応用意してみましたが今回の趣旨ではありません。)

【工夫点】

自動化にあたり、エラーを考慮してコードを書かなければなりません。以下を考慮して実装してみました。

  • すでにEC2に該当のアラートがついてた場合は?
  • 削除じゃなくて、停止した場合は?

【準備】

下記のサービスをコンソール上で作成します。ゴールはアラームの自動作成・削除が正常にできるか確認することなので、デフォルト設定でいいものはデフォルトにします。以下の順番で作成するとスムーズです。

EC2

イベントをEventBridgeにキャッチさせるためだけに作成するので、一番安いインスタンスタイプとストレージを選んでおきます。ネットワークはプライベートサブネットに入れておきます。(あらかじめ作成済みであること)

SQS

EventBridgeのターゲットとして指定するDLQです。何らかの原因でイベントを検知できない場合を考えます。EventBridgeのルール再試行回数を超えた場合はDLQに移動し、エラーの原因調査をやったりということも考えました。

SNS

Lambdaで自動作成するCloudWatchアラームの通知先として利用します。通知方法はなんでもいいですが、サブスクリプションはEmailとかにします。

Lambda

  • コードは以下に記載します。
# 必要なライブラリのインポート
import boto3
import json
import os
import logging

# ログレベルの設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    cloudwatch = boto3.client('cloudwatch')
    logger.info(f"Received event: {json.dumps(event)}")

    # EC2インスタンスの起動イベント
    if event['detail']['state'] == 'running':
        instance_id = event['detail']['instance-id']
        logger.info(f"Processing running state for instance: {instance_id}")

        try:
            # アラームが既に存在するかチェック
            response = cloudwatch.describe_alarms(
                AlarmNames=[f'{instance_id}-StatusCheckFailed']
            )

            if response['MetricAlarms']:
                logger.info(f"Alarm already exists for instance: {instance_id}")
                return {
                    'statusCode': 200,
                    'body': json.dumps(f'Alarm already exists for {instance_id}')
                }

            # アラームを作成
            cloudwatch.put_metric_alarm(
                AlarmName=f'{instance_id}-StatusCheckFailed',
                ComparisonOperator='GreaterThanThreshold',
                EvaluationPeriods=2,
                MetricName='StatusCheckFailed',
                Namespace='AWS/EC2',
                Period=60,
                Statistic='Maximum',
                Threshold=0.0,
                ActionsEnabled=True,
                AlarmActions=[
                    os.environ['SNS_TOPIC_ARN']
                ],
                AlarmDescription='EC2 Status Check Failed',
                Dimensions=[
                    {
                        'Name': 'InstanceId',
                        'Value': instance_id
                    }
                ]
            )
            logger.info(f"Successfully created alarm for instance: {instance_id}")

        except Exception as e:
            logger.error(f"Failed to create alarm for {instance_id}: {str(e)}")
            raise

    # インスタンス終了時のみAlarmを削除(停止時は削除しない)
    elif event['detail']['state'] == 'terminated':
        instance_id = event['detail']['instance-id']
        logger.info(f"Processing terminated state for instance: {instance_id}")

        try:
            response = cloudwatch.describe_alarms(
                AlarmNames=[f'{instance_id}-StatusCheckFailed']
            )

            if response['MetricAlarms']:
                cloudwatch.delete_alarms(
                    AlarmNames=[f'{instance_id}-StatusCheckFailed']
                )
                logger.info(f"Successfully deleted alarm for instance: {instance_id}")
            else:
                logger.warning(f"No alarm found for instance: {instance_id}")

        except cloudwatch.exceptions.ResourceNotFound:
            logger.warning(f"Alarm not found for instance: {instance_id}")
        except Exception as e:
            logger.error(f"Failed to delete alarm for {instance_id}: {str(e)}")

    # 停止時は何もしない(アラームを保持)
    elif event['detail']['state'] == 'stopped':
        instance_id = event['detail']['instance-id']
        logger.info(f"Instance {instance_id} stopped - keeping alarm intact")

    # 想定外の状態
    else:
        instance_id = event['detail'].get('instance-id', 'unknown')
        state = event['detail'].get('state', 'unknown')
        logger.warning(f"Unhandled state '{state}' for instance: {instance_id}")

    return {
        'statusCode': 200,
        'body': json.dumps('Successfully processed EC2 state change')
    }

コードのロジックを簡単にまとめてみました。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
EC2起動状態検知

既存アラート確認
├─ 【アラートあり】→ ログ出力:「すでにあるよ!」
└─ 【アラートなし】→ アラーム作成 → ログ出力:「成功したよ!」

EC2削除状態検知

既存アラート確認
├─ 【アラートなし】→ ログ出力:「アラートはないよ!」
├─ 【その他エラー】→ ログ出力:「アラームを削除できなかった!」
└─ 【アラートあり】→ ログ出力:「成功したよ!」

EC2停止状態検知

処理なし
└─ ログ出力:「何もしない!」

EC2想定外状態検知(runnning,stopped,terminated以外のステータスの場合)

警告ログ出力
└─ 正常完了扱い
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

  • 環境変数をコード内で呼び出しているので、SNSのARN情報は以下形式で環境変数として設定しましょう。
    SNS_TOPIC_ARN(SNSのarn情報)
    スクリーンショット 2025-07-09 121258

  • トリガーにEventBridgeを設定忘れしないようにしましょう。
    スクリーンショット 2025-07-09 121132

IAM

LambdaがCloudWatchでアラームを作成、削除、閲覧するための権限を付与します。

  • "cloudwatch:PutMetricAlarm"
  • "cloudwatch:DeleteAlarms"
  • "cloudwatch:DescribeAlarms"

EventBridge

以下のように設定を行います。
①ルール名: EventBrideの表示ルール名
②イベントパターン:

  • サービス名: EC2
  • イベントタイプ: EC2 Instance State-change Notification
  • 特定の状態: running, terminated,stopped
  • アカウント内のインスタンスすべてをルールの適用範囲にする

③ターゲットはLambdaを指定する。

【動作確認】

動作を確認する前に、Lambdaのコードは最新のものをデプロイしておきます。
スクリーンショット 2025-07-09 122839

①EC2を新規作成して起動した場合の挙動(起動操作はスキップします)
数秒すると、ロググループに以下のメッセージが出ました。
スクリーンショット 2025-07-04 174125

アラーム作成成功のメッセージが出ているので、実際に作成されているか確認します。
スクリーンショット 2025-07-04 174241
作成されていることが確認できました!

②作成されているインスタンスを削除した場合の挙動(削除操作はスキップします)
数秒すると、ロググループに以下のメッセージが出ました。
スクリーンショット 2025-07-07 091258

アラーム削除成功のメッセージが出ているので、実際に削除されているか確認します。
スクリーンショット 2025-07-09 125019
削除されていることが確認できました!

*一応、EC2が停止した場合の挙動も確認すると、アラート維持で「keeping alarm intact」のメッセージが出力されました。
スクリーンショット 2025-07-04 174001

【さいごに】

EventBridge→Lambda→SNSの構成は結構ありふれているような気がしますが、使い方に慣れておくと、今回のケースだけではなく、様々な場面で活用できそうです。運用を効率化するためのツールの1つとして学習できてよかったです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.