Kubernetes ver1.24新機能「non-graceful node shutdown」

はじめまして。NECソリューションイノベータの毛利と申します。

今日は、Kubernetes v1.24で新しく実装された機能「non-graceful node shutdown」について紹介したいと思います。
私もコミュニティでの議論やコードレビューに参加したので、この機能が実装され、無事にリリースされてとても嬉しいです！

# TL;DR
* Kubernetes ver1.24で「non-graceful node shutdown」機能が新しく実装された。
* Persistent Volume(以降「PV」)がアタッチされたpodが動いている場合、ホストノードがハードウェア障害などで正常なシャットダウンができなかった時でも、non-graceful node shutdown機能を使うことでpodの削除・PVのデタッチができるようになる。

# 背景
まずはこの機能が生まれた背景から説明したいと思います。
KubernetesクラスタのWorkerノードがハードウェア故障等によりダウンした際、Controllerノードはその障害ノード上の pod 群を他のノード上で再作成します。
現状、 `kubectl delete pod -f` コマンドによりpodを強制削除することで回避することが可能ですが、podがたくさんある場合は大変ですよね。

![slide14.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2668916/5e9c540f-04e0-e7c1-ee14-bf8ddb8a06ba.png)
 

この件に関しては数年前からKubernetesコミュニティで議論が行われていたものの、実装方針についてなかなか結論が出ず議論が停滞していました。

# non-graceful node shutdown機能の概要
workerノードがダウンした際に、そのノードに対してユーザは `node.kubernetes.io/out-of-service` taint（ver1.24で新規追加）を付与する必要があります。

kube-controller-managerのPod GC Controllerは、podの状態を確認し、`Terminating` 状態のpodを見つけると、そのpodが動いているノードの状態が `NotReady` になっている、かつ、ノードに `node.kubernetes.io/out-of-service` taintが付与されている場合は、`Terminating` 状態のpodを強制削除します。また、 effectに `NoExecute` を設定して `node.kubernetes.io/out-of-service` taintを付与すると、 `node.kubernetes.io/out-of-service` taintが付与されたノード上のpodはevictされ、また、そのノード上に新しくpodがスケジューリングされなくなります。（podがtolerationを持つ場合以外は）

podが強制削除されると、attachdetach reconciler（Podに対するPVのアタッチ/デタッチを管理する役割を持つkube-controller-managerのVolume Controllerの仕組み）はノードに `node.kubernetes.io/out-of-service` taintが付与されているかどうかをチェックします。taintが付与されていると、デフォルトで設定されている6分間の待機時間を待つことなく、ボリュームの強制デタッチを行います。taintについては https://kubernetes.io/docs/reference/labels-annotations-taints/ に記載されています。

ちなみに、reconcilerというのは、Kubernetesリソースの「あるべき姿」に「実際の姿」を合わせる処理を定期的に行う仕組みです。Kubernetesに詳しい方なら常識だと思うので偉そうに言うことじゃないですけど、、あと調べるとたくさん出てきます。

機能実装のPull Request（以降「PR」）は https://github.com/kubernetes/kubernetes/pull/108486 です。そんなに難しい箇所はないと思うので、見てみると面白いと思います。実際にロジックが変更されているのは以下の2つのファイルです。
* pkg/controller/podgc/gc_controller.go
* pkg/controller/volume/attachdetach/reconciler/reconciler.go




# graceful node shutdown機能との違い
「non-graceful node shutdown」と聞いて、ver1.21でβ実装された「graceful node shutdown」を思い出した方も多いと思います。
なので、ここでは `non-graceful node shutdown` 機能と `graceful node shutdown` 機能の違いを説明します。

ドキュメント https://kubernetes.io/ja/docs/concepts/architecture/nodes/#graceful-node-shutdown で説明されていますが、 `graceful node shutdown` というのは、ノードで `shutdown` シグナルが発生した際に、そのノードの上のpod終了までの猶予期間を設定できる機能です。

