Amazon EventBridge SchedulerでCloudWatchアラームを定期的に無効化・有効化するCloudFormationテンプレートを作成してみた

コード管理ができるよう、一番シンプルな設定ですが、Amazon EventBridge Schedulerを使ったCloudWatchアラームの計画的なダウンタイム設定のためのCloudFormationのテンプレートを作ってみたのでご共有します。
2023.04.24

データアナリティクス事業本部の鈴木です。

Amazon EventBridge Schedulerを使ったCloudWatchアラームの計画的なダウンタイム設定を簡単に実装できるよう、CloudFormationのテンプレートを作ってみたのでご紹介します。

Amazon EventBridge Schedulerを使ったCloudWatchアラームの計画的な無効化・有効化について

CloudWatchアラームはAPIやAWS CLI、およびコンソールから無効化・有効化ができます。API実行はAmazon EventBridge Schedulerのユニバーサルターゲットに指定することで、定期的な実行が可能です。

Amazon EventBridge Schedulerは、2022年の11月にリリースされたEventBridgeの機能です。

このサービスについては以下のブログ記事が大変参考になります。

今回はCloudWatchアラームのEnableAlarmActionsAPIおよびDisableAlarmActionsAPIを呼び出し、計画的なダウンタイム設定を実現するためのEventBridge Schedulerのテンプレートを作成しました。

なお、以前にはMetric Mathを使用してダウンタイムを設定する方法をご紹介しました。Metric Mathを使用しても実装なしで計画的なダウンタイムの設定が実現できますが、EventBridge Schedulerを使えば、単に設定した時間に無効化・有効化を実行してくれるのでシンプルで分かりやすいですね。

検証した内容

以下のような構成で、EventBridge Schedulerから指定した時間にCloudWatchアラームを無効化・有効化し、無効化期間中はSNSトピック経由でメール通知がされないことを確認しました。

検証した構成

やってみた

1. スケジュールの準備

ここでは、作成したEventBridge Scheduler用のテンプレートをご紹介します。

Lambda関数およびCloudWatchアラームについては今回紹介したいことの本題ではないので、補足として後ほど記載します。

EventBridge Scheduler用のテンプレートは以下になります。

AWSTemplateFormatVersion: '2010-09-09'
Description:  EventBridge Scheduler to enable/disable CloudWatch Alarm
Parameters:
  CloudWatchAlarmName:
    Type: String
  ScheduleEnableTime:
    Type: String
  ScheduleDisableTime:
    Type: String
  ScheduleTimezone:
    Type: String
    Default: Japan
Resources:
  ScheduleCloudWatchAlarmEnable:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub 'CloudWatchAlarmEnable${CloudWatchAlarmName}'
      Description: Enable CloudWatch Alarm
      ScheduleExpression: !Ref ScheduleEnableTime 
      ScheduleExpressionTimezone: !Ref ScheduleTimezone
      FlexibleTimeWindow:
        Mode: "OFF"
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:cloudwatch:enableAlarmActions
        Input: !Sub "{\"AlarmNames\": [\"${CloudWatchAlarmName}\"]}"
        RoleArn:
          Fn::GetAtt:
          - SchedulerCWAEnableDisableRole
          - Arn
  ScheduleCloudWatchAlarmDisable:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub 'CloudWatchAlarmDisable${CloudWatchAlarmName}'
      Description: Disable CloudWatch Alarm
      ScheduleExpression: !Ref ScheduleDisableTime 
      ScheduleExpressionTimezone: !Ref ScheduleTimezone
      FlexibleTimeWindow:
        Mode: "OFF"
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:cloudwatch:disableAlarmActions
        Input: !Sub "{\"AlarmNames\": [\"${CloudWatchAlarmName}\"]}"
        RoleArn:
          Fn::GetAtt:
          - SchedulerCWAEnableDisableRole
          - Arn
  SchedulerCWAEnableDisableRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - scheduler.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: CWAEnableDisable
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - cloudwatch:enableAlarmActions
                  - cloudwatch:disableAlarmActions
                Resource:
                  - "*"

テンプレートでは以下を作成します。

  • CloudWatchアラームを無効化するEventBridge Schedulerのスケジュール
  • CloudWatchアラームを有効化するEventBridge Schedulerのスケジュール
  • EventBridge Schedulerで使用するIAMロール

テンプレートはEventBridge Schedulerを使ってEC2を定期起動・停止するCloudFormationテンプレートを参考にしました。

スケジュールから、ユニバーサルターゲットを使ってCloudWatchのAPIを実行しました。ユニバーサルターゲットについては、ユーザーガイドの以下のページの説明を参照ください。

CloudWatchのAPIは、以下のAmazon CloudWatchのAPI Referenceを参考にしました。

CloudFormationでデプロイし、以下のようにスケジュールが作成されていることを確認しました。

スケジュールの確認1

スケジュールの確認2

スケジュールの確認3

ScheduleEnableTimeScheduleDisableTimeは、以下のようにcronの設定を入力しました。

# パラメータ 入力した値
1 ScheduleDisableTime cron(35 13 ? * 2 *)
2 ScheduleEnableTime cron(45 13 ? * 2 *)

スケジュールの指定方法は、ユーザーガイドの以下のページに説明がありました。

2. 無効化前に通知がくることを確認する

まず、無効化前にCloudWatchアラームをトリガーにメール通知が来ることを確認しました。CloudWatchアラームをOK状態からアラーム状態に遷移させてみました。

以下のようにアクションが有効になっていますであることを確認して、アラーム状態に遷移させました。

有効な状態での動作確認1回目アラーム状態

メールが送付されました。

有効な状態での動作確認1回目メール

