k8sで定期実行と1回だけバッチを実行する (CronJobとJob)

cronみたいな定期実行のバッチや1回しか使わないバッチをDockerコンテナとしてパッケージにして定期実行することができます。
k8sのkind CronJobってのがcron的なもので、Jobってのがその時だけ実行するやつですね。

Dockerコンテナ内でcrontabを書いておく不安な方法よりも、実行するときだけPodsが起動するのでエコです。

それぞれ、失敗したときの挙動とか、並列実行とか設定できるので便利。詳しくは下記ページをご参照あれ。

Job: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/
CronJob: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/

# Job

Jobの方から試してみましょう。

## こんな感じのファイルを用意します。

```dockerfile:Dockerfile
FROM docker/whalesay:latest
ENTRYPOINT [ ":" ] # なにもしない。Dockerfile作る意味はとくにない。
```

```yaml:k8s.cowsay.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: cowsay-job-20190520
spec:
  template:
    spec:
      containers:
      - name: cowsay-job
        image: murata/cowsay-job:v3
        command: ["cowsay", "わたしはk8sです。"]
      restartPolicy: OnFailure
  backoffLimit: 5
```

## Dockerfileをビルドします。

```
~/tmp/k8s-job
❯ docker build -t murata/cowsay-job:v3 .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM docker/whalesay:latest
 ---> 6b362a9f73eb
Step 2/2 : ENTRYPOINT [ ":" ] # なにもしない。Dockerfile作る意味ない。
 ---> Running in 93cd12fc5d81
Removing intermediate container 93cd12fc5d81
 ---> 453bf70f357d
Successfully built 453bf70f357d
Successfully tagged murata/cowsay-job:v3
```

## k8sに食わせます。

```
~/tmp/k8s-job
❯ kubectl apply -f ./k8s.cowsay.job.yaml
job.batch/cowsay-job-20190520 created
```

## job.batchとpodが出来上がっています。

すでに終わっていますね。

```
~/tmp/k8s-job
❯ kubectl get all
NAME                            READY   STATUS      RESTARTS   AGE
pod/cowsay-job-20190520-fpvmm   0/1     Completed   0          26s

NAME                            COMPLETIONS   DURATION   AGE
job.batch/cowsay-job-20190520   1/1           2s         26s
```

## ログをみてみましょう。

```
~/tmp/k8s-job
❯ kubectl log job.batch/cowsay-job-20190520
log is DEPRECATED and will be removed in a future version. Use logs instead.
 __________________________ 
< わたしはk8sです。 >
 -------------------------- 
    \
     \
      \     
                    ##        .            
              ## ## ##       ==            
           ## ## ## ##      ===            
       /""""""""""""""""___/ ===        
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
       \______ o          __/            
        \    \        __/             
          \____\______/   
```

うごいていますね。

## エラーの場合

`backoffLimit: 5`としているので失敗しても5回まで実行してくれます。失敗させてみましょう。
下記のように存在しないコマンドにしてみます。

```
apiVersion: batch/v1
kind: Job
metadata:
  name: pepesay-job-20190520
spec:
  template:
    spec:
      containers:
      - name: pepesay-job
        image: murata/cowsay-job:v3
        command: ["pepesay", "わたしはペペです。"]
      restartPolicy: OnFailure
  backoffLimit: 5
```

がんばっているようです。すでに2回リスタートしています。

```
~/tmp/k8s-job
❯ kubectl get all
NAME                             READY   STATUS              RESTARTS   AGE
pod/cowsay-job-20190520-fpvmm    0/1     Completed           0          5m30s
pod/pepesay-job-20190520-gchs9   0/1     RunContainerError   2          37s

NAME                             COMPLETIONS   DURATION   AGE
job.batch/cowsay-job-20190520    1/1           2s         5m30s
job.batch/pepesay-job-20190520   0/1           37s        37s
```

ちゃんと5回(リスタートは4回)終わって諦めたようです。

```
❯ kubectl get all
NAME                             READY   STATUS             RESTARTS   AGE
pod/cowsay-job-20190520-fpvmm    0/1     Completed          0          7m44s
pod/pepesay-job-20190520-gchs9   0/1     CrashLoopBackOff   4          2m51s

NAME                             COMPLETIONS   DURATION   AGE
job.batch/cowsay-job-20190520    1/1           2s         7m44s
job.batch/pepesay-job-20190520   0/1           2m51s      2m51s
```

jobのdescribeを見てみるとだめだったぽよってWarningが出ていますね。

```
❯ kubectl describe job.batch/pepesay-job-20190520
Name:           pepesay-job-20190520
Namespace:      cowsay
Selector:       controller-uid=28ea1281-7c74-11e9-9b99-080027ae51c3
Labels:         controller-uid=28ea1281-7c74-11e9-9b99-080027ae51c3
                job-name=pepesay-job-20190520
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pepesay-job-20190520","namespace":"cowsay"},"spec":{"backoffLim...
Parallelism:    1
Completions:    1
Start Time:     Wed, 22 May 2019 18:29:55 +0900
Pods Statuses:  0 Running / 0 Succeeded / 1 Failed
Pod Template:
  Labels:  controller-uid=28ea1281-7c74-11e9-9b99-080027ae51c3
           job-name=pepesay-job-20190520
  Containers:
   pepesay-job:
    Image:      murata/cowsay-job:v3
    Port:       <none>
    Host Port:  <none>
    Command:
      pepesay
      わたしはk8sです。
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type     Reason                Age    From            Message
  ----     ------                ----   ----            -------
  Normal   SuccessfulCreate      3m25s  job-controller  Created pod: pepesay-job-20190520-gchs9
  Normal   SuccessfulDelete      11s    job-controller  Deleted pod: pepesay-job-20190520-gchs9
  Warning  BackoffLimitExceeded  11s    job-controller  Job has reached the specified backoff limit
```

