CloudWatch アラーム の定期無効化・有効化

通知の垂れ流しは精神衛生上良くないです

AWSでの監視に関して仕事を通して改めて触れる機会があったので、改めて考えてみることにしました。
ClouwdWatch アラームを使って監視をするのは良いですが、実際のところお一人様や狭い範囲の固定メンバで運用するのであれば作業やらメンテのイベントで通知があっても無視すればよいのですが関わる人が増えればそうも行かないわけでメール垂れ流すのも無駄なので自動でアラームを無効・有効化したり手動で有効・無効を操作をLabmdaを使ってできるようにします。

Labmda用のIAMポリシー、ロールの作成

Labmda制御用のIAMポリシーとロールを用意します。
以下のポリシーを「cloudwatch-down-up-policy」と名前で作ります。
アラームの有効化、無効化と情報取得の権限を付与します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudwatch:EnableAlarmActions",
                "cloudwatch:DisableAlarmActions",
                "cloudwatch:DescribeAlarms"
            ],
            "Resource": "*"
        }
    ]
}

作成したポリシーを同じく「cloudwatch-down-up-policy」としてエンティティにLamdaを指定して作成します。
作成した「cloudwatch-down-up-policy」と「AWSXRayDaemonWriteAccess」、「AWSLambdaBasicExecutionRole」の二つのAWS管理ポリシーを割り当てます。

Labmda関数の作成

Labmda関数を作成します。

  1. 「一から作成」を選択
  2. 関数名を入力(任意のわかりやすい名前)
  3. Python3.8を選択(たぶん3.9でも動くと思うけど念の為)
  4. x86_64を選択
  5. アクセス制御に「既存のロールを使用するを選択」
  6. 制御ロールに作成した「cloudwatch-down-up-policy」を選択

作成後、「コード」タブを選択してlambda_function.pyにコードを入力。
今回作成して試したコードは以下。流れ的には以下のような感じで作りました。
まあ、突っ込みどころはあるかと思いますがとりあえず動きます。
事前のステータス判定のため、アラームの有効、無効状態をcheck_point_cloudwatch_alarm(alarm_name)で取得。
lambda_handler(event, context)で引数で指定したアラームと下したdisabled(無効)、enabled(有効)に状態を切り替えます。
念の為に自動で定期実行させる時のしくじり検知用に事前のステータスチェックで既に無効になっていたり、有効になっているとエラーログを吐いて終了するようにしてあります。

from urllib import response
import boto3
import logging
import sys

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def check_point_cloudwatch_alarm(alarm_name):
    cloudwatch = boto3.client('cloudwatch')
    response = cloudwatch.describe_alarms(AlarmNames=[alarm_name])
    return(f"ActionsEnabled:{response['MetricAlarms'][0]['ActionsEnabled']}")
    

    logger.info(alarm_name)
    logger.info(response)

def lambda_handler(event, context):
    cloudwatch = boto3.client('cloudwatch')
    alarm_name=event['alarm_name']
    state=event['state']
    
    if state == 'enabled':
        logger.info(alarm_name)
        logger.info("target alamem enabl start")
        if check_point_cloudwatch_alarm(alarm_name) == 'ActionsEnabled:False':
            logger.info("disabled stat check OK")
        elif check_point_cloudwatch_alarm(alarm_name) != 'ActionsEnabled:False':
            logger.error(alarm_name)
            logger.error("action change Fail. target alamem enabled")
            sys.exit()            
        cloudwatch.enable_alarm_actions (
            AlarmNames=[alarm_name]
        )
        logger.info(alarm_name)
        logger.info("action Success. target alamem enabled")
    elif state == 'disabled':
        logger.info(alarm_name)
        logger.info("target alamem disable start")
        if check_point_cloudwatch_alarm(alarm_name) == 'ActionsEnabled:True':
            logger.info("enabled stat check OK")
        elif check_point_cloudwatch_alarm(alarm_name) != 'ActionsEnabled:True':
            logger.error(alarm_name)
            logger.error("action change Fail. target alamem disabled")
            sys.exit()  
        cloudwatch.disable_alarm_actions (
            AlarmNames=[alarm_name]
        )
        logger.info(alarm_name)
        logger.info("action Success. target alamem disabled")

動作確認(というか手動で実行する場合はこれ)

「テスト」タブに移動してテストイベントを作成します。
「新しいイベント」を選択し、テンプレートに「mobile-backend-event」を選択します。
「名前」にはわかりやすい名前を任意で入力して、イベントドキュメントにJSON形式で記載します。
変更を保存をして保存すると以降、保存されたイベントで選択できます。
画像はWAFのテストとしてWAFの検知イベントのアラーム名を指定しています。

イベントドキュメントは以下
アラーム無効化の場合

{
  "alarm_name": "【無効化したいアラームのアラーム名】",
  "state": "disabled"
}

{
  "alarm_name": "【無効化したいアラームのアラーム名】",
  "state": "enabled"
}

保存したら実際にテストしてます。成功なら以下の用に表示されます。

実際に対象のアラームを確認して以下の用に無効化されれば成功です。

ClouwdWtachのロググループに実行ログも出力されているはずなので確認します。
ロググループ配下に「/aws/labmda/【作成した関数名】」を選択し確認します。
もしなかったら、ロググループの作成で「/aws/labmda/【作成した関数名】」で作成してください。

無効化の正常な場合のログ

有効化の正常な場合のログ

既に無効化されているのに無効化が行われた場合の異常終了時

既に有効化されているのに有効化が行われた場合の異常終了時

おまとめ

元々、ALBでターゲットグループの切り離し検知を入れていたらお銭の都合で夜間止めることになったでござるで毎日通知が鬱陶しいので急ごしらえしたものです。
EventBridgeでcron式でスケジュールし、ターゲットで作成したLabmdaを指定し、定数 (JSON テキスト)に「テスト」で入れた書式と同様に指定すれば定期の実行も可能です。