AWS Batch スケジューリングポリシーを学べるワークショップ Fair-share scheduling on AWS Batch をやってみた

これで AWS Batch のフェアシェアスケジューリングも Batchり

コンバンハ、千葉(幸)です。

AWS Batch について学べるワークショップはないかなと AWS Workshops を調べていたところ、Fair-share scheduling on AWS Batchというものを見つけました。

一通り手を動かしてみたのでその内容をご紹介します。ワークショップを実行する際に横に並べて見ていただければとおもいます。

このワークショップで学べること

2021年11月に登場した AWS Batch のフェアシェアスケジューリングについて学べます。

上記のブログでは AWS Batch におけるキューイングの考え方が gif で紹介されているので、興味がある方は見てみるとよいでしょう。

フェアシェアスケジューリングのイメージは以下です。

hpcblog-72-f2(画像引用元は上記のブログ)

キューには緑のジョブが4つ、その後に青のジョブが4つ入れられています。従来の FIFO 方式では先に入れられたジョブから処理されていましたが、上記の例では緑と青のジョブが均等に処理されていることが分かります。もちろん重み付けを変えることによって 50:50 以外の割合にすることも可能です。

このワークショップでは一通り AWS Batch の環境を構築した上でジョブを送信し、以下の動作を確認できます。

  • FIFO スケジューリング
  • フェアシェアスケジューリング
    • 共有識別子を指定した場合
    • shareDistribution を指定した場合
    • 優先度を指定した場合
    • コンピュート予約を指定した場合
    • 共有減退秒数を指定した場合

フェアシェアスケジューリングで使用するスケジューリングポリシーのパラメータは以下から確認できます。

ワークショップではステータスの確認やリソース削除など、AWS CLI で使えるテクニックも学べてお得に感じました。

お値段に注意

ワークショップで使用される AWS Batch のコンピューティング環境はスペック高めのものが指定されています。3 時間程度で終わらせれば 5~10 USD 程度でおさまると思いますが、お掃除を忘れるとなかなか高めの料金が発生しそうです。ご注意ください。

処理自体には高スペックは必要ないはずなので、自分でスペックを読み替えてやってみるとお安く済ませられるかと思います。

事前準備

ワークショップを行う上で必要となる事項がいくつかあります。

Cloud9 環境の作成

AWS CLI の実行元となる Cloud9 環境を作成しておきます。とは言えそれが必須というわけではなく、ローカルの端末から実行しても問題ありません。AWS CloudShell でも実施可能かと思います。

今回わたしは以下の条件で Cloud9 環境を作成しました。

項目
環境タイプ EC2(直接接続)
インスタンスタイプ t3.small
プラットフォーム Amazon Linux2

デフォルトでは jq が入っていないためインストールします。

$ sudo yum install -y jq

デフォルトの AWS CLI のバージョンは以下でした。

$ aws --version
aws-cli/1.19.112 Python/2.7.18 Linux/4.14.262-200.489.amzn2.x86_64 botocore/1.20.112

このバージョンでは後続の手順で必要となるコマンドが実行できなかったため、アップグレードしました。

$ aws --version
aws-cli/1.22.68 Python/3.7.10 Linux/4.14.262-200.489.amzn2.x86_64 botocore/1.24.13

デフォルト VPC の作成

コンピューティング環境をデフォルト VPC に作成する前提の手順とっているため、削除済みの場合は改めて作成しておきましょう。

Default_VPC_create

コマンドをカスタマイズできる方は他の VPC を指定しても問題ありません。

各種 IAM ロールの作成

以下のロールが必要となります。あらかじめ作成しておきましょう。

ecsInstanceRoleは IAM ロールの作成ウィザードからユースケースとして以下を選択することで作成します。

IAM_Management_Console_ECS_Instance_Role

IAMポリシーにはAmazonEC2ContainerServiceforEC2Roleがアタッチされ、信頼ポリシーは以下となります。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

名称は自分で指定が必要なので、ecsInstanceRoleを指定しましょう。

1. AWS Batch リソースの作成

以下を作成・実行していきます。

  • コンピューティング環境
  • ジョブキュー
  • ジョブ定義
  • ジョブ送信

ワークショップではマネジメントコンソール/AWS CLI 双方での手順が載っていますが、今回は AWS CLI を用います。

a. コンピューティング環境の作成

環境変数をセットします。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity |jq -r .Account)
DEFAULT_VPC_ID=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true | jq -r '.Vpcs[].VpcId')
DEFAULT_SUBNETS="$(aws ec2 describe-subnets --filters Name=default-for-az,Values=true \
     | jq -r '.Subnets[].SubnetId' |xargs \
     | sed -e 's/ /","/g')"
BATCH_SUBNETS=$(echo "[\"$DEFAULT_SUBNETS\"]")
BATCH_SG=$(aws ec2 describe-security-groups --filters Name=group-name,Values=default Name=vpc-id,Values=$DEFAULT_VPC_ID | jq -r '.SecurityGroups[].GroupId')
SVC_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/aws-service-role/batch.amazonaws.com/AWSServiceRoleForBatch"

