はじめに
この記事ではAWS公式が出しているAWS Batch実行環境を一発で用意する方法1を説明します。
知っていれば簡単にできると思うのですが、当初自分で一からcloudformationのyamlを用意しようとしたらコンテナ実行環境を用意するのが案外面倒で、結構時間がかかってしまいました。そこで AWSの公式レポジトリを引っ張ってきて必要な部分だけ改造したら過不足なく動いた、というお話です。
以下の図は1から引っ張ってきたものです。今回作りたいものはawsコンソールから手動でバッチ実行するので2,3,4,5,7の矢印はありません。
なお、時間がある人は詳しいハンズオン記事2とかを読むのがいいと思われます。
前提
- cloudshellとかで簡単なテスト実行をして動作確認ができたシェルスクリプトをそのままコンテナ環境に移植して長時間バッチ実行したい
- マイグレーションのロジックは単純だが、要所要所でネットワークアクセスが必要だったりして時間がかかる(具体的には並列化しても一週間以上かかる)
- AWS内部のサービス (cognito等)に入っているデータを、加工してdynamo dbにマイグレーションしたい
必要なもの
手順
githubからレポジトリをclone
https://github.com/aws-samples/aws-batch-processing-job-repo をクローンします。この記事では 023cbeaaba3ef5eb1d0be2670199ef672743f6bb 時点での情報を参照しています。
Dockerfileを確認
以下の通り、公式ではpythonスクリプトが動くdockerイメージになっているので、これをaws cliを動くようなイメージに差し替えます。aws cliを動かすスクリプト本体の名前は exec.sh
としています。
# ベースイメージとして公式の Amazon Linux を使用
FROM amazonlinux:latest
# 必要なツールのインストール(AWS CLI, jqなど)
RUN yum update -y && \
yum install -y \
aws-cli \
jq \
bash
RUN yum install -y findutils
# スクリプトをコンテナ内にコピー
COPY *.sh .
# デフォルトで実行するコマンド(必要に応じて変更)
CMD ["bash", "exec.sh"]
実行スクリプトを確認
exec.sh
の中身は例えば以下のような感じで、cognitoユーザーの情報を出力し情報を加工してdynamo dbに入れ直します。
#!/bin/bash
# データをCognitoから取得する
user_pool_id=ap-northeast-1_XXXXXXXXXX
dynamodb_table_name=YourDynamoDBTableName
# Cognitoからユーザーデータを取得
user_data=$(aws cognito-idp list-users --user-pool-id $user_pool_id --query 'Users')
# データを加工してDynamoDBに挿入
for row in $(echo "${user_data}" | jq -c '.[]'); do
# 必要なフィールドを抽出
username=$(echo $row | jq -r '.Username')
email=$(echo $row | jq -r '.Attributes[] | select(.Name=="email").Value')
# DynamoDBにデータを挿入
aws dynamodb put-item \
--table-name $dynamodb_table_name \
--item '{
"Username": {"S": "'$username'"},
"Email": {"S": "'$email'"}
}'
done
template.yaml を確認
今回はdynamo dbアクセス機能は使いますが、他の部分で使わない機能がかなりたくさんあります。そういった機能は削っていきます。CodeCommitなど新規作成ができないリソース以外に関しては、使わないだけでデプロイしても動きますが、無駄に課金することになるので削ります。
以下、削ったリソースのリストです。
LambdaExecutionRole
BatchProcessS3Bucket
: もとのレポジトリでは「s3にcsv配置したらそのタイミングでlambdaでバッチ開始」みたいなことをやっているのですが、そこまで高度なことはやらないので今回は削ります。BatchProcessBucketPermission
: 同上BatchProcessingLambdaInvokeFunction
: 同上CodeCommitRepository
: codecommitは新規アカウントでの利用ができなくなっています。今回参考にしたレポジトリでは更新が追いついていませんでした。CodeBuildProject
CloudWatchEventsCodeBuildRole
CloudWatchEventCodeBuildEventRule
BatchProcessingDynamoDBTable
: dynamo dbのリソースは使いましたが、ここで作られるものではなく既存環境にあるものを使いました。S3VPCEndpoint
: シェルスクリプトからs3アクセスはなかったので削除。
以下、変更したリソースのリストです。
DynamoDBVPCEndpoint
: 対象のテーブル名を変更CodeBuildRole
: CodeCommit関連のポリシーを削除BatchTaskExecutionRole
:PolicyName: !Sub ${StackName}-ecs-task-dynamo-policy
における対象のテーブル名を変更
以下、outputsから削除
BucketName
LambdaName
最終的には以下のようになりました。
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Orchestrating an Application Process with AWS Batch in Fargate using CloudFormation'
Parameters:
StackName:
Type: String
#Default: batch-processing-job
Description: The name of the application stack
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InternetGateway:
Type: AWS::EC2::InternetGateway
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: InternetGateway
NATEIP:
DependsOn: VPCGatewayAttachment
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NATGateway:
DependsOn: VPCGatewayAttachment
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NATEIP
- AllocationId
SubnetId:
Ref: PublicSubnet
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EC2 Security Group for instances launched in the VPC by Batch
VpcId:
Ref: VPC
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.0.0/24
VpcId:
Ref: VPC
MapPublicIpOnLaunch: 'True'
Tags:
- Key: Name
Value: !Sub ${StackName} Public Subnet
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub ${StackName} Private Subnet
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub ${StackName} Public Route
- Key: Project
Value: AWS Batch in Fargate
PublicSubnetRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId:
Ref: PublicSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet
RouteTableId:
Ref: PublicSubnetRouteTable
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub ${StackName} Private Route
- Key: Project
Value: AWS Batch in Fargate
PrivateSubnetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref 'PrivateSubnetRouteTable'
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: NATGateway
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PrivateSubnet
RouteTableId:
Ref: PrivateSubnetRouteTable
BatchServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: batch.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole
BatchProcessingJobDefinition:
Type: AWS::Batch::JobDefinition
Properties:
Type: container
PropagateTags: true
JobDefinitionName: BatchJobDefinition
ContainerProperties:
Image:
Fn::Join:
- ''
- - Ref: AWS::AccountId
- .dkr.ecr.
- Ref: AWS::Region
- !Sub '.amazonaws.com/${StackName}-repository:latest'
FargatePlatformConfiguration:
PlatformVersion: LATEST
ResourceRequirements:
- Value: 1
Type: VCPU
- Value: 2048
Type: MEMORY
JobRoleArn: !GetAtt 'BatchTaskExecutionRole.Arn'
ExecutionRoleArn: !GetAtt 'BatchTaskExecutionRole.Arn'
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref 'BatchLogGroup'
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Sub ${StackName}-logs
Command:
- bash
- exec.sh
PlatformCapabilities:
- FARGATE
Tags:
Service: Batch
Name: JobDefinitionTag
Expected: MergeTag
BatchLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${StackName}-awslogs
RetentionInDays: 7
BatchTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${StackName}-taskexec-role
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: AmazonECSTaskExecutionRolePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:GetDownloadUrlForLayer'
- 'ecr:BatchGetImage'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
- PolicyName: !Sub ${StackName}-ecs-task-s3-get-policy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:ListBucket
Resource:
- !Join
- ''
- - 'arn:aws:s3:::'
- !Sub "${StackName}-${AWS::AccountId}"
- !Join
- ''
- - 'arn:aws:s3:::'
- !Sub "${StackName}-${AWS::AccountId}"
- /*
- PolicyName: !Sub ${StackName}-ecs-task-dynamo-policy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'dynamodb:PutItem'
- 'dynamodb:Query'
- 'dynamodb:UpdateItem'
Resource:
- !Join
- ''
- - 'arn:aws:dynamodb:'
- !Sub "${AWS::Region}:${AWS::AccountId}:table/*"
- !Join
- ''
- - 'arn:aws:dynamodb:'
- !Sub "${AWS::Region}:${AWS::AccountId}:table/*/index/*"
BatchProcessingJobQueue:
Type: AWS::Batch::JobQueue
Properties:
JobQueueName: !Sub "${StackName}-queue"
State: ENABLED
Priority: 1
ComputeEnvironmentOrder:
- Order: 1
ComputeEnvironment:
Ref: ComputeEnvironment
ComputeEnvironment:
Type: AWS::Batch::ComputeEnvironment
Properties:
Type: MANAGED
State: ENABLED
ComputeResources:
Type: FARGATE
MaxvCpus: 40
Subnets:
- Ref: PrivateSubnet
SecurityGroupIds:
- Ref: SecurityGroup
ServiceRole:
Ref: BatchServiceRole
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
# - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [codebuild.amazonaws.com]
Version: '2012-10-17'
Path: /
Policies:
- PolicyName: CodeBuildAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'logs:*'
- 'ec2:CreateNetworkInterface'
- 'ec2:DescribeNetworkInterfaces'
- 'ec2:DeleteNetworkInterface'
- 'ec2:DescribeSubnets'
- 'ec2:DescribeSecurityGroups'
- 'ec2:DescribeDhcpOptions'
- 'ec2:DescribeVpcs'
- 'ec2:CreateNetworkInterfacePermission'
Effect: Allow
Resource: '*'
BatchProcessRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub ${StackName}-repository
DynamoDBVPCEndpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
RouteTableIds:
- !Ref PrivateSubnetRouteTable
ServiceName:
!Sub "com.amazonaws.${AWS::Region}.dynamodb"
VpcId: !Ref VPC
PolicyDocument:
Statement:
- Effect: Allow
Principal: '*'
Action:
- 'dynamodb:*'
Resource:
- !Join
- ''
- - 'arn:aws:dynamodb:'
- !Sub "${AWS::Region}:${AWS::AccountId}:table/*"
- !Join
- ''
- - 'arn:aws:dynamodb:'
- !Sub "${AWS::Region}:${AWS::AccountId}:table/*/index/*"
EcrApiVPCEndpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcEndpointType: Interface
ServiceName:
!Sub "com.amazonaws.${AWS::Region}.ecr.api"
VpcId: !Ref VPC
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- 'ecr:*'
Resource:
- !Join
- ''
- - 'arn:aws:ecr:'
- !Sub "${AWS::Region}:${AWS::AccountId}:repository/${StackName}"
EcrDkrVPCEndpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcEndpointType: Interface
ServiceName:
!Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
VpcId: !Ref VPC
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- 'ecr:*'
Resource:
- !Join
- ''
- - 'arn:aws:ecr:'
- !Sub "${AWS::Region}:${AWS::AccountId}:repository/${StackName}"
Outputs:
ComputeEnvironmentArn:
Value:
Ref: ComputeEnvironment
BatchProcessingJobQueueArn:
Value:
Ref: BatchProcessingJobQueue
BatchProcessingJobDefinitionArn:
Value:
Ref: BatchProcessingJobDefinition
デプロイ
githubのreadmeに書いてあるとおりデプロイします。
手動バッチ実行
awsコンソールのバッチ実行のページを開きます。バッチの新規作成をクリックします。ほとんどの項目はそのままでokですが、環境変数とコンテナのentrypointだけは必要に応じて修正します。修正できたらそのまま手動でバッチ実行できます。
感想
今回はIAMのクレデンシャルを環境変数としてジョブ実行時に設定し、コンテナ内ではawsコマンドをインストールしておくことで、awsコマンドがコンテナ内で自由に扱えるようになるという野蛮な方法を取りました(セキュリティ的に推奨されていないと思います)。
普通にec2インスタンスを最初から使えばよかったのではという気もしますが、単発バッチ実行の手順が全部コードで管理できるのは便利なので良しとします。
参考文献
-
Orchestrating an Application Process with AWS Batch using AWS CloudFormation https://github.com/aws-samples/aws-batch-processing-job-repo ↩ ↩
-
CloudFormationを使ってAWS Batchを(ほぼ)全部AWS CLIで構築してみるハンズオン! https://qiita.com/nokonoko_1203/items/7e499f92780469a28237 ↩