AWS BatchをStep Functionsから実行

やったこと

  • Cloud FormationでAWS Batchを構築
  • AWS Batchで動作させるプログラムはPythonで記述し、docker imageをBuild
  • Step FunctionsからAWS Batch Jobを実行

この記事に書いてあること

  • Cloud FormationでAWS Batchを構築する時に必要なリソースとポイント
  • Step Functionsの実装でつまづいたポイント
  • Python のloggingをCloud Watchから確認するためにstream出力する

Cloud FormationでAWS Batchを構築する時に必要なリソース

必要なResourceは以下で今回は全てCFnで作成した

  • Job definition
  • JobQueue
  • SecurityGroup
  • ComputeEnvironment
  • EcsTaskExecutionRole
  • AWSBatchServiceRole

ここでは躓いたポイントのみ紹介します

JobDefinition

Resources:
  JobDefinition:
    Type: AWS::Batch::JobDefinition
    Properties:
      Type: container
      JobDefinitionName: ${self:provider.stage}-${self:custom.system}-batch
      PlatformCapabilities:
        - FARGATE
      ContainerProperties:
        Command:
          - poetry
          - run
          - python3
          - main.py
        ResourceRequirements:
          - Type: VCPU
            Value: 1
          - Type: MEMORY
            Value: 2048
        Environment:
          - Name: database_name
            Value: your_db_name
          - Name: bucket_name
            Value: your_s3_bucket_name
        JobRoleArn: !GetAtt EcsTaskExecutionRole.Arn
        ExecutionRoleArn: !GetAtt EcsTaskExecutionRole.Arn
        Image: ${aws:accountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${self:custom.config.EcrRepositoryName}:latest
      RetryStrategy:
        Attempts: 1

dockerコンテナ内で実行してほしいコマンドはここで定義しています
Dockerfile内にもCMD ["poetry", "run", "python3", "/app/main.py"] のように設定できますが、外側(AWS Batch)で設定する方が後からの変更に強いかと思います

またコンテナ内で使用する環境変数もここに定義して
下記のようにpython の処理でos.getenv('database_name')のように値を取得できます
ちなみにStep Functionsからの実行時にオーバーライドも可能

ResourceRequirementsの設定は公式ドキュメントAWS::Batch::JobDefinition ResourceRequirement を読んで設定しました

PlatformCapabilitiesをFargateに設定している場合は、ExecutionRoleArnの項目が必要のようでした
executionRoleArn is required for Fargate jobs のエラーが発生したので追加しました

ComputeEnvironment

Buildが失敗&ComputeEnvironmentの設定が消せなくなる事態が発生しましたが、サービスロールが原因で消せなくなってしまったAWS Batchコンピューティング環境を消してみた | DevelopersIO の記事を参考に乗り越えました
エラー内容を残しておきます

CREATE_FAILED: ComputeEnv (AWS::Batch::ComputeEnvironment)
Resource handler returned message: "Resource of type 'AWS::Batch::ComputeEnvironment' with identifier 'arn:aws:batch:ap-northeast-1:xxxxxxxxx:compute-environment/xxxxxx-batch-compute-env2' did not stabilize." 

Step Functionsの実装

こちらもポイントを絞って記載してます

statemachine.ymlのAWS Batch Job実行部分

          ...
          run-batch:
            Type: Task
            Resource: arn:aws:states:::batch:submitJob.sync
            Parameters:
              JobName: my-batch-job
              JobDefinition: !Ref JobDefinition
              JobQueue: !Ref JobQueue
              ContainerOverrides:
                Environment:
                  - Name: database_name
                    Value: my_bucket_name
                  - Name: bucket_name
                    Value.$: $.Payload.my_bucket_name
            ResultPath: $.run-batch
            Next: next-job
          .....

Is not authorized to create managed-rule

このymlファイルとは別ものですが、
Build時にエラー.... xxxxStateMachineRole is not authorized to create managed-rule が発生しました
これはBatch Job で発生したイベントを EventBridge で検知できるようにpolicyの設定が必要だそうです
これができないとStep Functionsが次のstepに行くタイミングがわからないからですね

step function execute用のroleのpolicyに下記を追加すればエラーは解消されました

...
        {
            "Action": [
                "events:PutTargets",
                "events:PutRule",
                "events:DescribeRule"
            ],
            "Resource": [
                "arn:aws:events:ap-northeast-1:xxxxxx:rule/StepFunctions*"
            ],
            "Effect": "Allow"
        }
...

※下記の投稿にあった~~:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule で試したみたら同じエラーが発生していたのでStepFunctions* として回避しました

amazon web services – Nested Step Function in a Step Function: Unknown Error: “…not authorized to create managed-rule” – Stack Overflow

Step Functions実行時にエラー

エラーメッセージは忘れましたが、JobDefinitionとJobQueueに対する権限を同じくstep function execute用のroleに追加することで解消できました

...
 - Effect: Allow
   Action:
     - batch:SubmitJob
     - batch:DescribeJobs
     - batch:TerminateJob
   Resource:
     - !Ref JobDefinition
     - !Ref JobQueue
...

Python のloggingをCloud Watchから確認するためにstream出力する

import logging
logger = logging.getLogger()
log_format = '[%(levelname)s][%(filename)s][%(funcName)s:%(lineno)d]\t%(message)s'
for handler in logger.handlers:
    handler.setFormatter(logging.Formatter(log_format))
logger.setLevel(logging.INFO)
logger.info("process start by logger")
print("process start by print()")

下記のようにpythonのloggingを使用して出力してもコンテナ内のlogファイルに書き込まれるだけで、AWS Cloud Watchからは見れないんですね…
上記のコードの場合process start by print() の方のみCloud Watchに出力されます


pythonが標準出力したものはCloud Watchに出力されるので、loggingの設定をstreamに変更してみました

import logging
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(logging.Formatter(log_format))
stream_handler.setLevel(logging.INFO)

logger.addHandler(stream_handler)
logger.setLevel(logging.INFO)
logger.info("process start by logger")

これでCloud Watchに出力されました(キャプチャなくてすいません…)
これが最善の方法かは不明ですが、記事を漁るに「AWS Batchで動かしたプログラムのログ出力をどこから監視するか問題」はみんな困ってそうでした…

まとめ

  • AWS Batchを動かすために設定すべき箇所がたくさんある(そう考えるとLambdaは偉大)
  • Batch Jobの立ち上がりとログ出力が遅いので開発スピードに影響しそう
  • Step Functionsからの実行は想像よりシンプルだった

ECS TaskとAWS Batchどちらでバッチ処理を実装すべきかも後日まとめようと思う