kubernetes
ibmcloud

バッチのシェルを書いて、コンテナ化して、レジストリに登録して、k8sクラスタで実行するまでのお話し

Kuberntesには、ジョブというコントローラがあり、バッチ処理をスケジュールすることが出来ます。
しかし、Kuberntesはスケジュールするという表現が使われるのですが、業務システムで利用されるジョブのスケジューラーの様にジョブネットを組んで、前後関係を管理しながら、ジョブ全体の正常終了を管理するものでも無く、科学計算系で利用される様な、計算ノード群へ、ジョブを割り当てて、並列なバッチ処理をサポートするタイプとも、少し違う感じがします。

そこで、実際に試して確認する事にします。

シェルスクリプト作成

ローカルの環境で、下記のサンプルとなるシェル・スクリプトを作成します。 このシェルは、第一引数でループの回数を設定します。1増えるごとに、終了時間が3秒づつ長くなります。 そして、第二引数で終了コードを設定できます。

#!/bin/bash

if [ $# -ne 2 ]; then
  echo "指定された引数は$#個です。" 1>&2
  echo "実行するには2個の引数が必要です。" 1>&2
  echo "my-shell ループ数 完了時終了コード" 1>&2
  exit 1
fi

x=0
while true 
do
  let x=x+1
  echo $x `uuid`
  if [ $x -eq $1 ]; then
      exit $2
  fi
  sleep 3
done

シェルのコンテナ化

このシェルを次のDockerfileでコンテナ化します。 ここでコンテナ実行時のデフォルトの引数として、ループ回数=3、終了コード=0 を指定します。 このパラメータは後に、オーバーライトできますので、サクッとテストできるものが適切でしょう。

FROM ubuntu:latest
COPY ./my-shell /my-shell
RUN apt-get update
RUN apt-get install uuid -y
CMD ["/my-shell", "3","0"]

Dockerfile と my-shell の2つのファイルがあるディレクトリで、コンテナのビルドを実行します。

$ docker build --tag job-x .

これでローカルのリポジトリに登録されたコンテナを確認します。

$ docker images
REPOSITORY        TAG          IMAGE ID            CREATED             SIZE
job-x             latest       4481e04e94e7        37 seconds ago      151MB

次に、コンテナで動作するシェルを確認しておきます。

$ docker run --rm --name job-x job-x:latest "/my-shell" 2 7
1 ecab8456-071e-11e8-b25b-0242ac110002
2 ee7599ca-071e-11e8-aa3d-0242ac110002
$ echo $?
7

リモート・レジストリへ、コンテナ・イメージを登録

テストで問題がなければ、リモート・リポジトリのタグを付けて、リモート・リポジトリへプッシュします。

$ docker tag job-x:latest registry.au-syd.bluemix.net/takara/job:v1
$ docker push registry.au-syd.bluemix.net/takara/job:v1

リモート・リポジトリへの登録結果を確認します。

$ bx cr images
Listing images...

REPOSITORY                              NAMESPACE   TAG   CREATED         SIZE     VULNERABILITY STATUS   
registry.au-syd.bluemix.net/takara/job  takara      v1    6 minutes ago   72 MB    Vulnerable   

コンテナ・イメージの登録に失敗していたら、bx cr login を再実行して、docker pushをリトライして、認証の問題を解決します。

k8sクラスタでジョブ実行

次のジョブを実行するためのYAMLファイルを作成して、k8sクラスタ上にジョブを投入します。 ここで、command:フィールドに、オーバーライドするパラメータを書いておきます。

job.yml
apiVersion: batch/v1
kind: Job
metadata:
  name: job-batch
spec:
  template:
    spec:
      containers:
      - name: job-batch
        image: registry.au-syd.bluemix.net/takara/job:v1
        command: ["/my-shell",  "2", "0"]
      restartPolicy: Never
  backoffLimit: 4

kubectlコマンドでYAMLファイルを設定して実行するだけです。

kubctl create -f job.yml

成功終了の場合、ジョブはスグに終了して、結果を参照できます。kubectl get jobsで、ジョブの終了結果を参照できます。さらに、細かい情報が知りたければ、次の様にコマンドを実行します。 ここでは、Pod Statuses:フィールドで、正常終了が確認できます。 終了コード=0に指定しているので、正常終了したと見なされます。 もしも、ゼロ以外の値の場合は、Failed と見なされ、再試行に入ります。

$ kubectl describe job job-batch
Name:           job-batch
Namespace:      default
Selector:       controller-uid=16725165-0720-11e8-8cfc-76140a51e2a9
Labels:         controller-uid=16725165-0720-11e8-8cfc-76140a51e2a9
                job-name=job-batch
Annotations:    <none>
Parallelism:    1
Completions:    1
Start Time:     Thu, 01 Feb 2018 07:18:23 +0000
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=16725165-0720-11e8-8cfc-76140a51e2a9
           job-name=job-batch
  Containers:
   job-batch:
    Image:  registry.au-syd.bluemix.net/takara/job:v1
    Port:   <none>
    Command:
      /my-shell
      2
      0
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  1m    job-controller  Created pod: job-batch-kk8dd

既にジョブが終了しており、'kubectl get podsでは参照できないので、--show-all`を付加して、参照します。

$ kubectl get pods --show-all
NAME                               READY     STATUS             RESTARTS   AGE
...
job-batch-kk8dd                    0/1       Completed          0          3m
...

ポッドを指定して、ログを表示すると、前述のローカルで実行したものと同じ様に、結果を見ることが出来ます。

$ kubectl logs job-batch-kk8dd 
1 1bfdf5da-0720-11e8-babf-877ba3ad5a1c
2 1dca4cd8-0720-11e8-b9d6-274d9f3cf681

ログをコマンド1行で表示するには、ちょっと長いですが、以下の様にします。

$ kubectl logs $(kubectl get pods  --show-all --selector=job-name=job-batch --output=jsonpath={.items..metadata.name})
1 1bfdf5da-0720-11e8-babf-877ba3ad5a1c
2 1dca4cd8-0720-11e8-b9d6-274d9f3cf681

ジョブの削除

次のコマンドでジョブの削除を実行して、確認します。 結果から確かに削除されたことが解ります。

$ kubectl delete -f job.yml
job "job-batch" deleted

$ kubectl get job job-batch
Error from server (NotFound): jobs.batch "job-batch" not found

ジョブの失敗ケース

シェルの終了コードが 1となる様に、command: ["/my-shell", "2", "1"] を修正します。これは、Dockerfileを参照すれば、CMD ["/my-shell", "3","0"] の様にコンテナを開始した時に、my-shellコマンドが引数と共に実行するように、セットされているのですが、job.ymlに再設定することで、コンテナのCMDを上書きすることができます。

そして、backoffLimit: 2 に設定します。 最初の実行から2回リトライして、やはり失敗したらジョブの再試行を終了するというものです。

job.yml
apiVersion: batch/v1
kind: Job
metadata:
  name: job-batch
spec:
  template:
    spec:
      containers:
      - name: job-batch
        image: registry.au-syd.bluemix.net/takara/job:v1
        command: ["/my-shell",  "2", "1"]
      restartPolicy: Never
  backoffLimit: 2

ジョブを実行します。

$ kubectl create -f job.yml

約1分後に、ジョブの状態を確認すると、SUCCESSFUL(成功)が0の状態になっています。

$ kubectl get job job-batch
NAME        DESIRED   SUCCESSFUL   AGE
job-batch   1         0            1m

何回試行(backoff)したのかを確認します。次のコマンドで確認したところ、
初回起動時に加えて、2回試行して、終了していることが確認できます。

$ kubectl describe job job-batch
Name:           job-batch
Namespace:      default
Selector:       controller-uid=f60bbdef-0723-11e8-8cfc-76140a51e2a9
Labels:         controller-uid=f60bbdef-0723-11e8-8cfc-76140a51e2a9
                job-name=job-batch
Annotations:    <none>
Parallelism:    1
Completions:    1
Start Time:     Thu, 01 Feb 2018 07:46:06 +0000
Pods Statuses:  0 Running / 0 Succeeded / 3 Failed
Pod Template:
  Labels:  controller-uid=f60bbdef-0723-11e8-8cfc-76140a51e2a9
           job-name=job-batch
  Containers:
   job-batch:
    Image:  registry.au-syd.bluemix.net/takara/job:v1
    Port:   <none>
    Command:
      /my-shell
      2
      1
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type     Reason                Age   From            Message
  ----     ------                ----  ----            -------
  Normal   SuccessfulCreate      27s   job-controller  Created pod: job-batch-spvnl
  Normal   SuccessfulCreate      21s   job-controller  Created pod: job-batch-w5wpt
  Normal   SuccessfulCreate      11s   job-controller  Created pod: job-batch-xq6qh
  Warning  BackoffLimitExceeded  1s    job-controller  Job has reach the specified backoff limit

以上