CFNでStep Functions入門

CFNでStep Functions入門

CloudFormationでStep Functions入門

AWS DVAの出題範囲であるリファクタリングに関する内容です。

Step Functionsはサーバーレスオーケストレーションサービスです。

今回はStep Functions入門ということで、CloudFormationで簡単なステートマシンを作成します。

構築する構成

Diagram of introduction to Step Functions with CFN.

Step Functionsの1つのステートマシンと、その内部に2つのタスクを作成します。
タスク内で、Lambda関数を実行することとします。
関数は以下の働きを行います。

  • Lambda関数1:現在日時情報を取得します。
  • Lambda関数2:日時情報からUNIX時間を計算して返します。

関数のランタイム環境はPython3.8とします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-dva/tree/main/04/002

テンプレートファイルのポイント解説

Step Functions

ステートマシン

Resources:
  StateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      Definition:
        Comment: !Sub "${Prefix}-StateMachine"
        StartAt: FirstState
        States:
          FirstState:
            Type: Task
            Resource: !Ref Function1Arn
            Next: LastState
          LastState:
            Type: Task
            Resource: !Ref Function2Arn
            End: true
      LoggingConfiguration:
        Destinations:
          - CloudWatchLogsLogGroup:
              LogGroupArn: !GetAtt LogGroup.Arn
        IncludeExecutionData: true
        Level: ALL
      RoleArn: !GetAtt StateMachineRole.Arn
      StateMachineName: !Ref Prefix
      StateMachineType: STANDARD
Code language: YAML (yaml)

Definitionプロパティでステートマシンの構造を定義します。
構造はAmazon ステートメント言語と呼ばれる記法で表現します。
Amazon ステートメント言語はJSONまたはYAML形式で記述可能でして、今回は後者を採用します。

ステートマシン構造は状態(ステート)によって構成されています。
状態には実行するアクションを定義します。
今回の構成ですと、Lambda関数を実行するという1つのアクションを、1つの状態で定義するということです。
また2つのLambda関数を実行しますので、2つの状態を作成するということでもあります。

ステートマシン構造において、2つのフィールドが重要であり、必須でもあります。
1つ目はStartAtフィールドです。
複数の状態を定義した場合、その状態からアクションを開始するかを定めるフィールドです。
今回は現在日時を取得するLambda関数1を実行する状態から開始するように指定します。

2つ目はStatesフィールドです。
ステートマシン内で実行する状態を定義します。

状態を構成するフィールドを確認します。
Typeフィールドは状態の種別を指定します。今回のようにLambda関数実行のような何らかのアクションを行う場合は「Task」を指定します。
Resourceフィールドは実行するリソースのARNを指定します。今回ですと、実行するLambda関数をそれぞれ指定します。
Nextフィールドは次に実行する状態を指定するものです。今回ですと、関数1の後に関数2を実行しますので、関数1の状態に本フィールドを定義し、値に関数2用の状態を指定します。
Endフィールドは最後に実行する状態を示すものです。今回ですと、関数2用の状態を最後に実行しますので、こちらの状態に本フィールドを定義し、値に「true」を指定します。

LoggingConfigurationプロパティで、ステートマシン実行時のログに関する設定を行うことができます。
ログはCloudWatch Logsに保存することができます。
後述のロググループのARNを指定します。
IncludeExecutionDataプロパティに「true」を、Levelプロパティに「ALL」を指定しますと、全ログを収集することができるようになります。

StateMachineTypeプロパティでステートマシンのタイプを指定します。
StandardとExpressの2種類があります。
詳細は以下の公式ページをご確認いただきたいですが、今回はStandardを選択します。

https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-standard-vs-express.html

IAMロール

Resources:
  StateMachineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - states.amazonaws.com
      Policies:
        - PolicyName: !Sub "${Prefix}-InvokeTaskFunctions"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Ref Function1Arn
                  - !Ref Function2Arn
        - PolicyName: !Sub "${Prefix}-DeliverToCloudWatchLogPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogDelivery
                  - logs:GetLogDelivery
                  - logs:UpdateLogDelivery
                  - logs:DeleteLogDelivery
                  - logs:ListLogDeliveries
                  - logs:PutLogEvents
                  - logs:PutResourcePolicy
                  - logs:DescribeResourcePolicies
                  - logs:DescribeLogGroups
                Resource: "*"
Code language: YAML (yaml)

ステートマシンを実行するための権限を与えます。
大別すると2種類あります。
1つ目はLambda関数を実行するために必要な権限です。
2つ目はCloudWatch Logsにログを配信するための権限です。
後者につきましては、以下の公式ページを参照して作成しました。

https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/cw-logs.html#cloudwatch-iam-policy