ワークショップの手順に載っている以下は使用されないため、削って問題ありません。

BATCH_ECS_ROLE=ecsInstanceRole
TASK_EXECUTION_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/TaskExecutionRole"

後続の手順でオプションとして渡すxs-ce.jsonを作成します。vCPU が 128 で指定されており、インスタンスタイプもなかなかのサイズが指定されています。

$ cat > xs-ce.json << EOF
> {
>   "computeEnvironmentName": "xs-ce",
>   "type": "MANAGED",
>   "state": "ENABLED",
>   "computeResources": {
>     "type": "SPOT",
>     "allocationStrategy": "SPOT_CAPACITY_OPTIMIZED",
>     "minvCpus": 128,
>     "maxvCpus": 128,
>     "desiredvCpus": 128,
>     "instanceTypes": [
>       "m6i.8xlarge","c5.9xlarge","c5a.8xlarge","c4.8xlarge","m5.8xlarge","m5a.8xlarge","m5n.8xlarge","m4.10xlarge"
>     ],
>     "subnets": ${BATCH_SUBNETS},
>     "securityGroupIds": [
>       "${BATCH_SG}"
>     ],
>     "instanceRole": "ecsInstanceRole",
>     "tags": {
>       "Name": "Batch Instance - XS Scheduling",
>       "Workshop": "FairShare"
>     }
>   },
>   "serviceRole": "${SVC_ROLE_ARN}"
> }
> EOF
$ jq . xs-ce.json
{
  "computeEnvironmentName": "xs-ce",
  "type": "MANAGED",
  "state": "ENABLED",
  "computeResources": {
    "type": "SPOT",
    "allocationStrategy": "SPOT_CAPACITY_OPTIMIZED",
    "minvCpus": 128,
    "maxvCpus": 128,
    "desiredvCpus": 128,
    "instanceTypes": [
      "m6i.8xlarge",
      "c5.9xlarge",
      "c5a.8xlarge",
      "c4.8xlarge",
      "m5.8xlarge",
      "m5a.8xlarge",
      "m5n.8xlarge",
      "m4.10xlarge"
    ],
    "subnets": [
      "subnet-0726628d2fcd30fd8",
      "subnet-0d43b308c45e508d8",
      "subnet-0dfdc2a8a75f41548"
    ],
    "securityGroupIds": [
      "sg-0b4aa795db56df73f"
    ],
    "instanceRole": "ecsInstanceRole",
    "tags": {
      "Name": "Batch Instance - XS Scheduling",
      "Workshop": "FairShare"
    }
  },
  "serviceRole": "arn:aws:iam::012345678910:role/aws-service-role/batch.amazonaws.com/AWSServiceRoleForBatch"
}

上記のファイルを指定しコマンドを実行します。

$ aws batch create-compute-environment --cli-input-json file://xs-ce.json |jq .
{
  "computeEnvironmentName": "xs-ce",
  "computeEnvironmentArn": "arn:aws:batch:ap-northeast-1:012345678910:compute-environment/xs-ce"
}

もし ecsInstanceRole が足りないと

ecsInstanceRole が存在しない状態でコマンドを実行した場合、このように環境のステータスがINVALIDになります。

AWS_Batch_INVALID

後からecsInstanceRoleを作成すると、しばらくしてVALIDに遷移します。(ちゃんと確認しませんでしたが数分程度だったかと思います。)

b. ジョブキューの作成

コンピューティング環境がVALIDであることを確認したら、その環境を指定しジョブキューを作成します。

$ cat > xs-jq.json << EOF
> {
>   "jobQueueName": "xs-jq",
>   "state": "ENABLED",
>   "priority": 1,
>   "computeEnvironmentOrder": [
>     {
>       "order": 1,
>       "computeEnvironment": "xs-ce"
>     }
>   ]
> }
> EOF
$ aws batch create-job-queue --cli-input-json file://xs-jq.json |jq .
{
  "jobQueueArn": "arn:aws:batch:ap-northeast-1:012345678910:job-queue/xs-jq",
  "jobQueueName": "xs-jq"
}

c. ジョブ定義の作成

ジョブの雛型となるジョブ定義を作成します。

$ JDEF=$(aws batch register-job-definition \
>     --job-definition-name test-job --type container \
>     --container-properties '{"image":"busybox","vcpus":32,"memory":128,"command":["sleep","60"]}' \
>     | jq -r '.jobDefinitionName+":"+(.revision|tostring)')
$ echo $JDEF
test-job:1

vCPU が 32で、先ほど作成したコンピューティング環境の 25 % を占めます。ジョブの処理は単に 60 秒 sleepする、というものです。

d. ジョブの送信

作成したジョブ定義、ジョブキューを指定してジョブを送信します。

