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

# 前提条件

## EC2への権限

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

# 0. 準備

## 0.1. 環境構築

[#1 環境構築 Managed 編](http://qiita.com/pottava/items/435c65b1fa72cb643f6e) または [Unmanaged 編](http://qiita.com/pottava/items/2565418ed919224b8d29) が終わっていること

## 0.2. 変数の確認

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

```sh:コマンド
cat << ETX

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

ETX
```

```sh:結果（例）

    COMPUTE_ENV_NAME: aws-batch-managed-xxxxxxxxxx
    JOB_QUEUE_NAME:   aws-batch-job-queue-xxxxxxxxxx

```

## 0.3. AWS CLIのバージョン

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

* AWS CLI 1.11.36

```sh:コマンド
aws --version
```

```sh:結果（例）
 aws-cli/1.11.36 Python/2.7.5 Darwin/13.4.0 botocore/1.4.93
```

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

```sh:コマンド
sudo -H pip install -U awscli
```

## 0.4. AWS アカウントの属性

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

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

```sh:結果
 VPC
```

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

# 1. ジョブの定義

## 1.1. ジョブ定義名の指定

定義名の指定

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

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

```sh:コマンド
aws batch describe-job-definitions \
  --job-definition-name ${JOB_DEFINITION_NAME}
```

```sh:結果
{
    "jobDefinitions": []
}
```

## 1.2. ジョブ定義の作成

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

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

```sh:コマンド
CONRAINER_PROPS_FILE="aws_batch_container_props.json"
```

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

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

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

```sh:コマンド
jsonlint -q ${CONRAINER_PROPS_FILE}
```

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

```sh:コマンド
aws batch register-job-definition \
  --job-definition-name ${JOB_DEFINITION_NAME} \
  --type container \
  --container-properties file://${CONRAINER_PROPS_FILE} \
  --parameters Target="Hello\, AWS Batch :)"
```

```sh:結果（例）
{
    "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. ジョブ名・ジョブ定義の取得

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

```sh:コマンド
JOB_21="job-`date +%s`"
```

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

```sh:コマンド
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}
```

```sh:結果（例）
 arn:aws:batch:us-east-1:xxxxxxxxxxxx:job-definition/aws-batch-job-def-xxxxxxxxxx:1
```

## 2.2. ジョブの投入

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

```sh:コマンド
aws batch submit-job \
  --job-name ${JOB_21} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN}
```

```sh:結果（例）
{
    "jobName": "job-1484056465",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}
```

## 2.3. ジョブの状況を確認

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

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

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

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

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

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

```sh:コマンド
aws batch list-jobs \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-status RUNNABLE
```

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

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

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

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

```sh:コマンド
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.'
```

```sh:結果
返り値なし
```

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

```sh:コマンド
aws batch describe-jobs --jobs ${JOB_21_ID}
```

```sh:結果（例）
{
    "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 に名前をつけ

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

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

```sh:コマンド
aws batch submit-job \
  --job-name ${JOB_31} \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-definition ${JOB_DEFINITION_ARN} \
  --container-overrides '{"memory": 100}'
```

```sh:結果（例）
{
    "jobName": "job-1484076698",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}
```

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

```sh:コマンド
aws batch list-jobs \
  --job-queue ${JOB_QUEUE_NAME} \
  --job-status SUCCEEDED \
  | jq '.jobSummaryList' \
  | jq -r "map(select(.jobName==\"${JOB_31}\"))[]"
```

```sh:結果（例）
{
    "jobName": "job-1484076698",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}
```

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

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

Job に名前をつけ

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

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

```sh:コマンド
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!'
```

```sh:結果（例）
{
    "jobName": "job-1484078516",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
}
```

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

```sh:コマンド
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'
```

```sh:結果
[
  "echo",
  "Hello, Tokyo!"
]
```

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

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

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

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

```sh: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}
```

```sh: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` ステータスに入ります。

```sh:コマンド
aws batch list-jobs \
    --job-queue ${JOB_QUEUE_NAME} \
    --job-status PENDING \
    | jq '.jobSummaryList' \
    | jq -r "map(select(.jobName==\"${JOB_332}\"))"
```

```sh:結果（例）
[
  {
    "jobName": "job-1484079158",
    "jobId": "123abcde-4567-89fg-hijk-0123lmnopqrs"
  }
]
```

## 3.4. Job の停止

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

`terminate-job` を使えば
`STARTING` や `RUNNING` の Job も `FAILED` にできます

```sh:コマンド
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.'
```

```sh:結果
返り値なし
```

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

```sh:コマンド
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 .
```

```sh:結果（例）
{
    "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 のログを見てみようと思います。

```sh:コマンド
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}
```

```sh:結果
[
  {
    "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 では以下のような内容が出力されたことがわかります。

```sh:コマンド
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})'
```

```sh:結果
[
  {
    "time": "2017/01/17 14:10:27",
    "message": "Hello, Tokyo!"
  }
]
```


# 完了

ジョブの作成と実行は以上です。
[AWS Batch #3 より実践的な使い方](http://qiita.com/pottava/items/a1d316e5a622b705efba)へ

　

[^1]: 各コンテナインスタンスの厳密な残リソースは ECS の API / コンソールから確認できます。