履歴タブからもアクションが実行されたことを確認できました。

有効な状態での動作確認1回目履歴

3. 無効化後に通知がこないことを確認する

無効化される時間を待ち、EventBridge Schedulerにより、CloudWatchアラームが無効化されたことを確認しました。

無効化されたことを確認

確かに、アクションが無効になっていますとなっています。

無効になっていることの確認

CloudWatchアラームをOK状態からアラーム状態に遷移させてみました。

無効化された後にアラームに遷移

このとき、メールは送付されませんでした。確かにアクションも実行されていませんでした。

アクションが実行されていないこと

4. 再有効化後に通知がくることを確認する

有効化される時間を待ち、EventBridge Schedulerにより、CloudWatchアラームが再び有効化されたことを確認しました。

有効な状態での動作確認2回目

CloudWatchアラームをOK状態からアラーム状態に遷移させてみました。

有効な状態での動作確認2回目アラームへの遷移

メールが送付されました。

有効な状態での動作確認2回目メール

最後に

Amazon EventBridge Schedulerを使って、CloudWatchアラームの計画的なダウンタイム設定を簡単に実装できるよう、CloudFormationのテンプレートを作ってみたのでご共有しました。

ユニバーサルターゲットでのAPIの指定方法は最初はユーザーガイドを読んで使い方を理解する必要がありますが、各種サービスのAPIリファレンス記載のAPIと合わせて様々な操作が自動化できとても良いですね。

より実践的には、Amazon EventBridge Schedulerのデッドレターキュー(DLQ)を使ってみたのようにデッドレターキューの設定をしておくと、エラー発生時に原因調査が楽になります。

CloudWatchアラームの計画的なダウンタイム設定がしたい方や、EventBridge SchedulerのスケジュールのCloudFormationテンプレートのサンプルが欲しい方の参考になりましたら幸いです。

補足

使用したLambda関数について

AWS CDK v2で作成しました。

以下のようにプロジェクトを作成しました。

# CDK用のディレクトリの作成
mkdir cdk_cw_lambda

# CDKプロジェクトの初期化
cd cdk_cw_lambda
cdk init sample-app --language typescript

# Lambda関数のスクリプト用のディレクトリの作成
mkdir lambda_function

以下のようにLambda用のスタックを作成するためのスクリプトを作成しました。

lib/cdk_cw_lambda-stack.ts

import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import {
  aws_iam as iam,
  aws_lambda as lambda,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkCwLambdaStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const lambdaPolicy = new iam.ManagedPolicy(this, 'LambdaPolicy', {
      managedPolicyName: 'lambdaPolicy',
      description: 'Lambda execution policy',
      statements: [
        new iam.PolicyStatement({
          effect: iam.Effect.ALLOW,
          actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
          resources: ['*'],
        }),
      ],
    });
    const lambdaRole = new iam.Role(this, 'lambdaRole', {
      roleName: 'lambdaRole',
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    });
    
    lambdaRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchFullAccess'));

    const sampleLambda = new lambda.Function(this, 'cm-nayuts-sample-lambda', {
      runtime: lambda.Runtime.PYTHON_3_9,
      handler: 'function.lambda_handler',
      code: lambda.Code.fromAsset('lambda_function'),
      role: lambdaRole
    });
  }
}

Lambda関数で実行するPythonスクリプトは以下のようにしました。valueを変えることでCloudWachメトリクスにUnitがCountのメトリクスをPUTできます。

lambda_function/function.py

import os
import json

import boto3


cloudwatch = boto3.client("cloudwatch")


def lambda_handler(event, context):
    """ 
    """
    value = 1

    cloudwatch.put_metric_data(
        MetricData=[
            {
                "MetricName": "CmSmapleMetric",
                "Dimensions": [
                    {
                        "Name": "DimensionNAME",
                        "Value": "SampleDimension",
                    },
                ],
                "Unit": "Count",
                "Value": value,
            },
        ],
        Namespace="SampleCustomNamespace",
    )

    return

以下のようにcdk deployでデプロイしました。

cdk deploy --profile プロファイル名

--profileオプションについては、AWS CDK Toolkit (cdk command) - AWS Cloud Development Kit (AWS CDK) v2Specifying Region and other configurationをご確認ください。

使用したCloudWatchアラームおよびSNSトピックについて

以下のCloudFormationテンプレートで作成しました。

AWSTemplateFormatVersion: "2010-09-09"
Description: CloudWatch Alarm Sample Stack

Parameters:
  SampleDestinationEmail:
    Description: Destination Email for SNS
    Type: String

Resources:
  # CloudWatchアラーム用のSNSトピック
  CloudWatchAlarmTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: CmNayutsCloudwatchAlarmTopic
      Subscription:
        - Endpoint: !Sub ${SampleDestinationEmail}
          Protocol: email

  # CloudWatchアラームの定義
  LambdaFunctionErrorsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: CmNayutsSampleAlarm
      Namespace: SampleCustomNamespace
      Dimensions:
        - Name: DimensionNAME
          Value: SampleDimension
      MetricName: CmSmapleMetric
      ComparisonOperator: LessThanThreshold  # 閾値以上
      Period: 60  # 期間[s]
      EvaluationPeriods: 1  # 閾値を超えた回数
      Statistic: Maximum  # 最大
      Threshold: 1  # 閾値
      TreatMissingData: notBreaching  # Errorsがない場合は良好として扱う
      AlarmActions:
        - !Ref CloudWatchAlarmTopic  # アラーム遷移時のアクション

テンプレートはCloudFormation で AWS Chatbot と CloudWatch Alarm を定義してSlackにアラート通知してみたを参考に作成しました。