$ aws batch submit-job --job-definition ${JDEF:-test-job1} \
>         --job-name "test-job1" \
>         --job-queue xs-jq  |jq '.jobName'
"test-job1"

ここでジョブの実行ステータスを見るのに便利な関数が紹介されています。

$ function listBatchQueue() {
>     echo "### Queue: ${1}"
>     for STATUS in RUNNABLE RUNNING SUCCEEDED FAILED;do 
>         if [[ $STATUS == "RUNNABLE" ]];then
>             aws batch list-jobs --job-queue=${1} --job-status=${STATUS} \
>                 |jq --arg s "$STATUS" '.jobSummaryList[]| "["+$s+"] "+.jobName'
>         else
>             aws batch list-jobs --job-queue=${1} --job-status=${STATUS} \
>                 |jq --arg s "$STATUS" '.jobSummaryList |sort_by(.startedAt)[] | "["+$s+"] "+.jobName+" "+(.startedAt|tostring)'
>         fi 
>     done
> }

この関数の引数にキュー名を指定して実行すると、キュー内のジョブのステータスが確認できます。

$ listBatchQueue xs-jq
### Queue: xs-jq
"[SUCCEEDED] test-job1 1646548303512"

一番右の列はジョブの開始時刻を表す UNIX タイムスタンプです。下3桁を削ってあげると変換ができました。

% date -r  1646548303512
54147年 1月15日 日曜日 09時45分12秒 JST
% date -r  1646548303
2022年 3月 6日 日曜日 15時31分43秒 JST

この関数はこの後の手順で何度も出てくるので覚えておきましょう。

2. FIFO スケジューリングの確認

今回のワークショップの本題はフェアシェアスケジューリングですが、その前段として通常のスケジューリングの挙動を確認します。

キューイングの挙動のイメージが gif で紹介されています。(ワークショップ手順ページから引用します。)

シングルキューの場合は以下。キューに入れられたジョブが順番に処理されています。

fifo_single_queue

マルチキューの場合は以下。ここでは緑のキュー1の方が優先度が高く設定されており、そちらが先に処理されています。とは言えキュー1のジョブ 1~4 がすべて先に処理される、という挙動ではありません。

fifo_multi_queue

ちなみにここでの FIFO は SQS のように厳密な FIFO にはなりません。あくまでその傾向になる、という感覚で捉えておくとよいでしょう。

a. ジョブ定義の作成

先ほどの手順で既に作成済みであれば、改めての作成は不要です。

ジョブ定義があることを確認しましょう。

$ aws batch describe-job-definitions --status=ACTIVE |jq -r '.jobDefinitions[] |.jobDefinitionName+":"+(.revision|tostring)' | grep test
test-job:1

b. ジョブの送信

blue-V1-という接頭辞を持つジョブを 10 個、blue-V2-接頭辞を持つジョブを 10 個送信します。それらのジョブの処理される順番を確認します。

まずは blue-V1 のジョブです。

$ for x in {1..10}; do 
>     aws batch submit-job --job-name "blue-V1-${x}" --job-queue xs-jq  --job-definition ${JDEF:-test-job} |jq -r '.jobName'
> done
blue-V1-1
blue-V1-2
blue-V1-3
blue-V1-4
blue-V1-5
blue-V1-6
blue-V1-7
blue-V1-8
blue-V1-9
blue-V1-10

続いて blue-V2 のジョブも同様に送信します。

$ for x in {1..10}; do
>     aws batch submit-job --job-name "blue-V2-${x}" --job-queue xs-jq  --job-definition ${JDEF:-test-job}|jq -r '.jobName'
> done
blue-V2-1
blue-V2-2
blue-V2-3
blue-V2-4
blue-V2-5
blue-V2-6
blue-V2-7
blue-V2-8
blue-V2-9
blue-V2-10

おさらいとなりますが、今回のコンピューティング環境/ジョブ定義の組み合わせでは一度に 4 つのジョブを処理できます。

先ほどのlistBatchQueue関数を用いてジョブのステータスを確認します。上から以下の順に並びます。

  • RUNNABLE:実行待ち
  • RUNNING:実行中
  • SUCCEEDED:成功
    • 開始時刻が古い順にソート
$ listBatchQueue xs-jq |grep "blue-V"
"[RUNNABLE] blue-V1-8"
"[RUNNABLE] blue-V1-10"
"[RUNNABLE] blue-V2-1"
"[RUNNABLE] blue-V2-2"
"[RUNNABLE] blue-V2-3"
"[RUNNABLE] blue-V2-4"
"[RUNNABLE] blue-V2-5"
"[RUNNABLE] blue-V2-6"
"[RUNNABLE] blue-V2-7"
"[RUNNABLE] blue-V2-8"
"[RUNNABLE] blue-V2-9"
"[RUNNABLE] blue-V2-10"
"[RUNNING] blue-V1-5 1646551701350"
"[SUCCEEDED] blue-V1-3 1646551604341"
"[SUCCEEDED] blue-V1-2 1646551604541"
"[SUCCEEDED] blue-V1-1 1646551604825"
"[SUCCEEDED] blue-V1-6 1646551608812"
"[SUCCEEDED] blue-V1-4 1646551670487"
"[SUCCEEDED] blue-V1-9 1646551670962"
"[SUCCEEDED] blue-V1-7 1646551671081"