ノードのシャットダウン時にその上のpodが異常終了してしまう、という課題を解決するために提案された機能なのですが、ハードウェア障害などでノードがシャットダウンしてしまった場合は、 `shutdown` シグナルによるシャットダウンではなく意図せぬシャットダウンなので（例えば電プチとかですね）、シャットダウンまで待つ、ということができないという課題があります。そこで今回提案されたのが `non-graceful node shutdown` 機能です。


# non-graceful node shutdown機能を使ってみよう

まず、non-graceful node shutdown機能はver1.24ではα機能のためデフォルトでは無効になっています。有効にするには `NodeOutOfServiceVolumeDetach` Feature Gateを有効にする必要があります。`NodeOutOfServiceVolumeDetach` Feature Gateについては https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-gates-for-alpha-or-beta-features に書かれています。

準備ができたらさっそく動作確認してみましょう。
私の環境では以下のノードを用意しました。Controllerノード1台、Workerノード2台です。
```
$ kubectl get nodes
NAME                 STATUS   ROLES           AGE    VERSION
test-control-plane   Ready    control-plane   103s   v1.24.0-beta.0.113+7380fc735aca59-dirty
test-worker          Ready    <none>          69s    v1.24.0-beta.0.113+7380fc735aca59-dirty
test-worker2         Ready    <none>          69s    v1.24.0-beta.0.113+7380fc735aca59-dirty
```

CSIドライバは、私の環境ではNFSサーバを使っているのでNFS CSIドライバを使いました。NFS CSIドライバについてはこちら https://github.com/kubernetes-csi/csi-driver-nfs

動作確認に使うサンプルアプリも用意しましょう。NFS CSIドライバにはサンプルアプリが用意されているのでそれを使います。https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/hack/verify-examples.sh を実行するとアプリが作成されます。

```
$ kubectl get all
NAME                                  READY   STATUS    RESTARTS   AGE
pod/statefulset-nfs-0                 1/1     Running   0          71s

NAME                               READY   AGE
statefulset.apps/statefulset-nfs   1/1     71s
```

PVCも作成されています。
```
$ kubectl get pvc
NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistent-storage-statefulset-nfs-0   Bound    pvc-e174d35e-0a83-4fbb-bf8f-28aa8ec8b7f8   10Gi       RWO            nfs-csi        109s
```

こちらのサンプルアプリは、1秒おきに現在時刻をファイルに出力するというものになっています。
```
$ more /home/yuiko/foo/pvc-e174d35e-0a83-4fbb-bf8f-28aa8ec8b7f8/outfile
Fri May 13 04:34:27 UTC 2022
Fri May 13 04:34:29 UTC 2022
Fri May 13 04:34:30 UTC 2022
```

podが動いているノードを確認しましょう。
```
$ kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
statefulset-nfs-0                 1/1     Running   0          2m48s   10.244.1.3   test-worker2   <none>           <none>
```

test-worker2ですね。

