Kubernetes で Complete/Failed となった Job を自動削除する

# TL;DR

kube-apiserver に Feature Gates で以下を設定する。

```
--feature-gates=TTLAfterFinished=true
```

以下のように Job のマニフェストに `.spec.ttlSecondsAfterFinished` を追加する。

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  ttlSecondsAfterFinished: 30 # こちらのフィールドを追加
  template:
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["date"]
        env:
        - name: TZ
          value: "JST-9"
      restartPolicy: Never
```

# はじめに

Kubernetes でのバッチ処理などで Job を利用していて、以下のように実行が完了した Job とそれに依存する Pod が大量に残ってしまっているケースがあるかと思います。

```bash
$ kubectl get jobs,pods
NAME     COMPLETIONS   DURATION   AGE
myjob0   1/1           19s        112s
myjob1   1/1           23s        111s
myjob2   1/1           21s        111s
myjob3   1/1           26s        111s
myjob4   1/1           36s        111s
myjob5   1/1           33s        111s
myjob6   1/1           54s        111s
myjob7   1/1           59s        110s
myjob8   1/1           56s        110s
myjob9   1/1           30s        110s

NAME           READY   STATUS      RESTARTS   AGE
myjob0-lgx7h   0/1     Completed   0          90s
myjob1-sdbtg   0/1     Completed   0          89s
myjob2-ng5lb   0/1     Completed   0          89s
myjob3-fmmw4   0/1     Completed   0          89s
myjob4-z654m   0/1     Completed   0          89s
myjob5-4scjl   0/1     Completed   0          89s
myjob6-qg5xr   0/1     Completed   0          89s
myjob7-42s7r   0/1     Completed   0          88s
myjob8-7zzmg   0/1     Completed   0          88s
myjob9-hhrjw   0/1     Completed   0          88s
```

これを気付いた時に毎回手作業で削除するのは面倒なので、今回は Kubernetes で Complete/Failed となった Job を自動削除する方法を紹介します。

# Feature Gates `TTLAfterFinished` について

`TTLAfterFinished` は Kubernetes 1.12 から Alpha で追加された Feature Gates で、これを有効化することで終了したリソースをクリーンアップするための TTL controller が機能するようになります。なお、この機能は Alpha ステージの実験段階のものなのでデフォルトでは有効化されていません。
https://v1-16.docs.kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/

こちらの機能が提供された背景を知るために [Design Doc](https://github.com/kubernetes/enhancements/blob/master/keps/sig-apps/0026-ttl-after-finish.md) を見てみると Kubeflow, Prow, Apache Spark on Kubernetes, Jenkins Kubernetes plugin などから実行された Job をクリーンアップすることが具体的なユースケースとして挙げられていました。

# TTL Controller について

TTL Controller は実行が完了したリソースに TTL の機構を提供するもので、現在は Job のみがサポートされています。
https://v1-16.docs.kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/

TTL Controller が有効な状態で Job Spec の `.spec.ttlSecondsAfterFinished` に値を設定する（設定例は[こちら](https://v1-16.docs.kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#ttl-mechanism-for-finished-jobs)）ことで Complete/Failed となった Job を自動削除することが可能になります。リソースの実行が完了して `.spec.ttlSecondsAfterFinished` 秒後に、そのリソースが TTL Controller によって（依存するリソースと共に）自動削除されるイメージです。

# Kubernetes クラスタ管理者向けな情報

[Mutating Admission Webhook](https://v1-16.docs.kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) を使って、作成される Job にデフォルトで `.spec.ttlSecondsAfterFinished` を設定する運用も良いとのことでした。

また、TTL Controller はリソースに保存されている時間を元に制御を行うので、厳密な運用をしたければ Kubernetes クラスタの全ての Node で NTP を起動するのが望ましいとのことでした。詳細は [#6159](https://github.com/kubernetes/kubernetes/issues/6159#issuecomment-93844058) を参照ください。

# 動作検証

`TTLAfterFinished` を有効化して minikube を起動します。

```bash
$ minikube version
minikube version: v1.4.0
commit: 7969c25a98a018b94ea87d949350f3271e9d64b6

$ minikube start --kubernetes-version=v1.16.0 --feature-gates=TTLAfterFinished=true

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.4", GitCommit:"c27b913fddd1a6c480c229191a087698aa92f0b1", GitTreeState:"clean", BuildDate:"2019-03-01T23:35:19Z", GoVersion:"go1.12", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.0", GitCommit:"2bd9643cee5b3b3a5ecbd3af49d09018f0773c77", GitTreeState:"clean", BuildDate:"2019-09-18T14:27:17Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl get pods -n kube-system kube-apiserver-minikube -o yaml | grep feature-gates
    - --feature-gates=TTLAfterFinished=true
```

以下のスクリプトで `.spec.ttlSecondsAfterFinished` を設定した Job を複数作成します。今回は30秒で削除される設定を適用します。

```bash
for i in $(seq 0 4); do
cat << EOM | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob${i}
spec:
  ttlSecondsAfterFinished: 30
  template:
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["date"]
        env:
        - name: TZ
          value: "JST-9"
      restartPolicy: Never
EOM
done
```

上記のスクリプトを実行すると5つの Job 及び、それに依存する Pod が作成されます。

```
$ kubectl get jobs,pods
NAME               COMPLETIONS   DURATION   AGE
job.batch/myjob0   1/1           8s         23s
job.batch/myjob1   1/1           11s        23s
job.batch/myjob2   1/1           21s        23s
job.batch/myjob3   1/1           13s        22s
job.batch/myjob4   1/1           16s        22s

NAME               READY   STATUS      RESTARTS   AGE
pod/myjob0-g9vjm   0/1     Completed   0          23s
pod/myjob1-pfcdt   0/1     Completed   0          23s
pod/myjob2-mf95m   0/1     Completed   0          23s
pod/myjob3-8xv7k   0/1     Completed   0          22s
pod/myjob4-4bfp5   0/1     Completed   0          22s
```

30秒が経過すると、Job とそれに紐づく Pod が削除されていることがわかると思います。

```bash
$ kubectl get jobs,pods
No resources found.
```

# さいごに

今回は Kubernetes で Complete/Failed となった Job を自動削除する方法を紹介しました。CronJob を利用している方には不要かもしれませんが、Job 単体で利用されている方にとっては有効な設定かと思われます。

最近、個人的にカスタムコントローラーを学習していたのもあり、Kubernetes Way（client-go + code-generator）な実装である TTL Controller の実装を読めたことが非常に有意義でした。おしまい。

# おまけ: TTL Controller の実装

Kubernetes 1.16.0 の実装を見ていきます。

Reconcile Loop の中で以下の `processJob` 関数を使って、Job の状態と TTL をチェックして削除するという処理が実行されています。シンプルな実装で自分でも普通に読めたので、ご興味ある方は是非参照ください。
https://github.com/kubernetes/kubernetes/blob/v1.16.0/pkg/controller/ttlafterfinished/ttlafterfinished_controller.go#L186-L238

# 備考: CronJob を利用している場合

CronJob を利用している場合には `.spec.successfulJobsHistoryLimit (デフォルト:3)` と `.spec.failedJobsHistoryLimit (デフォルト:1)` をマニフェストに定義することで、CronJob により定期実行される Job 及び、依存する Pod の履歴数を管理することができます。
https://v1-16.docs.kubernetes.io/docs/tasks/job/#jobs-history-limits