おひとり

できる限りひとりで楽しむための情報やプログラミング情報など。

【サーバーレスバッチ処理ハンズオン】4章 EventBridgeでStateMachineをトリガー、デプロイ

※この記事は「AWSでサーバーレスなバッチ処理を作るハンズオン」の4章です。こちらの記事からスタートできます。

f:id:hitoridehitode:20211205154305p:plain
EventBridgeでStateMachineをトリガーする構成を作成しデプロイします。

ここでは、作成してきたLambda関数を束ねるStep FunctionsのState Machine、およびEvent Bridgeを作成していきます。

リポジトリ

この章における変更箇所は以下のコミットで確認できます。

4章 EventBridgeでStateMachineをトリガー、デプロイ · SRsawaguchi/simple-serverless-batch@448e3e8 · GitHub

※この章でsamconfig.tomlというファイルを作成しますが、こちらは自動生成するためコミットしていません。(通常の開発ではコミットしてください。)

また、このハンズオン全体のリポジトリはこちらです。

github.com

State Machineの作成

では、State Machineを作成していきましょう。
今回はMakeReportFunctionが成功したらHtmlToPdfFunctionを呼び出すようなState Machineを作ります。

State Machineの定義を作成

まずは、State Machineの定義を追加します。
State Machineの定義はJSONを拡張したASL(Amazon States Language)で記述します。

touch statemachine/make_report.asl.json

statemachine/make_report.asl.jsonには、以下のようなJSONを書き込みます。

{
  "Comment": "StateMachine of Make Report Tasks",
  "StartAt": "Make Report Html",
  "States": {
    "Make Report Html": {
      "Type": "Task",
      "Resource": "${MakeReportFunctionArn}",
      "Next": "Convert Html to Pdf"
    },
    "Convert Html to Pdf": {
      "Type": "Task",
      "Resource": "${HtmlToPdfFunctionArn}",
      "End": true
    }
  }
}

template.yamlへの追加

State Machineのリソースと、それに紐付けるIAM Role(Lambda関数をinvokeする権限を付与)、そしてログを閲覧するためのLogGroupが必要です。
template.yamlResourcesに以下を追加します。

  MakeReportStateMachineLogGroup:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: "simple-serverless-batch"
      RetentionInDays: 60

  StatesExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - !Sub states.${AWS::Region}.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: StatesExecutionPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "lambda:InvokeFunction"
                Resource: "*"
              - Effect: Allow
                Action:
                  - "logs:CreateLogDelivery"
                  - "logs:GetLogDelivery"
                  - "logs:UpdateLogDelivery"
                  - "logs:DeleteLogDelivery"
                  - "logs:ListLogDeliveries"
                  - "logs:PutResourcePolicy"
                  - "logs:DescribeResourcePolicies"
                  - "logs:DescribeLogGroups"
                Resource: "*"

  MakeReportStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      DefinitionUri: statemachine/make_report.asl.json
      DefinitionSubstitutions:
        MakeReportFunctionArn: !GetAtt MakeReportFunction.Arn
        HtmlToPdfFunctionArn: !GetAtt HtmlToPdfFunction.Arn
      Logging:
        Level: "ERROR"
        IncludeExecutionData: true
        Destinations:
          - CloudWatchLogsLogGroup: 
              LogGroupArn: !GetAtt MakeReportStateMachineLogGroup.Arn
      Role: !GetAtt StatesExecutionRole.Arn

State Machineの実行

AWSでは、Step Functions ローカルを提供しています。

これを使って先ほど作成したState Machineをローカルで実行してみましょう。

docker-compose.yamlservicesセクションに以下の記述を追加します。

  stepfn:
    image: amazon/aws-stepfunctions-local:latest
    hostname: stepfn
    depends_on:
      - dynamodb
      - minio
    ports:
      - "8083:8083"
    environment:
      LAMBDA_ENDPOINT: http://host.docker.internal:3001
      DYNAMODB_ENDPOINT: http://dynamodb:8000

LAMBDA_ENDPOINTはローカル上のLambdaのエンドポイントですが、これはSAMが提供します。
Step Functionsローカルは、このエンドポイントにホストマシンを経由してアクセスできます。

では、以下のコマンドでローカルにLambdaを起動します。

sam local start-lambda \
  --docker-network ssb-handson \
  --parameter-overrides \
    DynamoDBEndpoint=http://dynamodb:8000 \
    S3BucketName=handson-bucket \
    S3Endpoint=http://minio:9000 \
    MinioRootUser=root \
    MinioRootPassword=himitsu123