ロググループ

Resources:
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "${Prefix}-StateMachineLogGroup"
Code language: YAML (yaml)

特別な設定は不要です。
シンプルにロググループを作成します。

Lambda関数

インラインに記載したコードからLambda関数を作成します。
詳細につきましては以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

本ページは、実行するコードを中心に取り上げます。

関数1

Resources:
  Function1:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import datetime

          def lambda_handler(event, context):
            now = datetime.datetime.now()

            return {
              'year': now.year,
              'month': now.month,
              'day': now.day,
              'hour': now.hour,
              'minute': now.minute,
              'second': now.second,
              'microsecond': now.microsecond
            }
      FunctionName: !Sub "${Prefix}-function-01"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

現在日時を取得するための関数です。

現在日時を取得後、日時オブジェクトから7つの要素を抽出して返すという内容です。

関数2

Resources:
  Function2:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import datetime
          import pprint
          import time

          def lambda_handler(event, context):
            pprint.pprint(event)

            year = event['year']
            month = event['month']
            day = event['day']
            hour = event['hour']
            minute = event['minute']
            second = event['second']
            microsecond = event['microsecond']

            dt = datetime.datetime(year, month, day, hour, minute, second, microsecond)

            epoch_time = int(time.mktime(dt.timetuple()))
            print(epoch_time)

            return epoch_time
      FunctionName: !Sub "${Prefix}-function-02"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

日時情報からUNIX時間を取得するための関数です。

Pythonですと、eventオブジェクトから関数実行時の引数にアクセスすることができます。
今回は関数1が返す7つの値を取得するように設定します。

引数の取得後は、datetimeオブジェクトを作成後、これからUNIX時間を計算して返すという処理を行います。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • Step Functionsのステートマシン:dva-04-002
  • Lambda関数1:dva-04-002-function-01
  • Lambda関数2:dva-04-002-function-02
  • CloudWatch Logsロググループ:dva-04-002-StateMachineLogGroup

AWS Management Consoleからステートマシンの作成状況を確認します。

Step Functions 1.

正常に作成されています。

次にLambda関数も確認します。

Lambda function to be executed by Step Functions 1.
Lambda function to be executed by Step Functions 2.

こちらも正常です。

動作確認

準備が整いましたので、ステートマシンの実行を行います。
AWS Management Consoleから実行する場合は、「Start execution」を押下します。

Step Functions 2.

実行時のオプションを設定するページが表示されます。

Step Functions 3.

Inputは空で「Start execution」で押下します。

ステートマシンが開始されます。

Step Functions 4.

Execution Statusが「Running」となっています。
ステートマシンが実行中ということです。

もう少し待機します。

Step Functions 5.

Execution Statusが「Succeeded」となっています。
ステートマシンが完了しました。

実行ログが確認できます。

Step Functions 6.

Stepの列を見ると、ステートマシン開始から終了までに、2つの状態に遷移していることがわかります。
それぞれLambda関数1および2の状態です。
つまり2つの関数が正常に実行されたということです。

ステートマシン実行時の詳細なログを確認します。
まずステートマシン開始および関数1実行時のログから確認します。

Step Functions 7.

ExecutionStartのログを見ると、inputが空であることがわかります。
これはステートマシン実行時のオプションで、このパラメータを空にしたためです。

LambdaFunctionScheduledのログを見ると、resourceで実行するLambda関数1が指定されていることがわかります。
またステートマシンのinputが空だったため、関数実行時の引数としてのinputも空になりました。

TaskStateExitedのログを見ると、outputに関数1で返された結果が設定されています。
現在日時を構成する7つのデータが確認できます。

次に関数2および実行およびステートマシン終了時のログを確認します。

Step Functions 8.

TaskStateEnterdのログを見ると、inputに関数1で返された結果が確認できます。

LambdaFunctionScheduledのログを見ると、resourceで実行するLambda関数2が指定されていることがわかります。
またステートマシンのinputが設定されていますので、これが関数実行時の引数に設定されています。

TaskStateExitedのログを見ると、outputに関数2で返された結果が設定されています。
関数1から渡された日時、つまり現在日時のUNIX時間です。

ExecutionSuccededのログを見ると、こちらのoutputにも関数2の結果が設定されていることがわかります。

最後にCloudWatch Logsロググループに配信されたログも確認します。

CloudWatch Logs when Step Functions are executed 1

ログストリームが作成されています。

CloudWatch Logs when Step Functions are executed 2.

先ほど確認したログがCloudWatch Logsロググループにも配信されていることがわかります。

まとめ

Step Functions入門ということで、CloudFormationを使ってシンプルなStep Functions構成を構築しました。