エラーjobの仕込み方が悪かったのかログがでていない。。。

```
❯ kubectl logs job.batch/pepesay-job-20190520
error: timed out waiting for the condition
```

# CronJob

cronjobの場合は、scheduleにcrontab形式で記載します。また、前の処理が終わっていなかった場合にどうするかをconcurrencyPolicyで決めます。(終わるまでまつ、やらない、殺して実行とか。)

1分に1回実行してみます。

```docker:k8s.cowsay.cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cowsay-cronjob-20190520
spec:
  concurrencyPolicy: Replace
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cowsay-cronjob
            image: murata/cowsay-job:v3
            command: ["cowsay", "わたしはCronJobです。"]
          restartPolicy: OnFailure
```



こんな感じcronjobが配置されます。

```
❯ kubectl get all,jobs -o wide
NAME                            READY   STATUS      RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
pod/cowsay-job-20190520-fpvmm   0/1     Completed   0          57m   172.17.0.6   minikube   <none>           <none>

NAME                            COMPLETIONS   DURATION   AGE   CONTAINERS   IMAGES                 SELECTOR
job.batch/cowsay-job-20190520   1/1           2s         57m   cowsay-job   murata/cowsay-job:v3   controller-uid=7a5a3b6b-7c73-11e9-9b99-080027ae51c3

NAME                                    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE   CONTAINERS       IMAGES                 SELECTOR
cronjob.batch/cowsay-cronjob-20190520   */1 * * * *   False     0        <none>          18s   cowsay-cronjob   murata/cowsay-job:v3   <none>
```

しばらくするとこんな感じに表示されます。

```
❯ kubectl get all,jobs -o wide
NAME                                           READY   STATUS      RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
pod/cowsay-cronjob-20190520-1558520580-xtwq7   0/1     Completed   0          89s   172.17.0.6   minikube   <none>           <none>
pod/cowsay-cronjob-20190520-1558520640-br724   0/1     Completed   0          29s   172.17.0.6   minikube   <none>           <none>
pod/cowsay-job-20190520-fpvmm                  0/1     Completed   0          59m   172.17.0.6   minikube   <none>           <none>

NAME                                           COMPLETIONS   DURATION   AGE   CONTAINERS       IMAGES                 SELECTOR
job.batch/cowsay-cronjob-20190520-1558520580   1/1           1s         89s   cowsay-cronjob   murata/cowsay-job:v3   controller-uid=97619397-7c7b-11e9-9b99-080027ae51c3
job.batch/cowsay-cronjob-20190520-1558520640   1/1           1s         29s   cowsay-cronjob   murata/cowsay-job:v3   controller-uid=bb45dacd-7c7b-11e9-9b99-080027ae51c3
job.batch/cowsay-job-20190520                  1/1           2s         59m   cowsay-job       murata/cowsay-job:v3   controller-uid=7a5a3b6b-7c73-11e9-9b99-080027ae51c3

NAME                                    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE    CONTAINERS       IMAGES                 SELECTOR
cronjob.batch/cowsay-cronjob-20190520   */1 * * * *   False     0        36s             2m8s   cowsay-cronjob   murata/cowsay-job:v3   <none>
```

実行結果をみてみます。

```
❯ kubectl logs pod/cowsay-cronjob-20190520-1558520580-xtwq7
 ______________________________ 
< わたしはCronJobです。 >
 ------------------------------ 
    \
     \
      \     
                    ##        .            
              ## ## ##       ==            
           ## ## ## ##      ===            
       /""""""""""""""""___/ ===        
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
       \______ o          __/            
        \    \        __/             
          \____\______/   
```

自分の環境の場合、完了後のpodは3個残るようです。

`.spec.successfulJobsHistoryLimit` と `.spec.failedJobsHistoryLimit` で設定できるみたい。



# まとめ

今回はk8sでバッチを実行してみました。まぁ普通は並列実行とかしないので、k8sじゃない限りは、crontabでもよいのですが、k8s環境にいる人はぜひ使うと捗りそうです。

なによりロギングが楽。 
crontabに適当に書いて、 /dev/null に捨てちゃったりしていませんか？
ちゃんと設定すればよいのですが、デフォルトがメールやら/var/log/cronなので、他コンテナと同様に処理されるのはよいですね。

デーモン的な永遠に動き続けるもの場合はDaemonSet というものがあります。
https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
pm2も便利かもしれません。 http://pm2.keymetrics.io/
ただしpm2を使う場合1podの中にあまり詰め込みすぎないように気をつけましょう。ノード負荷が偏ります。