↑先に実行した V1 のジョブが優先されて処理されていることが分かります。V1 の中での順番はバラバラです。

一通り成功した後の結果は以下です。一部 V2 のジョブの方が先に処理されていました。

$ listBatchQueue xs-jq |grep "blue-V"
"[SUCCEEDED] blue-V1-3 1646551604341"
"[SUCCEEDED] blue-V1-2 1646551604541"
"[SUCCEEDED] blue-V1-1 1646551604825"
"[SUCCEEDED] blue-V1-6 1646551608812"
"[SUCCEEDED] blue-V1-4 1646551670487"
"[SUCCEEDED] blue-V1-9 1646551670962"
"[SUCCEEDED] blue-V1-7 1646551671081"
"[SUCCEEDED] blue-V1-5 1646551701350"
"[SUCCEEDED] blue-V1-10 1646551761563"
"[SUCCEEDED] blue-V2-1 1646551761824"
"[SUCCEEDED] blue-V1-8 1646551762711"
"[SUCCEEDED] blue-V2-2 1646551793927"
"[SUCCEEDED] blue-V2-3 1646551854287"
"[SUCCEEDED] blue-V2-6 1646551854553"
"[SUCCEEDED] blue-V2-4 1646551855176"
"[SUCCEEDED] blue-V2-5 1646551885103"
"[SUCCEEDED] blue-V2-8 1646551944936"
"[SUCCEEDED] blue-V2-7 1646551945140"
"[SUCCEEDED] blue-V2-9 1646551946036"
"[SUCCEEDED] blue-V2-10 1646551975468"

ワークショップでは V1 がすべて終了してから V2 がスケジュールされる、という挙動によって FIFO 方式の確認を意図していたようですが、その通りになりませんでした。

V2 の実行をあまり間隔を空けずに行ったからかもしれません。

念の為マネジメントコンソールからも開始時刻を確認しましたが、関数から得られるものと同一でした。

AWS_Batch_Job

完全な FIFO ではない、という話は以下でも取り上げれていますのであわせてご覧ください。

3. Fiar Share スケジューリング

続いて本題のフェアシェアスケジューリングです。

いくつかの種類のスケジューリングポリシーを作成して挙動を確認していきます。

a. スケジューリングポリシーでの識別子の使用

まずはschedulingPolicyという名称のスケジューリングポリシーを作成します。

