CloudWatch Alarmでアラーム発生時にStep Functionsステートマシンを起動する

2021.11.08

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、CloudWatch Alarmでアラーム発生時にStep Functionsステートマシンを起動する方法を確認してみました。

ステートマシンはどこから起動できるか

まず、なるべく簡潔な構成にするという前提で、アラーム発生時にステートマシンをどのようにして起動できるかを確認してみます。

CloudWatch Alarmの場合

まずCloudWatch Alarmからステートマシンを直接起動できるか。

CloudWatch Alarmのアクションの設定を見ると、ターゲットとしてSNS Topicしか指定できないようです。

ドキュメントを見る限りでもアラームアクションへのSNS Topic以外のサービスの指定については記述がありません。

よってCloudWatch Alarmからはステートマシンの直接起動はできないようです。

SNS Topicの場合

続いてCloudWatch Alarmのアクションの唯一の発行先となるSNS Topicからは起動できるのか。

Topicのサブスクリプションの設定を確認すると、サブスクライブするエンドポイントのプロトコルは以下が指定可能となっています。ステートマシンは一覧にありません。

  • HTTP/HTTPS
  • Email/Email-JSON
  • Amazon Kinesis Data Firehose
  • Amazon SQS
  • AWS Lambda
  • プラットフォームアプリケーションエンドポイント
  • SMS

このうち聞き慣れないものとしてプラットフォームアプリケーションエンドポイントがありますが、こちらはモバイルアプリケーションのエンドポイントに通知を送信する際に使用するものとのことです。

なのでステートマシンのArnを指定しようとしてもエラーとなります。

考えられる構成

よって現時点で考えられる、アラーム発生時にステートマシンを起動させる最も簡単な構成は以下となりそうです。

実装

上記構成を実装してみます。

CDKコード

lib/aws-cdk-app-stack.ts

import * as cdk from '@aws-cdk/core';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as cloudwatchActions from '@aws-cdk/aws-cloudwatch-actions';
import * as sns from '@aws-cdk/aws-sns';
import * as lambda from '@aws-cdk/aws-lambda';
import * as lambdaNodejs from '@aws-cdk/aws-lambda-nodejs';
import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources';
import * as iam from '@aws-cdk/aws-iam';

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

    //SNS Topic
    const cloudwatchAlarmNotifyTopic = new sns.Topic(
      this,
      'cloudwatchAlarmNotifyTopic'
    );

    //CloudWatch Alarm
    const sampleAlarm = new cloudwatch.Alarm(this, 'sampleAlarm', {
      alarmName: 'sampleAlarm',
      metric: new cloudwatch.Metric({
        namespace: 'StateMachinePublish',
        metricName: 'temperature',
      }),
      evaluationPeriods: 1,
      threshold: 5,
      statistic: cloudwatch.Statistic.MAXIMUM,
      comparisonOperator:
        cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
      period: cdk.Duration.minutes(3),
    });

    //Alarmアクション追加
    sampleAlarm.addAlarmAction(
      new cloudwatchActions.SnsAction(cloudwatchAlarmNotifyTopic)
    );

    //ステートマシン
    const stateMachine = new sfn.StateMachine(this, 'sampleStateMachine', {
      stateMachineName: 'sampleStateMachine',
      definition: new sfn.Pass(this, 'pass', {}),
    });

    //Lambda関数
    const startStateMachineFunc = new lambdaNodejs.NodejsFunction(
      this,
      'startStateMachineFunc',
      {
        functionName: 'startStateMachineFunc',
        entry: 'src/lambda/startStateMachineHandler.ts',
        handler: 'handler',
        runtime: lambda.Runtime.NODEJS_14_X,
        environment: {
          STATE_MACHINE_ARN: stateMachine.stateMachineArn,
        },
      }
    );

    //ステートマシン起動権限付与
    startStateMachineFunc.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ['states:StartExecution'],
        resources: [stateMachine.stateMachineArn],
      })
    );

    //Lambdaイベントソース追加
    startStateMachineFunc.addEventSource(
      new SnsEventSource(cloudwatchAlarmNotifyTopic)
    );
  }
}

Lambdaコード

src/lambda/startStateMachineHandler.ts

import * as AWS from 'aws-sdk';

const STATE_MACHINE_ARN = process.env.STATE_MACHINE_ARN!;

const sfn = new AWS.StepFunctions();

export const handler = async () => {
  await sfn
    .startExecution({
      stateMachineArn: STATE_MACHINE_ARN,
    })
    .promise();
};

動作

監視対象のメトリクスがしきい値を下回り、CloudWatch Alarmがアラーム状態となりました。

するとLambda実行を介してステートマシンがちゃんと起動しました。

まとめ

  • CloudWatch Alarmのアラーム発生時にステートマシンを起動したい場合、現在取りうる最も簡潔な構成は、CloudWatch Alarm -> SNS topic -> Lambda関数 -> ステートマシンとなる。
  • せめてSNS topicからステートマシンを直接起動できるようになると嬉しい。

参考

以上