この状態で、AWS CLIを使ってState Machineを作成します。
ここで、前回作成したmake_report.asl.jsonを使いたいところですが、これはCloud Formationで動作することが前提になっているため使えません。
具体的には、${HtmlToPdfFunctionArn}などの置換が必要な箇所があるからです。

ですので、やや冗長ですがローカルでテストするとき向けに置換が含まれないState Machineの定義を作成する必要があります。
make_report.asl.jsonをコピーしてmake_report_local.asl.jsonを作成します。

cp statemachine/make_report.asl.json statemachine/make_report_local.asl.json

make_report_local.asl.jsonの中身は以下のようになります。
Resourceの内容をダミーのARNに置き換えます。
ダミーのARNはarn:aws:lambda:us-east-1:123456789012:function:template.yamlに記載した関数の論理名を連結させればOKです。

{
  "Comment": "StateMachine of Make Report Tasks",
  "StartAt": "Make Report Html",
  "States": {
    "Make Report Html": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:MakeReportFunction",
      "Next": "Convert Html to Pdf"
    },
    "Convert Html to Pdf": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:HtmlToPdfFunction",
      "End": true
    }
  }
}

では実行してみましょう。 docker compose upでStep Functionsローカルなどのコンテナが起動していることを確認してください。
続いて、AWS CLIを使ってStep FunctionsローカルにStateMachineを登録します。

aws stepfunctions \
  --endpoint http://localhost:8083 \
  create-state-machine \
  --name "MakeReportStateMachine" \
  --definition file://statemachine/make_report_local.asl.json \
  --role-arn "arn:aws:iam::012345678901:role/DummyRole"

以下のコマンドでStateMachineを実行します。
--name$(uuidgen)を利用していますが、これは実行ごとに重複した名前をつけるためです。
実行するたびに手動で名前を考える手間を削減する目的です。

aws stepfunctions start-execution \
    --endpoint http://localhost:8083 \
    --state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:MakeReportStateMachine \
    --name $(uuidgen)

無事に実行できればMinio上にPDFが作成されているはずです。(過去に実行した際にできたPDF等があるかもしれないので、Minioコンソールから削除しておくと良いでしょう。)

AWSにデプロイ

ここまで動作確認できたので、AWSにデプロイしましょう。
その前に、HTMLやPDFをアップロードするためにS3バケットを1つ作成しておいてください。
template.yamlにS3バケットを記述することで、Cloud Formation上でバケットを作成できます。
しかし、今回は簡単なサンプルなので、S3バケットはtemplate.yamlに記載せず、これとは別に作成してください。

※S3バケットをtemplate.yamlに記載する場合、「S3バケットを作成するかどうか」の制御をする必要がある。

sam deployの実行

S3バケットを作成したら、早速デプロイしていきましょう。

sam build
sam deploy --guided

これは質問に答えながらデプロイするコマンドです。
それぞれ以下のように回答しましょう。

質問 回答 備考
Stack Name simple-serverless-batch
AWS Region ap-northeast-1 そのままEnter
Parameter TableName Messages そのままEnter
Parameter DynamoDBEndpoint そのままEnter
Parameter S3Endpoint そのままEnter
Parameter S3BucketName <作成したS3バケット名 >
Parameter MinioRootUser そのままEnter
Parameter MinioRootPassword そのままEnter
Confirm changes before deploy y
Allow SAM CLI IAM role creation Y
Disable rollback N
Save arguments to configuration file Y
SAM configuration file samconfig.toml そのままEnter
SAM configuration environment default そのままEnter

これにより、デプロイが開始されます。
今回はConfirm changes before deployyで回答しているため、デプロイが実行される前に作成、変更されるリソースの一覧が表示されます。
内容を確認して、問題なければyで回答してデプロイを実行します。

デプロイ状況はAWSマネジメントコンソールのCloud Formationのページからも確認できます。
なお、先ほど質問の結果はsamconfig.tomlに保存されています。質問への回答を変更したい場合は、このファイルを編集します。
samconfig.tomlの内容を使ってのデプロイは以下のようにします。

sam deploy --config-file ./samconfig.toml

これ以降はこのコマンドを使ってデプロイしていきましょう。

なお、デプロイでErrorが発生した後、修正して再度デプロイする場合にはCloud Formationのスタックを削除する必要があります。
その場合、以下のコマンドを実行します。

aws cloudformation delete-stack \
  --stack-name simple-serverless-batch

このコマンドを実行すると、このスタック内(template.yamlに定義した)のリソースが全て削除されます。
デプロイ後に動作確認したり検証したりした後は余分に課金されるのを防ぐために早めにスタックの削除を行うことをオススメします。

DynamoDBテーブルにitemを追加

