Posted at

[JAWS-UG CLI] AWS Batch #3 より実践的な使い方

More than 1 year has passed since last update.


前提条件


EC2への権限

EC2、ECS、AWS Batch などに対してフル権限があること。


0. 準備


0.1. ジョブの作成と実行の完了

AWS Batch #2 ジョブの作成と実行 が終わっていること


0.2. 変数の確認

#1, #2 から引き続き利用する変数を確認します


コマンド

cat << ETX

CFN_STACK_NAME: ${CFN_STACK_NAME}
COMPUTE_ENV_NAME:
${COMPUTE_ENV_NAME}
JOB_QUEUE_NAME:
${JOB_QUEUE_NAME}
JOB_DEFINITION_NAME:
${JOB_DEFINITION_NAME}
CONRAINER_PROPS_FILE:
${CONRAINER_PROPS_FILE}

ETX



結果(例)


CFN_STACK_NAME: aws-batch-xxxxxxxxxx
COMPUTE_ENV_NAME: aws-batch-managed-xxxxxxxxxx
JOB_QUEUE_NAME: aws-batch-job-queue-xxxxxxxxxx
JOB_DEFINITION_NAME: aws-batch-job-def-xxxxxxxxxx
CONRAINER_PROPS_FILE: aws_batch_container_props.json



0.3. AWS CLIのバージョン

以下のバージョンで動作確認済


  • AWS CLI 1.11.36


コマンド

aws --version



結果(例)

 aws-cli/1.11.36 Python/2.7.5 Darwin/13.4.0 botocore/1.4.93


バージョンが古い場合は最新版に更新しましょう。


コマンド

sudo -H pip install -U awscli



1. サンプルアプリケーション


  • AWS Batch で 定例バッチ1 を実装してみます

  • COBOL での帳票出力を AWS Batch で動かします

  • 作成された帳票は S3 へアップロード

  • ジョブには S3 へのアップロード権限があるロールを付与

  • CloudWatch Events + Lambda から毎分ごとに Job を Submit


1.1 Docker リポジトリの作成

ジョブ実行ロジックを push するためのリポジトリを作ります


コマンド

DOCKER_REPO=$( aws ecr create-repository \

--repository-name ${CFN_STACK_NAME}/sample \
| jq -r '.repository.repositoryUri' \
) && echo ${DOCKER_REPO}


結果(例)

xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/aws-batch-xxxxxxxxxx



1.2. #1 で生成したスタックから変数の取得

CloudFormation スタックの生成結果を再取得


コマンド

CFN_STACK_RESULT=$( aws cloudformation describe-stacks \

--stack-name ${CFN_STACK_NAME})

スタックの生成結果(Output)から、必要な変数を抜き出します。


コマンド

S3_BUCKET_NAME=$( echo ${CFN_STACK_RESULT} \

| jq '.Stacks[].Outputs[]' \
| jq -r 'select(.OutputKey=="S3Bucket").OutputValue' )

BATCH_JOB_ROLE=$( echo ${CFN_STACK_RESULT} \
| jq '.Stacks[].Outputs[]' \
| jq -r 'select(.OutputKey=="BatchJobRole").OutputValue' )

cat << ETX

S3_BUCKET_NAME: ${S3_BUCKET_NAME}
BATCH_JOB_ROLE:
${BATCH_JOB_ROLE}

ETX



結果


S3_BUCKET_NAME: aws-batch-xxxxxxxxxx-s3bucket-xxxxxxxxxxx
BATCH_JOB_ROLE: arn:aws:iam::xxxxxxxxxxxx:role/aws-batch-xxxxxxxxxx-BatchJobRole-xxxxxxxxxxxxx



1.3. ジョブ定義の作成

コンテナ定義ファイルを再生成します。


コマンド

cat << EOF > ${CONRAINER_PROPS_FILE}

