目次
やったこと
- 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*
として回避しました
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どちらでバッチ処理を実装すべきかも後日まとめようと思う