さっそく実行したいところですが、その前にDynamoDBテーブルにitemを追加しておきます。

aws dynamodb put-item \
    --table-name Messages \
    --item '{ 
        "Date": {"S": "2021/11/28"}, 
        "Message": {"S": "Hello, World"}
      }'

追加されたかどうか確認してみましょう。

aws dynamodb get-item \
    --table-name Messages \
    --key '{"Date": {"S": "2021/11/28"}}'

以下のようにitemが返ってくればOKです。

{
    "Item": {
        "Date": {
            "S": "2021/11/28"
        },
        "Message": {
            "S": "Hello, World"
        }
    }
}

AWSマネジメントコンソールのStepFunctionsからStateMachineを実行

では、先ほど作ったStateMachineを実行します。
AWS CLIでやっても良いですが、視覚的に分かりやすいためAWSマネジメントコンソールからやります。
AWSマネジメントコンソールにアクセスし、Step Functionsのページに行きます。
そこから、MakeReportStateMachine-*のStateMachineを見つけてアクセスし、「実行の開始」ボタンをクリックします。

f:id:hitoridehitode:20211205121938p:plain
「実行の開始」ボタンをクリック

今回はデータを与える必要がありませんのでその後に表示されるフォームは変更不要です。

f:id:hitoridehitode:20211205122150p:plain
何も変更せず「実行の開始」をクリック

すると、StateMachineの実行が開始されます。
全てのStateが「成功」(緑色)になることを確認してください。

f:id:hitoridehitode:20211205122329p:plain
実行状況が分かりやすく表示される。

あとはS3バケットに2021_11_28.pdfおよび2021_11_28.htmlが保存されていることを確認しましょう。

Event Bridgeを追加

前回はState Machineを手動で実行しました。ここではEvent Bridgeを使って指定した時間になったら自動でState Machineを実行する構成にします。

f:id:hitoridehitode:20211205145603p:plain
Event Bridgeを使って、指定時刻にState Machineを実行する。

今回は、平日の19時に先ほど作成したState Machineを起動するようEvent Brigeを設定しましょう。
以下の2つのリソースが必要になります。

  • Event Bridgeのイベント
  • State Machineを実行する権限が付与されたIAM Role

template.yamlにリソースを追加

template.yamlに以下を追加します。

  CallStateMachineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: events.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: InvokeStepFunction
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "states:StartExecution"
                Resource: !GetAtt MakeReportStateMachine.Arn

  CallStateMachine:
    Type: AWS::Events::Rule
    Properties:
      Name: "CallMakeReportStateMachine"
      Description: "Event for call MakeReportStateMachine(StepFunctions)."
      ScheduleExpression: cron(0 10 ? * MON-FRI *)
      State: ENABLED
      Targets: 
        -
          Arn: !GetAtt MakeReportStateMachine.Arn
          RoleArn: !GetAtt CallStateMachineRole.Arn
          Id: CallStateMachine

時刻を指定してる箇所はCallStateMachineの中にあるScheduleExpressionです。
ここはCron式で指定します。(他にもRate式が使えます。)

今回のCron式を解剖すると以下のようになります。

f:id:hitoridehitode:20211205151313p:plain
Cron式の解剖

はUTCで指定することに注意してください。
JST(日本標準時)はUTCより9時間進んでいます。
そのため、19時を指定する場合は10と指定します。

Cron式の詳細は公式ドキュメントを参照してください。豊富なExamplesが掲載されているため、要件に近いものを参考にすると良いです。

Schedule Expressions for Rules - Amazon CloudWatch Events

この後デプロイしますが、Cron式を修正して今すぐ検証しやすい時刻に設定すると良いでしょう。

デプロイ

デプロイしましょう。

sam deploy --config-file ./samconfig.toml

あとは、先ほど設定した時刻になったらStateMachineが実行され、成功したことを確認しましょう。
AWSマネジメントコンソールでStep Functionsのページにアクセス、当該StateMachineの「実行」の欄を確認するとよいです。

リソースの削除

余計な課金が発生するため、必ずAWSリソースを削除してください。

ここまで実行して確認できたら、今回作成したAWSリソースもう不要です。
今回template.yamlで作成したAWSリソースを全て削除します。

aws cloudformation delete-stack \
  --stack-name simple-serverless-batch

なお、これを実行してもS3バケットは削除されません。
template.yamlに記述したリソースではないからです。
今回使ったS3バケットが不要である場合は、それぞれ別途削除してください。

次のステップ

これまで簡単のために雑多に書いてきたLambda関数をリファクタリングして、ユニットテストおよびIntegrationテストを実装していきます。

www.ohitori.fun