AWS
aws-cli
AWS-Batch

[JAWS-UG CLI] AWS Batch #2 ジョブの作成と実行

More than 1 year has passed since last update.

前提条件

EC2への権限

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

0. 準備

0.1. 環境構築

#1 環境構築 Managed 編 または Unmanaged 編 が終わっていること

0.2. 変数の確認

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

コマンド
cat << ETX

    COMPUTE_ENV_NAME: ${COMPUTE_ENV_NAME}
    JOB_QUEUE_NAME:   ${JOB_QUEUE_NAME}

ETX
結果(例)
    COMPUTE_ENV_NAME: aws-batch-managed-xxxxxxxxxx
    JOB_QUEUE_NAME:   aws-batch-job-queue-xxxxxxxxxx

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

0.4. AWS アカウントの属性

EC2-Classic が見えない AWS アカウントであること。

コマンド
AWS_SUPPORT_PLATFORMS=$( \
         aws ec2 describe-account-attributes \
           --query 'AccountAttributes[?AttributeName == `supported-platforms`].AttributeValues[].AttributeValue' \
           --output text \
) && echo ${AWS_SUPPORT_PLATFORMS}
結果
 VPC

注釈: VPC の他に EC2 が表示される場合、別のアカウントを作成してください。

1. ジョブの定義

1.1. ジョブ定義名の指定

定義名の指定

コマンド
JOB_DEFINITION_NAME="aws-batch-job-def-`date +%s`"

同名のジョブ定義がないことを確認

コマンド
aws batch describe-job-definitions \
  --job-definition-name ${JOB_DEFINITION_NAME}
結果
{
    "jobDefinitions": []
}

1.2. ジョブ定義の作成

現時点では AWS Batch のジョブ種別には container しか選べませんが、きっと将来的にはシェルや Linux バイナリが選べるようになるのでしょう。

それはさておき、ジョブの定義として
実行したいコンテナのプロパティ定義ファイル名を決めます。

コマンド
CONRAINER_PROPS_FILE="aws_batch_container_props.json"

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

コマンド
cat << EOF > ${CONRAINER_PROPS_FILE}
{
  "image": "busybox",
  "command": ["echo", "Ref::Target"],
  "vcpus": 1,
  "memory": 5000
}
EOF
cat ${CONRAINER_PROPS_FILE}

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

コマンド
jsonlint -q ${CONRAINER_PROPS_FILE}

これをジョブ定義として AWS Batch に登録します

コマンド
aws batch register-job-definition \
  --job-definition-name ${JOB_DEFINITION_NAME} \
  --type container \
  --container-properties file://${CONRAINER_PROPS_FILE} \
  --parameters Target="Hello\, AWS Batch :)"
結果(例)
{
    "jobDefinitionArn": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:1", 
    "jobDefinitionName": "aws-batch-job-def-xxxxxxxxxx", 
    "revision": 1
}

2. ジョブの投入

2.1. ジョブ名・ジョブ定義の取得

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

コマンド
JOB_21="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:1

2.2. ジョブの投入

早速定義したジョブをひとつ流してみましょう。

コマンド
aws batch submit-job \
  --job-name ${JOB_21} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN}
結果(例)
{
    "jobName": "job-1484056465",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}

2.3. ジョブの状況を確認

投入されたばかりのジョブは SUBMITTED というステータスになります。確認してみましょう。

コマンド
aws batch list-jobs \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-status SUBMITTED

(タイミングによっては空かもしれません)

結果(例)
{
    "jobSummaryList": [
        {
            "jobName": "job-1484056465", 
            "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
        }
    ]
}

ちなみにコマンドで --job-status を指定しないとすべてのステータスかと思いきや、何も指定しない時のデフォルト値は RUNNING(起動中)です。

しばらくするとステータスが変わるはずなので、RUNNABLE になるまで以下のコマンドを打ち待ってみます。(Mac であれば watch -n 1 などで待ってもいいですね!)

コマンド
aws batch list-jobs \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-status RUNNABLE
結果(例)
{
    "jobSummaryList": [
        {
            "jobName": "job-1484056465", 
            "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
        }
    ]
}

このジョブ、実はこの先いつになっても RUNNABLE から先に進むことはありません。
Job の実行に クラスタで使用可能なメモリ を上回る 5,000MB を要求しているためです。

2.4. ジョブの実行をキャンセル

