初めに
みなさんAWS Batchは活用していますか?
AWS Batchはキューに登録したジョブを非同期で処理してくれるAWSのサービスになります。
軽量タスクをサーバーレスで行いたい場合はLambdaなどが第一の選択肢になってきますが、Lambdaは実行時間が15分という制限があるため、大容量データをいくつも処理したい時にLambdaは利用できません。
そんな時に活躍するのがAWS Batchになります。
DockerイメージをAWSのECRというサービスにプッシュしておけば、そのコンテナがそのままAWS Batchで利用することができます。
イメージとしてはSQSとFargateが統合されたサービスで、AWS Batch内部でキューとコンピューティングリソースがどちらも含まれていますので他のサービスと連携することなく利用できます。
LambdaとAWS Batchの細かい比較についてはこちらが参考になりました。
AWSでバッチ処理を実装する際の選択肢とサービス比較
弊社はこちらに代表されるような、5GBを超えるファイルの変換なども頻繁に行いますので、15分だとぎりぎりだったり、全然足りなかったりします。
Leafmap/Open3Dを使って掛川城の大規模点群データ(5GB)をPythonで可視化してみよう!
そんなAWS Batchですが、単一で利用できる、と言いつつもVPCやセキュリティグループ、ロールの設定、あとはDockerイメージをECRにプッシュしておく必要があるなど、Lambdaよりは構築が手間ではあります。
(Lambdaもまともに活用としようとすると同じような設定が必要ですが…)
なので今回は(ほぼ)CloudFormationのみを利用して、さらにシェルスクリプトを順に実行していくだけでバッチが構築できるCloudFormationテンプレート集を作成しました!
AWS Batchに関わる簡単な用語説明
- AWS Batch
- 定義されたジョブをジョブキューに登録することで、ジョブキューに紐づいたコンピューティング環境でジョブ実行するサービス
- 内部的にはECRのコンテナをECSでEC2かFargateで動かすサービス
- ジョブ
- 何らかの処理のこと
- AWS Batchでは作業単位のことで、コンテナ化されたアプリケーションを呼び出すこと
- ジョブ定義をすると、それをジョブとしてジョブキューに送信できる
- ジョブ定義
- 使用するイメージ・CPUなど・コマンド・環境変数など、コンテナ実行時の各種パラメーターを設定する
- キュー
- FIFO(先入れ先出し)を実現するデータ構造
- つまり、キューに登録した順に処理される
- ただ、SQSとかはデフォルトでランダム実行になっていそう(つまり厳密にはキューじゃない)
- ジョブキュー
- キューでジョブを管理するもの
- AWS Batchでは接続するコンピューティング環境だけ設定すれば良い
- コンピューティング環境
- ECSの設定・インスタンスタイプ・インスタンスの台数などを設定
ということで、実際に構築していきましょう!
.envを作成
まず.env
ファイルを作成しましょう。
各種項目の意味は以下になります。
-
AWS_ACCOUNT_ID
: 利用するアカウントのID -
AWS_PROFILE_ADMIN
: CloudFormationのサービスロールを作成するstack実行時に必要なIAMロール作成権限を持つユーザー -
AWS_PROFILE
: 後述のCloudFormation実行の権限を持ったユーザー -
AWS_DEFAULT_REGION
: 利用するリージョン -
PJ_PREFIX
: 自由にプロジェクト名を設定(このprefixがほとんどのリソース名の先頭に利用される) -
SEPARATOR
: 固定で「-」 -
CONTAINER_NAME
: コンテナ名 -
IMAGE_NAME
: イメージ名 -
〇〇_SUFFIX
: 各種スタック名のsuffix -
CLOUDFORMATION_SERVICE_ROLE_ARN
: 後述のCloudFormationのサービスロールのarn
# AWS settings
AWS_ACCOUNT_ID=xxxxx
AWS_PROFILE_ADMIN=xxxxx
AWS_PROFILE=xxxxx
AWS_DEFAULT_REGION=ap-northeast-1
PJ_PREFIX=batch-sample
SEPARATOR=-
CONTAINER_NAME=container
IMAGE_NAME=image
REPOSITORY_SUFFIX=repository
NETWORK_SUFFIX=network
SECURITY_GROUP_SUFFIX=security-group
IAM_ROLE_SUFFIX=iam-role
BATCH_SUFFIX=aws-batch
CLOUDFORMATION_SERVICE_ROLE_ARN=xxxxx
CloudFormationの実行ユーザーを作成する
次に、CloudFormationの実行ユーザーを作成しましょう。
これはAWS CLIからCloudFormationを実行するためだけのユーザーです。たくさんの権限を持ったユーザーを作成し、アクセスキーを発行するとセキュリティ的に問題がありますので、なるべく権限を絞ったユーザーを作成します。
.env
のAWS_PROFILE
に設定するCloudFormation実行用のユーザーを以下のような権限でAWSコンソールなどから作成し、~/.aws/credentials
に設定しましょう。
(セキュリティの都合でユーザーの作成(アクセスキーの発行)が許可されない場合は、以下のyamlのような権限を持つサービスロールを作成してもらいましょう)
Resources:
CloudformationExecuteUser:
Type: "AWS::IAM::User"
Properties:
Path: "/"
UserName: "batch-sample-CloudformationExecuteUser"
ManagedPolicyArns:
- arn:aws:iam::616902636047:policy/cloudfromation-stack-operate-policy
- arn:aws:iam::616902636047:policy/ecr-image-push-policy
- arn:aws:iam::616902636047:policy/ecs-deploy-with-docker-compose-policy
- ~/.aws/credentials
[batch-sample-CloudformationExecuteUser]
aws_access_key_id=xxxxx
aws_secret_access_key=xxxxx
その後、.env
のAWS_PROFILE
に記載します。
# AWS settings
...
AWS_PROFILE=batch-sample-CloudformationExecuteUser
...
CloudFormationのサービスロールを作成
IAMユーザーの作成権限が必要なので、こちらもセキュリティの都合で権限が与えられない場合はgenerate_cloudformation_service_role.yml
を参考にロールを作成してもらってください。
権限がある方はまずそのユーザーを~/.aws/credentials
のプロファイルを編集した上で.env
のAWS_PROFILE_ADMIN
に追記しましょう
- ~/.aws/credentials
[batch-sample-IAMRoleCreateUser]
aws_access_key_id=xxxxx
aws_secret_access_key=xxxxx
- .env
# AWS settings
...
AWS_PROFILE_ADMIN=batch-sample-IAMRoleCreateUser
...
そのうえで、以下のyamlを使ってCloudFormationでサービスロールを作成します。
- generate_cloudformation_service_role.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Make CloudFormation service role
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
CloudFormationServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PJPrefix}-CloudFormationServiceRole"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- cloudformation.amazonaws.com
Action:
- sts:AssumeRole
Path: "/service-role/"
CloudFormationServiceRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${PJPrefix}-CloudFormationServiceRolePolicy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "batch:DescribeJobQueues"
- "batch:CreateComputeEnvironment"
- "batch:DeleteComputeEnvironment"
- "batch:SubmitJob"
- "batch:UpdateComputeEnvironment"
- "batch:DescribeComputeEnvironments"
- "batch:CreateJobQueue"
- "batch:RegisterJobDefinition"
- "batch:DescribeJobDefinitions"
- "batch:DeleteJobQueue"
- "batch:UpdateJobQueue"
- "batch:DeregisterJobDefinition"
- "logs:CreateLogGroup"
- "logs:PutLogEvents"
- "logs:CreateLogStream"
Resource: "*"
- Effect: Allow
Action:
- "iam:GetRole"
- "iam:PassRole"
- "iam:DetachRolePolicy"
- "iam:UntagRole"
- "iam:ListRoleTags"
- "iam:DeleteRolePolicy"
- "iam:TagRole"
- "iam:CreateRole"
- "iam:DeleteRole"
- "iam:AttachRolePolicy"
- "iam:PutRolePolicy"
- "route53:AssociateVPCWithHostedZone"
Resource:
- "arn:aws:iam::616902636047:role/*"
- "arn:aws:route53:::hostedzone/*"
- "arn:aws:ec2:ap-northeast-1:616902636047:vpc/*"
- Effect: Allow
Action:
- "rds:*"
- "ec2:*"
Resource: "*"
- Effect: Allow
Action:
- 'ecr:CreateRepository'
- 'ecr:BatchGetImage'
- 'ecr:CompleteLayerUpload'
- 'ecr:GetAuthorizationToken'
- 'ecr:UploadLayerPart'
- 'ecr:InitiateLayerUpload'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:PutImage'
Resource: '*'
- Effect: Allow
Action:
- 'ecr:CreateRepository'
- 'ecr:DeleteRepository'
Resource: '*'
Roles:
- !Ref CloudFormationServiceRole
Outputs:
CloudFormationServiceRole:
Value: !GetAtt CloudFormationServiceRole.Arn
Export:
Name: !Sub "${PJPrefix}-CloudFormationServiceRole"
- generate_cloudformation_service_role.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
}
]
- generate_cloudformation_service_role.sh
source .env
export AWS_PROFILE="$AWS_PROFILE_ADMIN"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$GENERATE_CLOUDFORMATION_SERVICE_ROLE_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_cloudformation_service_role.yml \
--parameters file://generate_cloudformation_service_role.json \
--capabilities CAPABILITY_NAMED_IAM
各種ファイルを作成したらシェルスクリプトを実行しましょう!
bash generate_cloudformation_service_role.sh
作成後にAWSコンソールなどで作成したサービスロールのarnを確認して.env
のCLOUDFORMATION_SERVICE_ROLE_ARN
追記します。
# AWS settings
...
CLOUDFORMATION_SERVICE_ROLE_ARN=arn:aws:iam::xxxxxxxxxxxx:role/service-role/batch-sample-CloudFormationServiceRole
AWS Batchで動作させるためのサンプルのdocker imageを作成
AWS BatchはDockerイメージを利用しますので、適当なイメージを作成します。
- dockerfile
FROM amazonlinux:2
SHELL ["/bin/bash", "-c"]
ARG PROJECT_DIR
WORKDIR $PROJECT_DIR
CMD ["/bin/bash"]
- docker-compose.yml
version: '3.5'
services:
batch:
container_name: ${PJ_PREFIX}${SEPARATOR}${CONTAINER_NAME}
image: ${PJ_PREFIX}${SEPARATOR}${IMAGE_NAME}
build:
context: ./
dockerfile: Dockerfile
args:
- PROJECT_DIR=/var/app
env_file:
.env
working_dir: /var/app
tty: true
dockerでコンテナを立ち上げる
作成したDockerfileからイメージをビルドしていきましょう。
docker-compose.ymlに変数を利用しているので、—env-fileの指定が必要になります。
docker compose --env-file .env build
イメージを確認しましょう。
作成されていますね!
% docker image ls | grep batch
batch-sample-image latest xxxxxxxxxxxx 31 seconds ago 164MB
コンテナでコマンドを実行できることを確認しておきましょう。
% docker container run batch-sample-image pwd
/var/app
docker composeでコンテナを立ち上げる
docker compose起動用のスクリプトを作成して、毎回同じコマンドで起動できるようにしておきましょう。
- docker_up.sh
docker compose down -v
source .env
docker compose --env-file .env up -d --build
以下のコマンドで起動できるようになります。
bash docker_up.sh
同じようにコンテナでコマンドを実行してみましょう!
コマンドが正常に実行されることが確認できると思います。
% docker compose exec batch pwd
/var/app
ECRのリポジトリをCloudFormationで作成
ECRはDockerイメージをAWS上にホストしておけるサービスで、FargateやECS、AWS Batchで必須のサービスになります。
こちらにイメージ格納用のリポジトリを作成していきましょう。
以下のファイル群を作成していきます。
- generate_ecr_repository.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Make ECR repository
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
Resources:
MyECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub "${PJPrefix}-image"
Outputs:
MyECRRepository:
Value: !Ref MyECRRepository
Export:
Name: !Sub "${PJPrefix}-image"
- generate_ecr_repository.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
}
]
- generate_ecr_repository.sh
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$REPOSITORY_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_ecr_repository.yml \
--parameters file://generate_ecr_repository.json \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn "$CLOUDFORMATION_SERVICE_ROLE_ARN"
シェルスクリプトを実行します。
bash generate_ecr_repository.sh
リポジトリが作成されたか確認します。
コンソールで確認することもできますが、今回はAWS CLIで確認してみます。
ECRのリポジトリ操作権限を持つアクセスキーがない場合は、コンソールか管理者に確認してもらいましょう。
コマンド中の--profile admin_user
は操作権限を持つユーザーに各自置き換えてください。
% aws --profile admin_user --region ap-northeast-1 ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
batch-sample-image
ECRへイメージをプッシュ
次にECRへ作成したイメージをプッシュするためのスクリプトを書きましょう。
- ecr_image_push.sh
docker compose down -v
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export REPOSITORY_NAME="$PJ_PREFIX""$SEPARATOR""$IMAGE_NAME"
aws ecr get-login-password | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID".dkr.ecr.ap-northeast-1.amazonaws.com
docker compose --env-file .env build
docker tag "$REPOSITORY_NAME":latest "$AWS_ACCOUNT_ID".dkr.ecr.ap-northeast-1.amazonaws.com/"$REPOSITORY_NAME":latest
docker push "$AWS_ACCOUNT_ID".dkr.ecr.ap-northeast-1.amazonaws.com/"$REPOSITORY_NAME":latest
スクリプトを実行してイメージをプッシュします。
bash ecr_image_push.sh
ECRのイメージをpullできるか確認
スクリプトを用意します。
- ecr_image_pull.sh
docker compose down -v
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export REPOSITORY_NAME="$PJ_PREFIX""$SEPARATOR""$IMAGE_NAME"
aws ecr get-login-password | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID".dkr.ecr.ap-northeast-1.amazonaws.com
docker pull "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_DEFAULT_REGION".amazonaws.com/"$REPOSITORY_NAME":latest
実行します。
bash ecr_image_pull.sh
正常にpullできているか確認しましょう。
% docker image ls | grep amazonaws.com
xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/batch-sample-image latest xxxxxxxxxxxx 6 days ago 164MB
pullしたイメージを利用してコンテナを立ち上げ、コマンドを実行してみましょう
% docker container run --rm xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/batch-sample-image ls -al
total 8
drwxr-xr-x 2 root root 4096 Jun 14 06:49 .
drwxr-xr-x 1 root root 4096 Jun 14 06:49 ..
ネットワークの作成
AWS Batchに必要なリソースを作成していきましょう。
ここからはとにかくCloudFormation用のテンプレートファイル(yaml)を作成、それに与えるパラメーター(json)を作成、実行のためのシェルスクリプトを作成、そして実行!という流れになります。
CloudFormationのスタックの作成自体はCLIで行なっていきますが、エラーが発生するかもしれないので、コンソールを見れる方は見ながら作業するのをお勧めします!
- generate_network.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Build VPC network
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
VpcCidrBlock:
Description: Input a VPC IPv4 CidrBlock. ex) 192.168.0.0/16
Type: String
Default: "192.168.0.0/16"
AZ1:
Description: Input a AZ will be created.
Type: AWS::EC2::AvailabilityZone::Name
AZ2:
Description: Input a AZ will be created.
Type: AWS::EC2::AvailabilityZone::Name
PublicSubnetCidrBlock1:
Description: Input a Public Subnet IPv4 CidrBlock. ex) 192.168.1.0/24
Type: String
Default: "192.168.1.0/24"
PublicSubnetCidrBlock2:
Description: Input a Public Subnet IPv4 CidrBlock. ex) 192.168.2.0/24
Type: String
Default: "192.168.2.0/24"
PrivateSubnetCidrBlock1:
Description: Input a Private Subnet IPv4 CidrBlock. ex) 192.168.3.0/24
Type: String
Default: "192.168.3.0/24"
PrivateSubnetCidrBlock2:
Description: Input a Private Subnet IPv4 CidrBlock. ex) 192.168.4.0/24
Type: String
Default: "192.168.4.0/24"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Project Configuration
Parameters:
- PJPrefix
- Label:
default: Network Configuration
Parameters:
- VpcCidrBlock
- AZ1
- AZ2
- PublicSubnetCidrBlock1
- PublicSubnetCidrBlock2
- PrivateSubnetCidrBlock1
- PrivateSubnetCidrBlock2
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidrBlock
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-vpc"
# ------------------------------------------------------------#
# Public Subnet
# ------------------------------------------------------------#
MyPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1
CidrBlock: !Ref PublicSubnetCidrBlock1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-public-subnet-1"
VpcId: !Ref MyVPC
MyPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ2
CidrBlock: !Ref PublicSubnetCidrBlock2
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-public-subnet-2"
VpcId: !Ref MyVPC
# ------------------------------------------------------------#
# Private Subnet
# ------------------------------------------------------------#
MyPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1
CidrBlock: !Ref PrivateSubnetCidrBlock1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-private-subnet-1"
VpcId: !Ref MyVPC
MyPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ2
CidrBlock: !Ref PrivateSubnetCidrBlock2
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-private-subnet-2"
VpcId: !Ref MyVPC
# ------------------------------------------------------------#
# Internet Gateway
# ------------------------------------------------------------#
MyIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-igw"
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref MyIGW
VpcId: !Ref MyVPC
# ------------------------------------------------------------#
# PubLic Route Table
# ------------------------------------------------------------#
MyPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-public-route-table"
VpcId: !Ref MyVPC
MyPublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyIGW
RouteTableId: !Ref MyPublicRouteTable
MyPublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet1
MyPublicSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet2
# ------------------------------------------------------------#
# Private Route Table
# ------------------------------------------------------------#
MyPrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-private-route-table"
VpcId: !Ref MyVPC
MyPrivateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet1
MyPrivateSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet2
Outputs:
MyVPC:
Value: !Ref MyVPC
Export:
Name: !Sub "${PJPrefix}-vpc"
MyPublicSubnet1:
Value: !Ref MyPublicSubnet1
Export:
Name: !Sub "${PJPrefix}-public-subnet-1"
MyPublicSubnet2:
Value: !Ref MyPublicSubnet2
Export:
Name: !Sub "${PJPrefix}-public-subnet-2"
MyPrivateSubnet1:
Value: !Ref MyPrivateSubnet1
Export:
Name: !Sub "${PJPrefix}-private-subnet-1"
MyPrivateSubnet2:
Value: !Ref MyPrivateSubnet2
Export:
Name: !Sub "${PJPrefix}-private-subnet-2"
MyPublicRouteTable:
Value: !Ref MyPublicRouteTable
Export:
Name: !Sub "${PJPrefix}-public-route-table"
MyPrivateRouteTable:
Value: !Ref MyPrivateRouteTable
Export:
Name: !Sub "${PJPrefix}-private-route-table"
- generate_network.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
},
{
"ParameterKey": "VpcCidrBlock",
"ParameterValue": "192.168.0.0/20"
},
{
"ParameterKey": "AZ1",
"ParameterValue": "ap-northeast-1a"
},
{
"ParameterKey": "AZ2",
"ParameterValue": "ap-northeast-1c"
},
{
"ParameterKey": "PublicSubnetCidrBlock1",
"ParameterValue": "192.168.0.0/24"
},
{
"ParameterKey": "PublicSubnetCidrBlock2",
"ParameterValue": "192.168.1.0/24"
},
{
"ParameterKey": "PrivateSubnetCidrBlock1",
"ParameterValue": "192.168.2.0/24"
},
{
"ParameterKey": "PrivateSubnetCidrBlock2",
"ParameterValue": "192.168.3.0/24"
}
]
- generate_network.sh
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$NETWORK_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_network.yml \
--parameters file://generate_network.json \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn "$CLOUDFORMATION_SERVICE_ROLE_ARN"
- スクリプトを実行
bash generate_network.sh
セキュリティグループの作成
大変ですが、ガシガシリソースを作っていきましょう!
- generate_security_group.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Build SecurityGroup
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
SGDescription:
Description: Input a SecurityGroupDescription.
Type: String
Default: "my security-group description"
VPCSGDescription:
Description: Input a SecurityGroupDescription.
Type: String
Default: "my vpc-endpoint-security-group description"
Resources:
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${PJPrefix}-security-group"
GroupDescription: !Ref SGDescription
VpcId:
Fn::ImportValue: !Sub "${PJPrefix}-vpc"
SecurityGroupEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId: !Ref MySecurityGroup
IpProtocol: -1
CidrIp: 0.0.0.0/0
# ------------------------------------------------------------#
# VPC Endpoint Security Group
# ------------------------------------------------------------#
MyVPCEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${PJPrefix}-vpc-endpoint-security-group"
GroupDescription: !Ref VPCSGDescription
VpcId:
Fn::ImportValue: !Sub "${PJPrefix}-vpc"
VPCEndpointSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref MyVPCEndpointSecurityGroup
IpProtocol: tcp
ToPort: 443
FromPort: 443
SourceSecurityGroupId:
!Ref MySecurityGroup
VPCEndpointSecurityGroupEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId: !Ref MyVPCEndpointSecurityGroup
IpProtocol: -1
CidrIp: 0.0.0.0/0
Outputs:
MySecurityGroup:
Value: !Ref MySecurityGroup
Export:
Name: !Sub "${PJPrefix}-security-group"
MyVPCEndpointSecurityGroup:
Value: !Ref MyVPCEndpointSecurityGroup
Export:
Name: !Sub "${PJPrefix}-vpc-endpoint-security-group"
- generate_security_group.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
},
{
"ParameterKey": "SGDescription",
"ParameterValue": "batch-sample security-group description"
},
{
"ParameterKey": "VPCSGDescription",
"ParameterValue": "batch-sample vpc-endpoint-security-group description"
}
]
- generate_security_group.sh
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$SECURITY_GROUP_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_security_group.yml \
--parameters file://generate_security_group.json \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn "$CLOUDFORMATION_SERVICE_ROLE_ARN"
スクリプトを実行
bash generate_security_group.sh
AWS Batchに必要なIAMロールを作成する
- generate_iam_role.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Make IAM role
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
# example: https://docs.amazonaws.cn/en_us/batch/latest/userguide/service_IAM_role.html
AWSBatchServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PJPrefix}-AWSBatchServiceRole"
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
Path: "/service-role/"
# example: https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_execution_IAM_role.html
ecsTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${PJPrefix}-ecsTaskExecutionRole"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Path: "/service-role/"
Outputs:
AWSBatchServiceRole:
Value: !GetAtt AWSBatchServiceRole.Arn
Export:
Name: !Sub "${PJPrefix}-AWSBatchServiceRole"
ecsTaskExecutionRole:
Value: !GetAtt ecsTaskExecutionRole.Arn
Export:
Name: !Sub "${PJPrefix}-ecsTaskExecutionRole"
- generate_iam_role.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
}
]
- generate_iam_role.sh
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$IAM_ROLE_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_iam_role.yml \
--parameters file://generate_iam_role.json \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn "$CLOUDFORMATION_SERVICE_ROLE_ARN"
スクリプトを実行
bash generate_iam_role.sh
AWS Batchのテンプレートを作成
いよいよAWS Batchを作成していきます!
- generate_aws_batch.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Build AWS Batch environment
Parameters:
PJPrefix:
Description: Input a project prefix. ex) my-project
Type: String
Default: "my-project"
Resources:
# ------------------------------------------------------------#
# ComputeEnvironment
# ------------------------------------------------------------#
MyComputeEnv:
Type: "AWS::Batch::ComputeEnvironment"
Properties:
Type: MANAGED
State: ENABLED
ComputeEnvironmentName: !Sub "${PJPrefix}-compute-environment"
ServiceRole:
Fn::ImportValue: !Sub "${PJPrefix}-AWSBatchServiceRole"
ComputeResources:
MaxvCpus: 256
SecurityGroupIds:
- Fn::ImportValue: !Sub "${PJPrefix}-security-group"
Type: FARGATE
Subnets:
- Fn::ImportValue: !Sub "${PJPrefix}-public-subnet-1"
- Fn::ImportValue: !Sub "${PJPrefix}-public-subnet-2"
# ------------------------------------------------------------#
# JobQueue
# ------------------------------------------------------------#
MyJobQueue:
Type: AWS::Batch::JobQueue
Properties:
ComputeEnvironmentOrder:
- Order: 1
ComputeEnvironment: !Ref MyComputeEnv
State: ENABLED
Priority: 1
JobQueueName: !Sub "${PJPrefix}-job-queue"
# ------------------------------------------------------------#
# JobDefinition
# ------------------------------------------------------------#
MyJobDefinition:
Type: AWS::Batch::JobDefinition
Properties:
JobDefinitionName: !Sub "${PJPrefix}-job-definition"
Type: container
RetryStrategy:
Attempts: 1
Timeout:
AttemptDurationSeconds: 300
PlatformCapabilities:
- FARGATE
ContainerProperties:
Image: !Sub
- "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryName}"
- RepositoryName: { 'Fn::ImportValue': !Sub '${PJPrefix}-image' }
NetworkConfiguration:
AssignPublicIp: ENABLED
FargatePlatformConfiguration:
PlatformVersion: 1.4.0
ResourceRequirements:
- Type: VCPU
Value: 4
- Type: MEMORY
Value: 8192
ExecutionRoleArn:
Fn::ImportValue: !Sub "${PJPrefix}-ecsTaskExecutionRole"
Command:
- 'pwd'
- generate_aws_batch.json
[
{
"ParameterKey": "PJPrefix",
"ParameterValue": "batch-sample"
}
]
- generate_aws_batch.sh
source .env
export AWS_PROFILE="$AWS_PROFILE"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export STACK_NAME="$PJ_PREFIX""$SEPARATOR""$BATCH_SUFFIX"
aws cloudformation create-stack \
--stack-name "$STACK_NAME" \
--template-body file://generate_aws_batch.yml \
--parameters file://generate_aws_batch.json \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn "$CLOUDFORMATION_SERVICE_ROLE_ARN"
- スクリプトを実行
bash generate_aws_batch.sh
作成したBatchを実行してみる
AWS CLIを利用して以下のようなコマンドでジョブを登録してみましょう。
job-nameは適当に設定、job-queueは作成したジョブキューを指定、job-definitionも作成したジョブ定義を指定しましょう。
もしかするとジョブ登録に必要な権限を持っていないかもしれないので、適切に付与しましょう。
- job_registration.sh
source .env
export AWS_PROFILE="$AWS_PROFILE_ADMIN"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export PJ_PREFIX="$PJ_PREFIX"
aws batch submit-job \
--job-name "$PJ_PREFIX"-job \
--job-queue "$PJ_PREFIX"-job-queue \
--job-definition "$PJ_PREFIX"-job-definition
スクリプトを実行します。
bash job_registration.sh
AWS Batchはコンテナ起動のオーバーヘッドが少し大きいので、ジョブ登録から1分程度立った後に、こんなスクリプトも作成して、jobのステータスをのぞいてみましょう!
source .env
export AWS_PROFILE="$AWS_PROFILE_ADMIN"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export PJ_PREFIX="$PJ_PREFIX"
export JOB_NAME="$PJ_PREFIX"-job
JOB_LOG=$( aws logs describe-log-streams \
--log-group-name /aws/batch/job \
| jq '.logStreams' \
| jq "map(select(.logStreamName | index(\"${JOB_NAME}\")))" \
) && echo ${JOB_LOG}
aws logs get-log-events \
--log-group-name /aws/batch/job \
--log-stream-name $( echo ${JOB_LOG} \
| jq -r '.[].logStreamName' ) \
| jq '.events | map({time: (.timestamp / 1000 | strftime("%Y/%m/%d %H:%M:%S")), message: .message})'
スクリプトを実行します。
問題なくジョブが動作していれば以下のようなレスポンスが得られます!
"message": "/var/app"
となっており、ジョブ定義の通り、pwdコマンド実行の結果が返ってきました!成功です!
% bash job_status.sh
[ { "logStreamName": "batch-sample-job-definition/default/ab7dbced4e9e445b99e5cb3fa073f74b", "creationTime": 1655901962342, "firstEventTimestamp": 1655901969204, "lastIngestionTime": 1655901973881, "uploadSequenceToken": "xxxxxxxx", "arn": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxx:log-group:/aws/batch/job:log-stream:batch-sample-job-definition/default/ab7dbced4e9e445b99e5cb3fa073f74b", "storedBytes": 0 } ]
[
{
"time": "2022/06/22 12:46:09",
"message": "/var/app"
}
]
終わりに
ということで、それなりのボリュームになってしまったので、「Lambdaよりもめちゃくちゃめんどくさいじゃないか!」って思った方もいるかもしれませんが、テンプレート化することにより、イメージを入れ替えるだけでどんなバッチも作れてしまいます!
最初は大変かもしれませんが、ぜひ活用してみてください!