AWS CLI で AWS Batch にジョブを送信する (第２回)

AWS CLI を使って AWS Batch を実行するまでを解説します。
このドキュメントを読むためには AWS EC2、AWS S3 および docker に対する基本的な知識が必要です。
なお、ジョブのスケジュール設定については解説しません。

## 目次

全３回です。

1. [はじめに](https://qiita.com/aokad/items/f28ffa5ae984678817f6)
2. パラメータを使ってジョブを送信する          ← イマココ
3. [環境変数を使ってジョブを送信する](https://qiita.com/aokad/items/aed8034fb087a6bf99c3)

AWS CLI 公式リファレンスはこちらです。[aws batch](https://docs.aws.amazon.com/cli/latest/reference/batch/index.html)

## 1. AWS CLI の準備

手順はこちらを参照してください。 [AWS CLI のインストールと設定](https://qiita.com/aokad/items/88fe9312eea1c12ba8a2)

今回は JSON を扱うために `jq` コマンドも使用しますのでインストールしてください。
インストール手順はこちらです。
https://stedolan.github.io/jq/download/

## 2. docker イメージを作成する（オプション）

本記事ではこちらで用意した docker イメージ [aokad/aws-wordcount](https://hub.docker.com/r/aokad/aws-wordcount/) を使用しますので、この手順は不要ですが、独自イメージを使用する場合は [上記イメージの DockerFile](https://raw.githubusercontent.com/aokad/qiita_aws_batch/master/Dockerfile) を参考にしてください。

本記事においてイメージに必要なもの（最終的にはなくてもよいです）

 - AWS CLI ... アカウントの設定をしてはいけません（タスク実行時に自動で一時的なアカウントが設定されます）
 - docker run の時に実行するスクリプトファイル

AWS ECR を使用する場合は [AWS CLI で Amazon ECR に docker イメージを push する](https://qiita.com/aokad/items/17a06c2384041bd60d16) を参照してください。

## 3. AWS 情報を環境変数にセット

この記事ではコピペ実行できるようにアカウントの違いを環境変数にセットしておきます。

 - AWS_ACCOUNTID ..... 自分のアカウントID
 - AWS_REGION ..... `aws configure` で設定したリージョン
 - SUBNET1,2,3 ..... 「AWSコンソール」→「VPC」→「サブネット」からデフォルトVPCのサブネットを確認してください
 - SECURITYGROUPID ..... 「AWSコンソール」→「VPCダッシュボード」→「セキュリティグループ」からデフォルトのセキュリティグループをのIDを確認してください。新しく作成する場合は22番ポートを開けてください。
 - S3_BUCKET ... 作業用の S3 バケットです。任意の名称で作成してください。

設定例

```Bash
export AWS_ACCOUNTID=123456789012
export AWS_REGION=ap-northeast-1
export SUBNET1=subnet-123a456b
export SUBNET2=subnet-789c012d
export SUBNET3=subnet-345e678f
export SECURITYGROUPID=sg-11335577
export S3_BUCKET=mybucket
```

## 4. コンピュート環境を作成

ジョブを実行するコンピュート環境 (起動する EC2 インスタンス) を定義します。

```Bash
SERVICEROLE="arn:aws:iam::${AWS_ACCOUNTID}:role/service-role/AWSBatchServiceRole"
INSTANCEROLE="arn:aws:iam::${AWS_ACCOUNTID}:instance-profile/ecsInstanceRole"

cat << EOF > compute-environment.spec.json
{
    "computeEnvironmentName": "mybatch-compute-environment",
    "type": "MANAGED",
    "state": "ENABLED",
    "computeResources": {
        "type": "EC2",
        "minvCpus": 0,
        "maxvCpus": 256,
        "desiredvCpus": 0,
        "instanceTypes": ["optimal"],
        "subnets": ["${SUBNET1}", "${SUBNET2}", "${SUBNET3}"],
        "securityGroupIds": ["${SECURITYGROUPID}"],
        "instanceRole": "${INSTANCEROLE}"
    },
    "serviceRole": "${SERVICEROLE}"
}
EOF

aws batch create-compute-environment --cli-input-json file://compute-environment.spec.json
```

Web ブラウザで「AWSコンソール」→「Batch」→「コンピューティング環境」を確認してください。
コンピュート環境が新しく作成されていることが確認できます。

<FONT SIZE=1>

備考：

1. 今回はマネージド型としています。アンマネージド型は [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/compute_environments.html) を参照してください。
2. computeResources:minvCpus: は 0 にすることをお勧めします。minvCpus が 0 の場合、ジョブがないときは EC2 インスタンスが削除され、ジョブが発生したら再作成されます。minvCpus が 1 以上の場合、ジョブがないときでも EC2 インスタンスを起動してしまうので、コスト的にもったいないからです。
3. computeResources:instanceTypes: ["optimal"] とした場合、起動するインスタンスタイプはAWS Batch で判断されます。指定する場合は C, M, R インスタンスファミリーを使用できます。複数記入する場合は ["r4.2xlarge","m4.2xlarge"] とカンマ区切りで指定します。ファミリー全体を指定する場合は ["m4"] と書くこともできます。
4. computeResources:type を "SPOT" にすると、スポットインスタンスを使用できます。このとき、computeResources:bidPercentage として、上限入札価格を設定し、computeResources:spotIamFleetRole として、スポットフリートロールを設定します。詳しくは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/spot_fleet_IAM_role.html)を参照してください。
</FONT>

## 5. ジョブキューの作成

ジョブキューを作成します。

```Bash
COMPUTE_ENV_ARN="arn:aws:batch:${AWS_REGION}:${AWS_ACCOUNTID}:compute-environment/mybatch-compute-environment"

aws batch create-job-queue \
  --job-queue-name mybatch-queue \
  --priority 1 \
  --compute-environment-order order=1,computeEnvironment=${COMPUTE_ENV_ARN} \
> create-job-queue.log
```

ジョブキューを作成した後に返ってくる "jobDefinitionArn" を変数にセットします。ジョブ送信に使用します。
※ ARN とは、Amazon リソースネームの略で、AWS リソースを一意に識別するためのものです。

```Bash
JOB_QUEUE_ARN=$(jq -r '.jobQueueArn' create-job-queue.log)
```

```create-job-queue.log
{
    "jobQueueArn": "arn:aws:batch:{region}:{account_id}:job-queue/mybatch-queue",
    "jobQueueName": "mybatch-queue"
}
```

「AWSコンソール」→「Batch」→「ジョブキュー」に新しく作成されています。

## 6. ジョブ定義

ジョブ定義を作成します。

```Bash
ECSTASKROLE="arn:aws:iam::${AWS_ACCOUNTID}:role/AmazonECSTaskS3FullAccess"

cat << EOF > job-definition.spec.1.json
{
  "image": "aokad/aws-wordcount:0.0.1",
  "command": ["ash", "run.sh", "Ref::Input", "Ref::Output"],
  "vcpus": 1,
  "memory": 500,
  "jobRoleArn": "${ECSTASKROLE}"
}
EOF

aws batch register-job-definition \
  --job-definition-name mybatch-job-def1 \
  --type container \
  --container-properties file://job-definition.spec.1.json \
  --parameters Input="",Output="" \
> register-job-definition.log

```

register-job-definition コマンドで返ってくる "jobDefinitionArn" を変数にセットします。ジョブ送信に使用します。ちなみに、ジョブ定義にはリビジョン番号があり、上記 register-job-definition を実行するたびにインクリメントされます。

```Bash
JOB_DEFINITION_ARN=$(jq -r '.jobDefinitionArn' register-job-definition.log)
```

```register-job-definition.log
{
    "jobDefinitionArn": "arn:aws:batch:{region}:{account_id}:job-definition/mybatch-job-def1:1", 
    "jobDefinitionName": "mybatch-job-def1",
    "revision": 1
}
```

Web ブラウザで「AWSコンソール」→「Batch」→「ジョブ定義」を確認してください。
ジョブ定義が新しく作成されていることが確認できます。

<FONT SIZE=1>

備考：
1. parameters Input, Output が今回のジョブ定義におけるパラメータ（変数）です。ジョブ送信の時に設定しますので、ここでは空です。変数は "command" で "Ref::**" の形式で使用することができます。詳しくは[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/job_definition_parameters.html#parameters)を参照してください。
</FONT>

## 7. ジョブの送信

ジョブを送信する前に、入出力用の S3 バケットを作成し、適当なデータを置いておきます。

```Bash
cat << EOF > Humpty.txt
Humpty Dumpty sat on a wall,
Humpty Dumpty had a great fall.
All the king's horses and all the king's men
Couldn't put Humpty together again.
EOF

aws s3 cp Humpty.txt s3://${S3_BUCKET}/
```

いよいよジョブを送信してみます。

```Bash
aws batch submit-job \
    --job-name "mybatch-job-try1" \
    --job-queue "${JOB_QUEUE_ARN}" \
    --job-definition "${JOB_DEFINITION_ARN}" \
    --parameters Input="s3://${S3_BUCKET}/Humpty.txt",Output="s3://${S3_BUCKET}/Humpty.count.param.txt"
```

「AWSコンソール」→「Batch」→「ダッシュボード」→「ジョブキュー」を見てください。ジョブが新しく作成されています。

![aws-batch-1.PNG](https://qiita-image-store.s3.amazonaws.com/0/226829/c902bcbf-3453-af4c-cbed-52bba3671304.png)
新しくジョブを送信したい場合は、この「7. ジョブの送信」だけを繰り返せばよいです。

EC2 インスタンスが終了し、s3://${S3_BUCKET}/Humpty.count.param.txt が出力されていれば成功です。
なお、スクリプトの実行ログは「AWSコンソール」→「ClowdWatch」→「ログ」→ロググループ「/aws/batch/job」から確認できます。

![aws-batch-2.png](https://qiita-image-store.s3.amazonaws.com/0/226829/1cffbfcf-30fd-74cf-9c92-e65b58d7aecb.png)

## 8. 片付け

AWS Batch はコンピュート環境やジョブ定義には料金がかからず実際に立ち上げたインスタンスとデータ転送量、AWS ECR イメージサイズ、ClowdWatch（ログ）に課金されます。

１）少なくとも EC2 インスタンスは削除しましょう。インスタンスが残っていないか AWS EC2 で確認し、残っていれば terminate しておけばひとまず安心です。

２）ジョブ定義、コンピュート環境、キューを削除します。

AWSコンソールから削除してもよいですが、AWS CLI でのコマンド例も記載しておきます。

```Bash
# ジョブ定義を削除
# リビジョンが複数ある場合はそれぞれ削除してください
aws batch deregister-job-definition --job-definition mybatch-job-def1:1

# キューを削除
aws batch update-job-queue --job-queue mybatch-queue --state DISABLED
aws batch delete-job-queue --job-queue mybatch-queue

# コンピュート環境を削除
# キューを削除したのち、直ぐにコマンドを実行すると内部で状態が反映されておらずエラーになることがあります
# エラーになった場合は少し時間を置いてから実行してみてください
aws batch update-compute-environment --compute-environment mybatch-compute-environment --state DISABLED
aws batch delete-compute-environment --compute-environment mybatch-compute-environment
```

３）AWS Batch が出力した log を削除します

「AWSコンソール」→「ClowdWatch」→「ログ」→該当するロググループをクリック→ログストリームを選択して、「ログストリームの削除」
「AWSコンソール」→「ClowdWatch」→「ログ」→該当するログを選択→ログストリームを選択して、「アクション」→「ロググループの削除」

AWS CLI で実行する場合は以下です。

```Bash
# ログストリームを削除
# 複数ある場合はそれぞれ削除してください
aws logs delete-log-stream --log-group-name /aws/batch/job --log-stream-name mybatch-job-def1/default/{36桁のコード}

# ロググループを削除
aws logs delete-log-group --log-group-name /aws/batch/job
```

今回はジョブの違いをパラメータで吸収しました。
次回、[環境変数を使ってジョブを送信する](https://qiita.com/aokad/items/aed8034fb087a6bf99c3)では、環境変数で実現する方法を記載します。