ステータスが SUBMITTEDPENDING または RUNNABLE
Job はその実行をキャンセルできます。キャンセルします

コマンド
JOB_21_ID=$( aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status RUNNABLE \
    | jq '.jobSummaryList' \
    | jq "map(select(.jobName==\"${JOB_21}\"))[]" \
    | jq -r '.jobId' )

aws batch cancel-job \
  --job-id ${JOB_21_ID} \
  --reason 'Due to lack of memory.'
結果
返り値なし

しばらくしたらステータスを確認してみます。

コマンド
aws batch describe-jobs --jobs ${JOB_21_ID}
結果(例)
{
    "jobs": [
        {
            "status": "FAILED", 
            "container": {
                "mountPoints": [], 
                "image": "busybox", 
                "environment": [], 
                "vcpus": 1, 
                "command": [
                    "sleep", 
                    "10", 
                    "&&", 
                    "echo", 
                    "Hello, AWS Batch :)"
                ], 
                "volumes": [], 
                "memory": 1000, 
                "ulimits": []
            }, 
            "parameters": {
                "WaitTime": "10", 
                "Target": "Hello, AWS Batch :)"
            }, 
            "jobDefinition": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:1", 
            "statusReason": "Due to lack of memory.", 
            "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 
            "jobQueue": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-queue/aws-batch-job-queue-xxxxxxxxxx", 
            "dependsOn": [], 
            "jobName": "job-xxxxxxxxxx", 
            "createdAt": 1484074048650, 
            "stoppedAt": 1484074857766
        }
    ]
}

status が FAILED になりました。
細かいことですが、コンテナの実行時パラメタも引き渡したものに置換されていることも確認できますね。

3. ジョブのさまざまな投げ方

3.1. コンテナ定義を上書き Job を投入

次の Job に名前をつけ

コマンド
JOB_31="job-`date +%s`"

メモリのハードリミットを 100MB に下げて再投入します。

コマンド
aws batch submit-job \
  --job-name ${JOB_31} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN} \
  --container-overrides '{"memory": 100}'
結果(例)
{
    "jobName": "job-1484076698",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}

しばらくすると以下のコマンドで
SUCCEEDED となった JOB_31 が返ってきます。

コマンド
aws batch list-jobs \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-status SUCCEEDED \
  | jq '.jobSummaryList' \
  | jq -r "map(select(.jobName==\"${JOB_31}\"))[]"
結果(例)
{
    "jobName": "job-1484076698",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}

3.2. パラメタを変更して Job を投入

バッチジョブらしく、Job ごとにパラメタを変えてみます。
(コンテナ定義 CommandRef:XXX が都度変わります)

Job に名前をつけ

コマンド
JOB_32="job-`date +%s`"

デフォルトでは "Hello, AWS Batch :)" と echo していましたが
"Hello, Tokyo!" と echo させる Job を投入します

コマンド
aws batch submit-job \
  --job-name ${JOB_32} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN} \
  --container-overrides '{"memory": 100}' \
  --parameters Target='Hello\, Tokyo!'
結果(例)
{
    "jobName": "job-1484078516",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}

しばらくすると以下のコマンドで Job 実行時のコマンドが
デフォルトパラメタを上書きできていたことが確認できます。

コマンド
aws batch describe-jobs \
  --jobs $( candidate=$( aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status SUCCEEDED \
    | jq '.jobSummaryList' \
    | jq -r "map(select(.jobName==\"${JOB_32}\"))[].jobId" ); \
    echo ${candidate:-none} \
) | jq '.jobs[].container.command'
結果
[
  "echo",
  "Hello, Tokyo!"
]

3.3. 依存関係のある Job の投入

とある Job に、別の Job が依存するバッチを想定します。
2 つ Job を投げるので、名前も 2 つ用意します。

コマンド
JOB_331="job-`date +%s`"
sleep 2
JOB_332="job-`date +%s`"

JOB_331 は 1 時間かかる Job、
JOB_332 は JOB_331 の終了をもって起動する Job として投入。

JOB_331
JOB_331_RESULT=$( aws batch submit-job \
  --job-name ${JOB_331} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN} \
  --container-overrides \
    '{"command": ["sleep", "Ref::WaitTime"], "memory": 100}' \
  --parameters WaitTime=3600 \
) && echo ${JOB_331_RESULT}
JOB_332
aws batch submit-job \
  --job-name ${JOB_332} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN} \
  --container-overrides '{"memory": 100}' \
  --depends-on jobId=$( echo ${JOB_331_RESULT} \
    | jq -r '.jobId')

