AWS Step Functionsステートマシンが時間内に正常終了したか監視してみた

2022.11.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

定期実行しているAWS Step Functionsステートマシンが想定時間内に正常終了したか、EventBridge SchedulerとLambdaを使って監視する方法を紹介します。

前提とするステートマシン

依存関係のあるステートマシンや日次バッチが時間内に完了したか確認したいケースを想定し、ステートマシンの実行間隔が十分に長い定期実行は想定していますが、同じステートマシンがオーバーラップして実行されることは想定していません。

具体的には、1日に1回呼び出され、処理に3時間かかるステートマシンは想定内ですが、2時間間隔で呼び出され、処理に3時間かかり、実行がオーバーラップするようなステートマシンは想定外です。

また、EXPRESS ステートマシンも対象外です。

監視方法

Step Functions には ListExecutions という API が存在し、実行履歴を最新のものから時系列順に取得できます。

ステートマシンが午前0時に呼び出され、2時間には終わっているはずであれば、午前2時にLambda関数で最新のステータスを確認し、正常終了していなければ、SNSに通知します。

以上をまとめたCloudFormationテンプレートを本文最後に記載しています。

StepFunctions:ListExecutions API について

StepFunctions:ListExecutions APIを利用すると、実行履歴を時系列順に取得できます。 最新の実行だけを取得したい場合、 maxResults=1 と個数を指定します。

各実行のステータスは以下のいずれかの値をとります。

  • SUCCEEDED
  • RUNNING
  • FAILED
  • TIMED_OUT
  • ABORTED

SUCCEEDED ステータスだけが正常終了です。

RUNNING ステータスは遅延を意味し、残りのステータスは異常終了です。

AWS CLI からは、以下のように呼び出します。

$ aws stepfunctions list-executions \
  --state-machine-arn arn:aws:states:eu-west-1:12345789012:stateMachine:XXX \
  --max-items 1

Python SDKからは、以下のように呼び出します。

sfn = boto3.client('stepfunctions')
response = sfn.list_executions(
    stateMachineArn = "arn:aws:states:eu-west-1:12345789012:stateMachine:XXX",
    maxResults = 1
)
latest_activity = response['executions'][0]
status = latest_activity['status']
if status != 'SUCCEEDED':
    # TODO:異常系処理
    pass

定期呼び出し方法

EventBridge SchedulerあるいはEventBridge Ruleを利用すると、簡単に定期呼び出しできます。 後発のEventBridge Schedulerは、実行ウィンドウやタイムゾーンの指定など、より柔軟な設定が可能です。

各ステートマシンには

  • ステートマシンの実行
  • ステートマシン実行の監視

の2種類のスケジュール設定が必要です。

EventBridge SchedulerはたくさんのAWS APIをターゲットとして直接呼び出せます。

残念ながら、StepFunctions:ListExecutions APIは対象外だったため、Lambdaを挟んでいます。

通知

最新の実行が正常終了していなかった場合、SNS等を利用してエラー通知しましょう。

異常終了に限定すると、デッドレターキュー(DLQ)を利用すると、異常終了を速やかに検知できます。

Amazon EventBridge Schedulerのデッドレターキュー(DLQ)を使ってみた

CloudWatchメトリクスは利用しづらい

CloudWatchメトリクスには、ExecutionsSucceededExecutionsFailedExecutionTimeのように、各ステータスに対応するメトリクスが存在します。

ステータス毎にメトリクスが異なり、処理が遅延(RUNNING)している場合、実際に処理が終了するまで処理時間(ExecutionTime)のデータポイントが発生しななくて監視できないことから、今回のようにSUCCEEDED以外のすべてのステートを補足したいユースケースには向かないでしょう。

最後に

AWS Step Functionsステートマシンの実行をStepFunctions:ListExecutions APIを使って監視する方法を紹介しました。

実行スケジュールがシンプルであれば、それなりにカバーできるのではないかと思います。

それでは。

付録:CloudFormationテンプレート

今回紹介した構成のCloudFormationテンプレートです。

監視対象のステートマシンとエラー通知先のSNSトピックはすでに存在するものとします。

Lambdaに渡すステートマシンはEventBridgeのInputに利用し、Lambdaから送信するSNSトピックはLambdaの環境変数で管理しています。

このテンプレートでは

  • EventBridge Scheduler
  • EventBridge Rule

の2種類のスケジュールを作成しています。

好きな方を残してください。

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  SnsArn:
    Type: String
  SfnArn:
    Type: String

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties: 
      Handler: index.lambda_handler
      Code:
        ZipFile: !Sub |
          import boto3
          import os

          sfn = boto3.client('stepfunctions')
          sns = boto3.client('sns') 
          SNS_ARN = os.environ['SNS_ARN']

          def lambda_handler(event, context):
              sfn_arn = event['SFN_ARN']
              response = sfn.list_executions(
                  stateMachineArn = sfn_arn,
                  maxResults = 1
              )
              print(response)
              
              if response['executions']:
                  latest_activity = response['executions'][0]
                  status = latest_activity['status']
                  if status != 'SUCCEEDED':
                      print("last activity didn't succeed")
                      sns.publish(
                          TargetArn=SNS_ARN,
                          Message='state machine {} is in the state {}'.format(sfn_arn, status)
                      )
                  else:
                      print("last activity succeeded")
              else:
                  print("No acitivity found")

      Runtime: python3.9
      Timeout: 30
      Environment: 
        Variables:
          SNS_ARN : !Ref SnsArn
      Role: !GetAtt LambdaFunctionRole.Arn

  LambdaFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: function-nsn-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: !Ref SnsArn
                Effect: Allow
                Action:
                  - sns:Publish
              - Resource: "*"
                Effect: Allow
                Action:
                  - states:ListExecutions

  EventSchedule:
    Type: AWS::Scheduler::Schedule
    Properties: 
      GroupName: default
      ScheduleExpression: cron(0 0 * * ? *)
      ScheduleExpressionTimezone: Japan
      FlexibleTimeWindow:
        Mode: "OFF"
      State: ENABLED
      Target: 
        Arn: !GetAtt LambdaFunction.Arn
        Input: !Sub '{"SFN_ARN": "${SfnArn}"}'
        RoleArn: !GetAtt EventScheduleRole.Arn

  EventScheduleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - scheduler.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: CallStepFunctions
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeLambda
                Resource:
                  - !GetAtt LambdaFunction.Arn

  EventBridgeRule:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      ScheduleExpression: cron(0 15 * * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt LambdaFunction.Arn
          Id: LambdaFunction
          Input: !Sub '{"SFN_ARN": "${SfnArn}"}'
  LambdaEvent:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EventBridgeRule.Arn

AWS::Scheduler::ScheduleMode: "OFF" に関して、ダブルクオートを外して Mode: OFF とすると、次のエラーが発生します。

Properties validation failed for resource EventSchedule with message: #/FlexibleTimeWindow/Mode: #: only 1 subschema matches out of 2 #/FlexibleTimeWindow/Mode: failed validation constraint for keyword [enum]

ご注意ください。