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



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

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


## シェルスクリプト作成

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

~~~my-shell
#!/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 を指定します。 このパラメータは後に、オーバーライトできますので、サクッとテストできるものが適切でしょう。

~~~Dockerfile
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 の２つのファイルがあるディレクトリで、コンテナのビルドを実行します。

~~~
$ 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
~~~

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

~~~
$ 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` に設定します。 最初の実行から２回リトライして、やはり失敗したらジョブの再試行を終了するというものです。

~~~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)したのかを確認します。次のコマンドで確認したところ、
初回起動時に加えて、２回試行して、終了していることが確認できます。

~~~
$ 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
~~~


以上