私はKubernetes環境としてKind( https://kind.sigs.k8s.io/ )を使っているのですが、Kindのノードの正体はdockerコンテナなので、こんな感じでworkerノードを止めます。
```
$ docker stop test-worker2
```

test-worker2の状態がNotReadyになっていることが確認できました。

```
$ kubectl get nodes 
NAME                 STATUS     ROLES           AGE     VERSION
test-control-plane   Ready      control-plane   10m     v1.24.0-beta.0.113+7380fc735aca59-dirty
test-worker          Ready      <none>          9m30s   v1.24.0-beta.0.113+7380fc735aca59-dirty
test-worker2         NotReady   <none>          9m30s   v1.24.0-beta.0.113+7380fc735aca59-dirty
```

podの状態もTerminatingになり、そのまま他のノードにevictしません、、
```
$ kubectl get pods
NAME                              READY   STATUS        RESTARTS   AGE
statefulset-nfs-0                 1/1     Terminating   0          11m
```

ダウンしているtest-worker2ノードにout-of-service Taintを付与します。
```
$ kubectl taint nodes test-worker2 node.kubernetes.io/out-of-service=nodeshutdown:NoExecute
node/test-worker2 tainted
```

podがtest-workerノードに移動しました！
```
$ kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
statefulset-nfs-0                 1/1     Running   0          6s    10.244.2.4   test-worker   <none>           <none>
```
PVとPVCも確認してみましょう。STATUSが `Bound` になっていますね。
```
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS   REASON   AGE
pvc-e174d35e-0a83-4fbb-bf8f-28aa8ec8b7f8   10Gi       RWO            Delete           Bound    default/persistent-storage-statefulset-nfs-0   nfs-csi                  1d
```
```
$ kubectl get pvc
NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistent-storage-statefulset-nfs-0   Bound    pvc-e174d35e-0a83-4fbb-bf8f-28aa8ec8b7f8   10Gi       RWO            nfs-csi         1d
```

サンプルアプリの出力ファイルも見てみましょう。
```
$ tail /home/yuiko/foo/pvc-e174d35e-0a83-4fbb-bf8f-28aa8ec8b7f8/outfile
Fri May 13 05:15:38 UTC 2022
Fri May 13 05:15:39 UTC 2022
Fri May 13 05:15:40 UTC 2022
Fri May 13 05:21:56 UTC 2022 ←6分ほどファイル出力されていない
Fri May 13 05:21:57 UTC 2022
Fri May 13 05:21:58 UTC 2022
```
本来は1秒おきに現在時刻が出力されるはずですが、途中6分ほどファイル出力が行われていないことがわかります。

# 今後の予定
コミュニティでの議論の結果、ver1.24では手動でtaintをノードに付与することでノードからのボリュームデタッチが許可される、という方針で合意形成がされましたが、将来的にはこのtaintを自動で付与したいという話も出ています。

以下のKEPのセクションにこのように記載されています。
https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/2268-non-graceful-shutdown#goals
Once the feature in this KEP is developed and tested, we plan to work on automatic way of detecting and fencing node for shutdown/failure use cases and re-evaluating some approaches listed in the Alternatives section.

# まとめ
今回はKubernetes ver1.24で新しく実装された「non-graceful node shutdown」機能を紹介しました。長年コミュニティで問題視されていたので、今回のリリースで恩恵を受ける方はたくさんいらっしゃると思います！ガンガン使ってコミュニティにもフィードバックを投げましょう！

# おまけ
この件は、もともとは社内で改善要望が出たことがきっかけでコミュニティでの開発に参加したのですが、私が参加するだいぶ前から議論が行われており、他の企業でも改善要望が多かったことがうかがえます。Kubernetesを始めとする比較的大きいOSSコミュニティでは、新しい機能の実装を提案する場合はKEPと呼ばれる機能提案書のようなものを提出し、KEPが承認されれば開発に進むことができます。今回、KEPの承認に向けて議論はかなりの長期戦になり、とても大変でした、、。本件がsig-nodeとsig-storageの両方に関係するためステークホルダーが多いのもその原因かと思います。方向性の合意ができてKEPが無事承認された後は機能実装はトントン拍子に進み、ver1.24の開発期間に機能実装PRまで一気に完了しました！

# 参考
* Kubernetes Documentation https://kubernetes.io/docs/concepts/cluster-administration/node-shutdown/#non-graceful-node-shutdown
* Kubernetes blogでの紹介記事 https://kubernetes.io/blog/2022/05/20/kubernetes-1-24-non-graceful-node-shutdown-alpha/
* Kubernetes ver1.24リリースノート https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md
* NECのOSS/Linuxへの取り組み紹介記事 https://jpn.nec.com/oss/community/OSSJ2021/OSSJ2021-1.html