$ cat > schedulingPolicy.json << EOF
> {
>    "fairsharePolicy": { 
>       "computeReservation": 0,
>       "shareDecaySeconds": 0
>    },
>    "name": "schedulingPolicy"
> }
> EOF
$ SP_ARN=$(aws batch create-scheduling-policy --cli-input-json file://schedulingPolicy.json |jq -r .arn)
$ echo $SP_ARN
arn:aws:batch:ap-northeast-1:012345678910:scheduling-policy/schedulingPolicy

上記のスケジューリングポリシーを設定した新たなジョブキューを作成します。使用するコンピューティング環境は同一のものです。

$ aws batch create-job-queue \
>     --job-queue-name schedulingPolicy-queue \
>     --state enabled \
>     --scheduling-policy-arn $SP_ARN \
>     --compute-environment-order "order=1,computeEnvironment=xs-ce" \
>     --tags Workshop=FairShare \
>     --priority 1 |jq -r .
{
  "jobQueueName": "schedulingPolicy-queue",
  "jobQueueArn": "arn:aws:batch:ap-northeast-1:012345678910:job-queue/schedulingPolicy-queue"
}

共有識別子としてalicebobを指定したジョブを 8 個ずつ送信します。両者は同一の優先度を指定します。

$ for x in {1..8};do
>   aws batch submit-job --job-definition  ${JDEF:-test-job} \
>         --share-identifier "alice"  \
>         --job-name "alice-${x}" \
>         --scheduling-priority-override 1 \
>         --job-queue schedulingPolicy-queue  |jq '.jobName'
> done
"alice-1"
"alice-2"
"alice-3"
"alice-4"
"alice-5"
"alice-6"
"alice-7"
"alice-8"
$ for x in {1..8};do
>   aws batch submit-job --job-definition  ${JDEF:-test-job} \
>         --share-identifier "bob"  \
>         --job-name "bob-${x}" \
>         --scheduling-priority-override 1 \
>         --job-queue schedulingPolicy-queue  |jq '.jobName'
> done
"bob-1"
"bob-2"
"bob-3"
"bob-4"
"bob-5"
"bob-6"
"bob-7"
"bob-8"

listBatchQueue関数を用いてチェックすると、両者がほぼ均等に処理されたことが分かります。

$ listBatchQueue schedulingPolicy-queue
### Queue: schedulingPolicy-queue
"[SUCCEEDED] alice-2 1646555486402"
"[SUCCEEDED] alice-1 1646555486883"
"[SUCCEEDED] bob-1 1646555487373"
"[SUCCEEDED] bob-2 1646555487383"
"[SUCCEEDED] alice-5 1646555576064"
"[SUCCEEDED] alice-3 1646555576705"
"[SUCCEEDED] alice-6 1646555577120"
"[SUCCEEDED] alice-4 1646555577745"
"[SUCCEEDED] bob-5 1646555667794"
"[SUCCEEDED] bob-4 1646555668181"
"[SUCCEEDED] bob-7 1646555668683"
"[SUCCEEDED] bob-3 1646555668783"
"[SUCCEEDED] alice-8 1646555758651"
"[SUCCEEDED] bob-6 1646555759344"
"[SUCCEEDED] bob-8 1646555759765"
"[SUCCEEDED] alice-7 1646555760046"

前のセクションで試行した V1 と V2 のジョブの処理のされ方と違いがあることが分かります。

b. マルチワークロード

先ほどはスケジューリングポリシーでshareDistributionが未設定でしたが、それを指定するパターンを試行します。

shareDistributionでは以下のように共有識別子とその重み係数を指定できます。(が、今回は重み係数は未指定で、デフォルトの1になります。)

"shareDistribution": [
   {
      "shareIdentifier": "string",
      "weightFactor": number
   }
]

前フェーズとは異なるスケジューリングポリシーを作成します。

$ cat > multiWorkloadSP.json << EOF
> {
>     "name": "multiWorkloadSP",
>     "fairsharePolicy": { 
>       "computeReservation": 0,
>       "shareDecaySeconds": 0,
>       "shareDistribution": [{ 
>             "shareIdentifier": "blueSP1"
>         },{
>             "shareIdentifier": "greenSP1"
>       }]
>    }
> }
> EOF
$ SP_ARN=$(aws batch create-scheduling-policy  --cli-input-json file://multiWorkloadSP.json |jq -r .arn)
$ echo $SP_ARN
arn:aws:batch:ap-northeast-1:012345678910:scheduling-policy/multiWorkloadSP

作成したスケジューリングポリシーを関連づけたジョブキューを作成します。

$ aws batch create-job-queue \
>     --job-queue-name multiWorkloads-queue \
>     --state enabled \
>     --scheduling-policy-arn $SP_ARN \
>     --compute-environment-order "order=1,computeEnvironment=xs-ce" \
>     --tags Workshop=FairShare \
>     --priority 1 |jq -r .
{
  "jobQueueName": "multiWorkloads-queue",
  "jobQueueArn": "arn:aws:batch:ap-northeast-1:012345678910:job-queue/multiWorkloads-queue"
}

共有識別子としてblueSP1greenSP1を指定したジョブを 10 個ずつ送信します。

$ for x in {1..10}; do 
>     aws batch submit-job --job-definition  ${JDEF:-test-job} \
>         --share-identifier "blueSP1"  \
>         --job-name "fair-blue-${x}" \
>         --scheduling-priority-override 1  \
>         --job-queue multiWorkloads-queue  |jq '.jobName'
> done
"fair-blue-1"
"fair-blue-2"
"fair-blue-3"
"fair-blue-4"
"fair-blue-5"
"fair-blue-6"
"fair-blue-7"
"fair-blue-8"
"fair-blue-9"
"fair-blue-10"
$ for x in {1..10}; do 
>     aws batch submit-job --job-definition  ${JDEF:-test-job} \
>         --share-identifier "greenSP1"  \
>         --job-name "fair-green-${x}" \
>         --scheduling-priority-override 1  \
>         --job-queue multiWorkloads-queue  |jq '.jobName'
> done
"fair-green-1"
"fair-green-2"
"fair-green-3"
"fair-green-4"
"fair-green-5"
"fair-green-6"
"fair-green-7"
"fair-green-8"
"fair-green-9"
"fair-green-10"

処理された順番を見ると、概ね均等になっていることが分かります。

$ listBatchQueue multiWorkloads-queue |egrep "fair-(blue|green)"
"[SUCCEEDED] fair-blue-1 1646561885570"
"[SUCCEEDED] fair-blue-4 1646561885586"
"[SUCCEEDED] fair-blue-3 1646561885636"
"[SUCCEEDED] fair-blue-2 1646561886040"
"[SUCCEEDED] fair-green-2 1646561975276"
"[SUCCEEDED] fair-green-5 1646561975465"
"[SUCCEEDED] fair-green-3 1646561975467"
"[SUCCEEDED] fair-green-1 1646561976617"
"[SUCCEEDED] fair-blue-9 1646562067287"
"[SUCCEEDED] fair-blue-5 1646562067391"
"[SUCCEEDED] fair-blue-6 1646562068263"
"[SUCCEEDED] fair-blue-8 1646562068927"
"[SUCCEEDED] fair-green-4 1646562158404"
"[SUCCEEDED] fair-green-9 1646562159702"
"[SUCCEEDED] fair-green-7 1646562159787"
"[SUCCEEDED] fair-green-8 1646562160220"
"[SUCCEEDED] fair-green-6 1646562250368"
"[SUCCEEDED] fair-blue-7 1646562250425"
"[SUCCEEDED] fair-green-10 1646562250880"
"[SUCCEEDED] fair-blue-10 1646562251345"

今回は重み係数が 1:1 だったので前フェーズとあまり変わりがない結果になりました。

c. 優先度の設定

優先度の違いによる処理の順番の違いを確認します。

先ほど作成したジョブキューを使用し、優先度の異なるblue-lowジョブとblue-highジョブを 10 個ずつ送信します。

$ for x in {1..10}; do 
>     aws batch submit-job --job-definition ${JDEF:-test-job} \
>         --share-identifier "blueSP1"  \
>         --job-name "blue-low-${x}" \
>         --scheduling-priority-override 1  \
>         --job-queue multiWorkloads-queue  |jq '.jobName'
> done
"blue-low-1"
"blue-low-2"
"blue-low-3"
"blue-low-4"
"blue-low-5"
"blue-low-6"
"blue-low-7"
"blue-low-8"
"blue-low-9"
"blue-low-10"
$ for x in {1..10}; do 
>     aws batch submit-job --job-definition ${JDEF:-test-job} \
>         --share-identifier "blueSP1"  \
>         --job-name "blue-high-${x}" \
>         --scheduling-priority-override 2  \
>         --job-queue multiWorkloads-queue  |jq '.jobName'
> done
"blue-high-1"
"blue-high-2"
"blue-high-3"
"blue-high-4"
"blue-high-5"
"blue-high-6"
"blue-high-7"
"blue-high-8"
"blue-high-9"
"blue-high-10"

↑ 前者が優先度1、後者が優先度2で送信されているため、blue-highのジョブが先に処理されることが期待されます。

処理の途中を確認すると、blue-highから処理されていることが分かります。

$ listBatchQueue multiWorkloads-queue |egrep "blue-(low|high)"
"[RUNNABLE] blue-low-1"
"[RUNNABLE] blue-low-2"
"[RUNNABLE] blue-low-3"
"[RUNNABLE] blue-low-4"
"[RUNNABLE] blue-low-5"
"[RUNNABLE] blue-low-6"
"[RUNNABLE] blue-low-7"
"[RUNNABLE] blue-low-8"
"[RUNNABLE] blue-low-9"
"[RUNNABLE] blue-low-10"
"[RUNNABLE] blue-high-8"
"[RUNNABLE] blue-high-10"
"[RUNNING] blue-high-7 1646562708311"
"[RUNNING] blue-high-6 1646562709490"
"[RUNNING] blue-high-9 1646562709576"
"[RUNNING] blue-high-4 1646562709922"
"[SUCCEEDED] blue-high-5 1646562616961"
"[SUCCEEDED] blue-high-3 1646562617282"
"[SUCCEEDED] blue-high-2 1646562618008"
"[SUCCEEDED] blue-high-1 1646562618069"

一通り終わってから確認すると、blue-highがすべて処理されてからblue-lowが処理されたことが分かります。

$ listBatchQueue multiWorkloads-queue |egrep "blue-(low|high)"
"[SUCCEEDED] blue-high-5 1646562616961"
"[SUCCEEDED] blue-high-3 1646562617282"
"[SUCCEEDED] blue-high-2 1646562618008"
"[SUCCEEDED] blue-high-1 1646562618069"
"[SUCCEEDED] blue-high-7 1646562708311"
"[SUCCEEDED] blue-high-6 1646562709490"
"[SUCCEEDED] blue-high-9 1646562709576"
"[SUCCEEDED] blue-high-4 1646562709922"
"[SUCCEEDED] blue-high-8 1646562799399"
"[SUCCEEDED] blue-high-10 1646562799620"
"[SUCCEEDED] blue-low-2 1646562800735"
"[SUCCEEDED] blue-low-1 1646562801021"
"[SUCCEEDED] blue-low-3 1646562890565"
"[SUCCEEDED] blue-low-5 1646562890649"
"[SUCCEEDED] blue-low-4 1646562891893"
"[SUCCEEDED] blue-low-6 1646562892208"
"[SUCCEEDED] blue-low-7 1646562982561"
"[SUCCEEDED] blue-low-9 1646562982580"
"[SUCCEEDED] blue-low-8 1646562982836"
"[SUCCEEDED] blue-low-10 1646562983489"

今回はこのような綺麗な結果となりましたが、一部のblue-lowが先に処理される、といったことも起こりうるかと思います。

d. キャパシティの予約

続いてキャパシティ予約について確認します。キャパシティ予約はスケジューリングポリシーに設定可能な項目で、ジョブが送信されていない共有識別子のためにコンピューティング環境のリソースを一部予約しておく機能です。

下記のアニメーションは以下条件でのジョブ実行の様子を表したものです。

  • スケジューリングポリシーで以下の共有識別子が定義されている
    • blue:重み係数 1
    • green : 重み係数 1
    • コンピュート予約:50(%)
  • コンピューティング環境ではジョブ 4 個分の 最大 vCPU が定義されている

fair-share_reservation_weights

(画像はワークショップのページより引用)

はじめに green のジョブが4つキューイングされますが、ひとつ空きを残した状態でジョブが処理されます。blue のジョブがキューイングされるとその空きがあてがわれ、以降は 1:1 となるようにジョブが処理されていきます。

ここでの空きが「予約されたキャパシティ」です。予約される割合は以下の計算式で導かれます。

  • コンピュート予約/100 の「アクティブな共有識別子の数」乗

今回で言えば 50 % * 50% で 25 % が該当します。コンピュート予約が 60 で共有識別子が 3 個の場合は 60% * 60% * 60% で 21.6% が予約されます。

コンピュート予約の挙動を確認する手順を実行していきます。また新たなスケジューリングポリシーを作成します。

$ cat > reservedSP.json << EOF
> {
>     "name": "reservedSP",
>     "fairsharePolicy": { 
>       "computeReservation": 50,
>       "shareDecaySeconds": 0,
>       "shareDistribution": [{ 
>             "shareIdentifier": "blueSP1"
>         },{
>             "shareIdentifier": "greenSP1"
>       }]
>    }
> }
> EOF
$ SP_ARN=$(aws batch create-scheduling-policy  --cli-input-json file://reservedSP.json |jq -r .arn)

上記のスケジューリングポリシーを指定したジョブキューを作成します。

$ aws batch create-job-queue \
>     --job-queue-name reservedSP-queue \
>     --state enabled \
>     --scheduling-policy-arn $SP_ARN \
>     --compute-environment-order "order=1,computeEnvironment=xs-ce" \
>     --priority 1 |jq .
{
  "jobQueueName": "reservedSP-queue",
  "jobQueueArn": "arn:aws:batch:ap-northeast-1:012345678910:job-queue/reservedSP-queue"
}

スケジューリングポリシーで指定した共有識別子blueSP1greenSP1を指定し、重みが 1 のジョブを10個ずつ送信します。

まずはblueSP1の送信です。

$ for x in {1..10}; do 
>     aws batch submit-job --job-definition ${JDEF:-test-job} \
>         --share-identifier "blueSP1"  \
>         --job-name "reserved-v3-blue-${x}" \
>         --scheduling-priority-override 1  \
>         --job-queue reservedSP-queue  |jq '.jobName'
> done
"reserved-v3-blue-1"
"reserved-v3-blue-2"
"reserved-v3-blue-3"
"reserved-v3-blue-4"
"reserved-v3-blue-5"
"reserved-v3-blue-6"
"reserved-v3-blue-7"
"reserved-v3-blue-8"
"reserved-v3-blue-9"
"reserved-v3-blue-10"

ここでは 25% の空きを残し 3 つずつジョブが処理されます。

続いてgreenSP1を送信します。

$ for x in {1..10}; do 
>     aws batch submit-job --job-definition ${JDEF:-test-job} \
>         --share-identifier "greenSP1"  \
>         --job-name "reserved-v3-green-${x}" \
>         --scheduling-priority-override 1  \
>         --job-queue reservedSP-queue  |jq '.jobName'
> done
"reserved-v3-green-1"
"reserved-v3-green-2"
"reserved-v3-green-3"
"reserved-v3-green-4"
"reserved-v3-green-5"
"reserved-v3-green-6"
"reserved-v3-green-7"
"reserved-v3-green-8"
"reserved-v3-green-9"
"reserved-v3-green-10"

以降はジョブが 4 つずつ処理されるようになり、blueSP1greenSP1が(概ね)均等になるように順番に処理されていきます。(コマンドの結果を控え損ねたので割愛します。)

e. Decay Factor

最後はshareDecaySecondsについて取り上げられます。共有減退秒数とでも訳しておきましょう。

ここで指定した秒数だけ、過去のジョブ実行の偏りが考慮されます。例えば共有識別子A,Bがあり A だけが過去に多く実行されていた場合、B のジョブが送信された場合その重みが大きくなるよう調整されます。 0 ~ 604,800(一週間)で指定可能で、0の場合は過去の実行が考慮されないということになります。ここまでの手順で試してきたのはすべてshareDecaySecondsが 0 のスケジュールポリシーでした。

shareDecaySecondsが 600 のスケジューリングポリシーを作成します。

$ cat > multiDecaySP.json << EOF
> {
>     "name": "multiDecaySP",
>     "fairsharePolicy": { 
>       "computeReservation": 0,
>       "shareDecaySeconds": 600,
>       "shareDistribution": [{ 
>             "shareIdentifier": "blueSP1"
>         },{
>             "shareIdentifier": "greenSP1"
>       }]
>    }
> }
> EOF
$ SP_ARN=$(aws batch create-scheduling-policy  --cli-input-json file://multiDecaySP.json |jq -r .arn)
$ echo $SP_ARN
arn:aws:batch:ap-northeast-1:012345678910:scheduling-policy/multiDecaySP

上記のスケジューリングポリシーを関連づけたジョブキューを作成します。

$ aws batch create-job-queue \
>     --job-queue-name multiDecaySP-queue \
>     --state enabled \
>     --scheduling-policy-arn $SP_ARN \
>     --compute-environment-order "order=1,computeEnvironment=xs-ce" \
>     --tags Workshop=FairShare \
>     --priority 1 |jq -r .
{
  "jobQueueName": "multiDecaySP-queue",
  "jobQueueArn": "arn:aws:batch:ap-northeast-1:012345678910:job-queue/multiDecaySP-queue"
}

blueSP1共有識別子を持つジョブを 100 個送信します。

for x in {1..100}; do 
    aws batch submit-job --job-definition ${JDEF:-test-job} \
        --share-identifier "blueSP1"  \
        --job-name "decay-blue-${x}" \
        --scheduling-priority-override 1  \
        --job-queue multiDecaySP-queue  |jq '.jobName'
done

ここで 10 分ほど置いてからgreenSP1共有識別子を持つジョブを 50 個送信します。

for x in {1..50}; do 
    aws batch submit-job --job-definition ${JDEF:-test-job} \
        --share-identifier "greenSP1"  \
        --job-name "decay-green-${x}" \
        --scheduling-priority-override 1  \
        --job-queue multiDecaySP-queue  |jq '.jobName'
done
sleep 60
listBatchQueue multiDecaySP-queue

当初わたしはよく意図を理解しないままgreenSP1のジョブも送信してしまったので、ワークショップで意図された結果になっているか自信がありません。

完了したジョブの数を定期的に確認すると、green が上限の 50 に達するまではほぼ同じペースでジョブが処理されていたように見えます。

$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     16 decay-blue
     16 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     24 decay-blue
     20 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     28 decay-blue
     28 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     36 decay-blue
     32 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     46 decay-blue
     38 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     49 decay-blue
     43 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
      59 decay-blue
     49 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
     74 decay-blue
     50 decay-green
$ listBatchQueue multiDecaySP-queue | grep "SUCCEEDED" | egrep -o "(decay-(green|blue))" | sort | uniq -c
    100 decay-blue
     50 decay-green

最後ごちゃっとしましたが、一通りの手順が終わりました。

クリーンアップ

今回作成したリソースのうち、少なくともコンピューティング環境は忘れずに削除(もしくは無効化)しておいてください。スポットインスタンスではあるものの、m5.8xlarge と同程度のインスタンスが 4 台稼働している状態です。

ジョブキューを削除するまでコンピューティング環境が削除できない、といった関連性を持つものもあります。

基本的にはページの手順に則って順次削除していけばよいでしょう。

以下のように削除に使用できるコマンドも用意されています。

for j in $(aws batch describe-job-definitions --output json |jq -r '.jobDefinitions[] | "\(.jobDefinitionName):\(.revision)"' |xargs);do
    echo "> aws batch deregister-job-definition --job-definition=${j}"
    case $(basename $(echo $0|sed -e 's/-//g')) in 
    "zsh") read "yn?Do you wish to deregister the job definition '${j}'? ";;
    *)     read -p "Do you wish to deregister the job definition '${j}'? " yn;;
    esac
    case $yn in
        [Yy]* ) aws batch deregister-job-definition --job-definition=${j};;
        [Nn]* ) continue;;
        * ) echo "Please answer yes or no.";;
    esac 
done

これを実行するとリソース(上記の例ではジョブ定義)が一つずつ出力され、それぞれに y(削除する)もしくは n (スキップする)を入力していく、という流れになります。

ちょっとカスタマイズすれば他のリソースでも使い回しができそうなコマンドですね。

終わりに

AWS Batch のフェアシェアスケジューリングが学べるワークショップをやってみました。なかなかボリューミーでしたが、AWS Batch のジョブのスケジューリングについて理解が深まった気がします。

ちょこちょこワークショップの手順でつまずいた部分があったのですが、そこをカバーできるように本エントリを書いてみたので参考になれば幸いです。

スムーズに行けば 2 時間かからず終わるかと思いますので、興味がある方は試してみてください。

以上、 チバユキ (@batchicchi) がお送りしました。