EC2のシステムアラート作成・削除をEventBridge+Lambdaで自動化してみた
【はじめに】
EC2の死活監視を入れる際は、システムステータスを示す以下のCloudWatchメトリクスからアラートを作成しますよね。
・StatusCheckFailed
インスタンス内のシステムエラー(メモリ、破損ファイル、起動設定など)とAWS側で管理するハードウェアのエラー(ネットワークやシステム電源、物理マシンに搭載するソフトウェアなど)を検知してくれるメトリクス
メトリクスの詳細は公式ドキュメントを見てください。保守の際に、サーバが増えて同じ条件でアラートを手動設定する必要があったり、Autoscalingを使った自動増減の際にアラートを個々に設定するのが困難だったりします。そこで、今回はアラート設定を「自動化」してみたいと思います。
今回は下記作業を自動化する仕組みを作ってみようと思います。
・インスタンスを作成したときに指定したアラートが付与される
・インスタンスが削除されたときに指定したアラートが削除される
【構成の概要】
構成図は下記のとおりです。
赤枠部分の検証を主に行います。
簡単に説明すると、以下の流れになります。
①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情報)
-
トリガーにEventBridgeを設定忘れしないようにしましょう。
IAM
LambdaがCloudWatchでアラームを作成、削除、閲覧するための権限を付与します。
- "cloudwatch:PutMetricAlarm"
- "cloudwatch:DeleteAlarms"
- "cloudwatch:DescribeAlarms"
EventBridge
以下のように設定を行います。
①ルール名: EventBrideの表示ルール名
②イベントパターン:
- サービス名: EC2
- イベントタイプ: EC2 Instance State-change Notification
- 特定の状態: running, terminated,stopped
- アカウント内のインスタンスすべてをルールの適用範囲にする
③ターゲットはLambdaを指定する。
【動作確認】
動作を確認する前に、Lambdaのコードは最新のものをデプロイしておきます。
①EC2を新規作成して起動した場合の挙動(起動操作はスキップします)
数秒すると、ロググループに以下のメッセージが出ました。
アラーム作成成功のメッセージが出ているので、実際に作成されているか確認します。
作成されていることが確認できました!
②作成されているインスタンスを削除した場合の挙動(削除操作はスキップします)
数秒すると、ロググループに以下のメッセージが出ました。
アラーム削除成功のメッセージが出ているので、実際に削除されているか確認します。
削除されていることが確認できました!
*一応、EC2が停止した場合の挙動も確認すると、アラート維持で「keeping alarm intact」のメッセージが出力されました。
【さいごに】
EventBridge→Lambda→SNSの構成は結構ありふれているような気がしますが、使い方に慣れておくと、今回のケースだけではなく、様々な場面で活用できそうです。運用を効率化するためのツールの1つとして学習できてよかったです。