すると JOB_332 は JOB_331 の終了を待つため、
PENDING ステータスに入ります。

コマンド
aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status PENDING \
    | jq '.jobSummaryList' \
    | jq -r "map(select(.jobName==\"${JOB_332}\"))"
結果(例)
[
  {
    "jobName": "job-1484079158",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
  }
]

3.4. Job の停止

依存関係のある Job の場合、JOB_331 が正常終了したら
JOB_332 も実行されることは容易に想像がつきますが
JOB_331 が失敗ステータスになったらどうなるでしょうか?

terminate-job を使えば
STARTINGRUNNING の Job も FAILED にできます

コマンド
aws batch terminate-job \
  --job-id $( aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status RUNNING \
    | jq '.jobSummaryList' \
    | jq -r "map(select(.jobName==\"${JOB_331}\"))[].jobId" \
  ) \
  --reason 'Forced to fail.'
結果
返り値なし

しばらくすると、依存していた JOB_332 も JOB_331 同様
FAILED になっていることが確認できます。

コマンド
aws batch describe-jobs \
  --jobs $( candidate=$( aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status FAILED \
    | jq '.jobSummaryList' \
    | jq -r "map(select(.jobName==\"${JOB_332}\"))[].jobId" ); \
    echo ${candidate:-none} \
) | jq .
結果(例)
{
    "jobs": [
        {
            "status": "FAILED", 
            "container": {
                "mountPoints": [], 
                "image": "busybox", 
                "environment": [], 
                "vcpus": 1, 
                "command": [
                    "echo", 
                    "Hello, AWS Batch :)"
                ], 
                "volumes": [], 
                "memory": 100, 
                "ulimits": []
            }, 
            "parameters": {
                "Target": "Hello, AWS Batch :)"
            }, 
            "jobDefinition": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:1", 
            "statusReason": "Dependent Job failed", 
            "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 
            "jobQueue": "arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-queue/aws-batch-job-queue-xxxxxxxxxx", 
            "dependsOn": [
                {
                    "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                }
            ], 
            "jobName": "job-xxxxxxxxxx", 
            "createdAt": 1484080086870, 
            "stoppedAt": 1484080595978
        }
    ]
}

落ちた理由は Dependent Job failed になっていますね

4. ログを確認する

4.1. CloudWatch Logs

ジョブでの標準出力は、すべて CloudWatch Logs に保存されています。

4.2 特定 JOB のログストリーム特定

JOB_32 のログを見てみようと思います。

コマンド
JOB_32_LOG=$( aws logs describe-log-streams \
  --log-group-name /aws/batch/job \
  | jq '.logStreams' \
  | jq "map(select(.logStreamName | index(\"${JOB_32}\")))" \
) && echo ${JOB_32_LOG}
結果
[
  {
    "firstEventTimestamp": 1484080086870,
    "lastEventTimestamp": 1484080086870,
    "creationTime": 1484080086870,
    "uploadSequenceToken": "xxxxxxxxxx",
    "logStreamName": "job-1484078516/123abcde-4567-89fg-hijk-0123lmnopqrs/12345678-abcd-efgh-9012-3456ijklmnop",
    "lastIngestionTime": 1484080595978,
    "arn": "arn:aws:logs:us-east-1:xxxxxxxxxxxx:log-group:/aws/batch/job:log-stream:job-1484078516/123abcde-4567-89fg-hijk-0123lmnopqrs/12345678-abcd-efgh-9012-3456ijklmnop",
    "storedBytes": 0
  }
]

4.3 特定 JOB のログ参照

JOB_32 では以下のような内容が出力されたことがわかります。

コマンド
aws logs get-log-events \
  --log-group-name /aws/batch/job \
  --log-stream-name $( echo ${JOB_32_LOG} \
    | jq -r '.[].logStreamName' ) \
  | jq '.events | map({time: (.timestamp / 1000 | strftime("%Y/%m/%d %H:%M:%S")), message: .message})'
結果
[
  {
    "time": "2017/01/17 14:10:27",
    "message": "Hello, Tokyo!"
  }
]

完了

ジョブの作成と実行は以上です。
AWS Batch #3 より実践的な使い方