{
"image": "
${DOCKER_REPO}",
"command": ["Ref::Arg1", "Ref::Arg2"],
"jobRoleArn": "
${BATCH_JOB_ROLE}",
"environment": [
{ "name": "AWS_DEFAULT_REGION", "value": "
${AWS_DEFAULT_REGION}"},
{ "name": "AWS_S3_BUCKET", "value": "
${S3_BUCKET_NAME}"},
{ "name": "APP_VERSION", "value": "production!"}
],
"vcpus": 1,
"memory": 100
}
EOF
cat ${CONRAINER_PROPS_FILE}

コンテナ定義ファイルの検証


コマンド

jsonlint -q ${CONRAINER_PROPS_FILE}


これを使い、ジョブ定義を更新します


コマンド

aws batch register-job-definition \

--job-definition-name ${JOB_DEFINITION_NAME} \
--container-properties file://${CONRAINER_PROPS_FILE} \
--type container


結果(例)

{

"jobDefinitionArn": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:1",
"jobDefinitionName": "aws-batch-job-def-xxxxxxxxxx",
"revision": 2
}


1.4. ジョブ投入に使う変数の確認

流したジョブを識別できるよう名前を決めます


コマンド

JOB_NAME="job-`date +%s`"


リビジョンが最新のジョブ定義 ARN を取得します


コマンド

JOB_DEFINITION_ARN=$( aws batch describe-job-definitions \

--job-definition-name ${JOB_DEFINITION_NAME} \
--status ACTIVE \
| jq -r '.jobDefinitions | max_by(.revision).jobDefinitionArn' \
) && echo ${JOB_DEFINITION_ARN}


結果(例)

 arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:2



2. ジョブ投入のための追加リソース生成


2.1. CloudFormation スタックの生成

テンプレートの取得


コマンド

curl --location --output ${CFN_STACK_NAME}-job.yaml \

https://raw.githubusercontent.com/supinf/aws-batch-refarch/master/cloudformation/job-executor.yaml

CloudFormation スタックの生成


コマンド

aws cloudformation create-stack \

--stack-name ${CFN_STACK_NAME}-job \
--template-body file://${CFN_STACK_NAME}-job.yaml \
--parameters ParameterKey=JobName,ParameterValue=${JOB_NAME} \
ParameterKey=JobQueueName,ParameterValue=${JOB_QUEUE_NAME} \
ParameterKey=JobDefinitionArn,ParameterValue=${JOB_DEFINITION_ARN} \
--capabilities CAPABILITY_IAM


結果(例)

{

"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/aws-batch-xxxxxxxxxx-job/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

このスタックにより以下のリソースが作成されます


  • Job 投入 Lambda

  • 定期的にその Lambda を蹴る CloudWatch Events


2.2. 生成されたリソースから変数を取得

スタックの生成を待機


コマンド

aws cloudformation wait stack-create-complete \

--stack-name ${CFN_STACK_NAME}-job


結果

返り値なし


生成結果を取得


コマンド

CFN_STACK_RESULT=$( aws cloudformation describe-stacks \

--stack-name ${CFN_STACK_NAME}-job \
) && echo ${CFN_STACK_RESULT} | jq .

スタックの生成結果(Output)から、必要な変数を抜き出します。


コマンド

TRIGGER_ARN=$( echo ${CFN_STACK_RESULT} \

| jq '.Stacks[].Outputs[]' \
| jq -r 'select(.OutputKey=="BatchJobEvent").OutputValue' )
echo ${TRIGGER_ARN}


結果(例)

arn:aws:events:us-east-1:xxxxxxxxxxxx:rule/BatchJobTriggerRule



3. 実行するジョブの準備

引数 2 つ、環境変数 2 つを受け取り、ファイルを生成する

以下の COBOL プログラムをジョブとして動かしてみます。

https://github.com/pottava/docker-cobol/blob/master/examples/batch.cbl

(サンプルには COBOL 言語を使っていますが、もちろんシェルスクリプトでも、Java でも Python でも、Swift でも Elixir でも Docker にさえすれば AWS Batch 上で動きます)


3.1. エントリーポイントの用意

AWS Batch からの引数をうまいこと受け取り、かつ出力されたファイルを S3 にアップロードするためのラッパースクリプトを用意します。

ファイル名を決め


コマンド

DOCKER_ENTRYPOINT_FILE="entrypoint.sh"


生成します


entrypoint.sh

cat << EOF > ${DOCKER_ENTRYPOINT_FILE}

#!/bin/bash

if [ -z "\$AWS_DEFAULT_REGION" ]; then
echo "Missing environment variable 'AWS_DEFAULT_REGION'." 1>&2
exit 1
fi
if [ -z "
\$AWS_S3_BUCKET" ]; then
echo "Missing environment variable 'AWS_S3_BUCKET'." 1>&2
exit 1
fi
if [ -z "
\$AWS_S3_KEY" ]; then
AWS_S3_KEY="result-
\$(date +%Y%m%d%H%M)"
fi

/usr/local/bin/batch \$@

rc=\$?
if [
\$rc -ne 0 ] ;then
echo "[Error] Executing batch went wrong..." 1>&2
echo "ARGUMENTS: "
\$@ 1>&2
echo "APP_VERSION: "
\${APP_VERSION} 1>&2
echo "APP_TARGET: "
\${APP_TARGET} 1>&2
exit
\$rc
fi

aws --region \$AWS_DEFAULT_REGION s3api put-object \\
--bucket
\$AWS_S3_BUCKET --key \$AWS_S3_KEY \\
--body result.txt

rc=\$?
if [
\$rc -ne 0 ] ;then
echo "[Error] Sending job results went wrong..." 1>&2
echo "ARGUMENTS: "
\$@ 1>&2
echo "APP_VERSION: "
\${APP_VERSION} 1>&2
echo "APP_TARGET: "
\${APP_TARGET} 1>&2
echo "AWS_S3_BUCKET: "
\${AWS_S3_BUCKET} 1>&2
echo "AWS_S3_KEY: "
\${AWS_S3_KEY} 1>&2
exit
\$rc
fi
EOF



3.2. Dockerfile の作成

お決まりのファイル名で


コマンド

DOCKER_FILE="Dockerfile"


生成します


Dockerfile

cat << EOF > ${DOCKER_FILE}

FROM debian:wheezy-slim

ADD https://raw.githubusercontent.com/pottava/docker-cobol/master/examples/batch.cbl /usr/local/src/
ADD entrypoint.sh /

RUN BUILD_PACKAGES="build-essential curl" \\
&& apt-get update && apt-get autoremove -y
\\
&& apt-get install -y
\${BUILD_PACKAGES} open-cobol python \\
&& chmod +x /entrypoint.sh
\\

# Build application
&& cd /usr/local/bin \\
&& cobc -x /usr/local/src/batch.cbl
\\

# Install AWS-CLI
&& curl --location --silent --show-error \\
https://bootstrap.pypa.io/get-pip.py | python
\\
&& pip install "awscli==1.11.56"
\\

# Clean up
&& apt-get purge -y --auto-remove \${BUILD_PACKAGES} \\
&& find / -depth -type d -name test -exec rm -rf {}
\\; \\
&& find / -depth -type d -name
\__pycache__ -exec rm -rf {} \\; \\
&& rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* /root/.cache

WORKDIR /app

ENV AWS_DEFAULT_REGION=ap-northeast-1 \\
AWS_S3_BUCKET=""
\\
AWS_S3_KEY=""
\\
APP_VERSION=0.1.0
\\
APP_TARGET=executor

ENTRYPOINT ["/entrypoint.sh"]
EOF



3.3. アプリケーションの Docker イメージ化

ECR のリポジトリ名を指定しつつ、ビルドします。

回線が細いと 15 分ほどかかるかもしれません。


コマンド

docker build -t ${DOCKER_REPO} .



結果(例)

Sending build context to Docker daemon 24.06 kB

Step 1/7 : FROM debian:wheezy-slim
---> b7230ec23103
Step 2/7 : ADD https://raw.githubusercontent.com/pottava/docker-cobol/master/examples/batch.cbl /usr/local/src/
Downloading 2.687 kB
---> Using cache
---> 723925bd3df1

(中略)

Step 7/7 : ENTRYPOINT /entrypoint.sh
---> Running in 94ca4974b074
---> ca617324a141
Removing intermediate container 94ca4974b074
Successfully built ca617324a141


ECR にログインし、Docker イメージを push しましょう


コマンド

aws ecr get-login | sh

docker push ${DOCKER_REPO}


結果(例)

The push refers to a repository [xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/aws-batch-xxxxxxxxxx/sample]

de07e4081e81: Pushed
f0c9c1e82d65: Pushed
4e4c70200302: Pushed
d4a5f6671fae: Pushed
6db2a2c16cbd: Pushed
latest: digest: sha256:c7e5ef31e1bde0a6ec4327118e5d2976da098e7a5e9fa5a7ee236ceb942e094a size: 1361


4. ジョブの投入

Docker イメージも準備できました。

では事前に CloudFormation で用意しておいた Lambda を CloudWatch Events から定期的に起動し、AWS Batch へ Job を Submit してみましょう。


4.1. CloudWatch Events の有効化

1 分おきに Lambda を蹴るルールは定義済みですが

DISABLED で停止している状態になっています。

ENABLE に更新して処理を開始してみましょう。


コマンド

aws events enable-rule \

--name $( aws events list-rules | jq '.Rules' \
| jq "map(select(.Arn==\"${TRIGGER_ARN}\"))" \
| jq -r '.[].Name' )


結果

返り値なし



4.2. Job ステータスの確認

流れてくる Job の様子を確認します。

1 分おきに結果が増えていくはずです。


コマンド

aws batch list-jobs \

--job-queue ${JOB_QUEUE_NAME} \
--job-status SUCCEEDED \
| jq '.jobSummaryList' \
| jq "map(select(.jobName==\"${JOB_NAME}\"))"


結果(例)

[

{
"jobName": "job-1488375104",
"jobId": "71895de8-b28c-41eb-b94f-43fe2fb7878a"
},
{
"jobName": "job-1488375104",
"jobId": "cbc64814-a27a-48cc-9376-d67c95984505"
}
]


4.3. 出力された帳票の確認

まずは帳票の一覧を表示してみます


コマンド

aws s3api list-objects \

--bucket ${S3_BUCKET_NAME} \
| jq '.Contents' \
| jq 'map({Key: .Key, LastModified: .LastModified})'


結果(例)

[

{
"Key": "result-201703011749",
"LastModified": "2017-03-01T17:49:13.000Z"
},
{
"Key": "result-201703011750",
"LastModified": "2017-03-01T17:50:18.000Z"
}
]

どれかを選び、ダウンロードしてみましょう


コマンド

S3_KEY="result-201703011749"



コマンド

aws s3api get-object \

--bucket ${S3_BUCKET_NAME} \
--key ${S3_KEY} ${S3_KEY}


結果(例)

{

"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Wed, 01 Mar 2017 17:49:13 GMT",
"ContentLength": 121,
"ETag": "\"190d1fecd45c7996ab8f124cd60a42ee\"",
"Metadata": {}
}

ファイルを開いてみます


コマンド

cat ${S3_KEY}



結果(例)

     2 17:48:59        from-AWS-Lambda

production! 0dfc1g7b47
1 49 12
2 49 12
3 49 12
4 49 12
5 49 12


完了

より実践的な使い方は以上です。

AWS Batch #4 環境の破棄

 





  1. AWS Batch にはまる ユースケース ではないものの、できないわけではありません。ネタです。