#初めに
kubernetes 1.21 が 2021/4/8 にリリースされました。
その新機能の概要は Kubernetes 1.21: Power to the Community に記載されています。
その中で、 Graceful Node Shutdown がちょっと気なったので、試してみました。
Release note や kubernetes docs などをベースにやってみた内容です。
そのため、もし理解違いなどがあればコメント等いただければありがたいです。
可能な範囲で、ソースコードも確認し、Markdown の注釈でリンクを張っています。
必要に応じてご参照ください。
#用意するもの
- kubernetes 1.21 クラスター (Worker Node 2台以上(shutdown するため))
#環境
KVM 環境上に 4つの VM を立て、 1 Controller / 3 Worker 構成の kubernetes クラスターを構成しています。
主に確認した環境は以下の通りです。
- KVM ホスト
- CentOS Linux release 8.2.2004 (Core)
- Controller / Node ( 1 Contoller / 3 Worker 構成 )
- OS: 下表参照
- containerd 1.4.4
- calico 3.19
- kubernetes 1.21
また、ノードの OS を変えて各種確認を行っているのですが、その OS 情報は以下の通りです。
略称 | バージョン | kernel バージョン | systemd バージョン |
---|---|---|---|
CentOS 8 | 8.2.2004 | 4.18.0-193 | 239-29 |
CentOS 7 | 7.9.2009 | 3.10.0-1160.24.1 | 219-78 |
RHEL 8 | 8.1 | 4.18.0-147 | 239-18 |
RHEL 7 | 7.9 | 3.10.0-1160 | 219-78 |
ubuntu21 | 21.04 | 5.11.0-16-generic | 247.3-3ubuntu3 |
ubuntu20 | 20.04 LTS | 5.4.0-72-generic | 245.4-4ubuntu3 |
#サマリー
まず、そもそも想定通りに動かないケースがある、という事象が発生しています。
現時点で「この環境なら全く問題なく稼働する。」というケースが無く、どの環境も何かしら問題点がありました。
もしうまくいっている方がいらっしゃれば、コメント等でアドバイスいただければ助かります。
- CentOS / RHEL は、 Graceful Node Shutdown はうまく動くが、他の問題があり結局使えない
- Ubunts 20, 21 は、 そもそも Graceful Node Shutdown がうまく動かない
完全に解決してから記事にしたかったのですが、時間がかかりそうなので取り急ぎ事実をまとめました。
詳細は後述しますが、Graceful Node Shutdown は、 kubelet が systemd-logind (以下 logind )の inhibitor-lock を取得し、shutdown のシグナル( PrepareForShutdown )が発生した際に shutdown を遅らせる、という仕組みです。
各OS で確認したところ、以下のようになりました。
ワーカー OS | 概要 |
---|---|
CentOS 8 | GracefulNodeShutdown自体は動くが、そもそも iptables 関連で kubernetes 自体が正しく稼働しない。(コントローラー再起動後、ワーカーが NotReady になる) |
RHEL 8 | 同上 |
CentOS 7 | inhibitor-lock は取得されるが、poweroff 時に PrepareForShutdown シグナルが発行されない。 PrepareForShutdown シグナルをコマンドで発行した場合は kubelet は正しく応答する(Graceful Node Shutdown される) |
RHEL 7 | 同上 |
ubuntu20 | inhibitor-lock 自体が取得されない(Graceful Node Shutdown しない) |
ubuntu21 | inhibitor-lock 自体が取得されない(Graceful Node Shutdown しない) |
Graceful Node Shutdown が有効な場合、ノードを shutdown させると、該当のノード上の Pod の Status が Shutdown
となり他のノードで必要数の Pod が立ち上がりました。
なお、Shutdown
状態の Pod は5分間残り続けるため、その間は kubectl get pods
で確認すると ReplicaSet のPod 数指定よりも多く見えます。
うまくいっていない環境については、OS 上の設定等で何らかの対応が必要だと思いますが
現時点ではそこまでの調査ができていない状況です。
#Graceful Node Shutdown の仕組みの概要
Graceful Node Shutdown の機能は、 kubernetes 1.20 で アルファ、 kubernetes 1.21 でベータとなっており
ベータリリースされた機能はデフォルトで有効になっています。ただし設定上は 0秒指定になっており
事実上無効状態です。
Graceful Node Shutdown の動作の概要は下図のようになります。
- kubelet の Graceful Node Shutdown 関連設定(
config.yaml
)を変更し、kubelet を再起動する - kubelet が設定に基づき、logind の
InhibitDelayMaxSec
を上書きする1 - kubelet が logind の
inhibitor-lock
を取得する2 - ユーザー操作/障害等での停止に伴い、D-Bus経由で送信される
PrepareForShutdown
シグナルを受信する3 - kubelet は Pod を Graceful Shutdown させる4 (その後 inhibitor-lock を解除する5)
- logind は inhibitor-lock 解消または指定時間経過後に shutdown を行う
[inhibitor-lock][inhibitor-lock] 補足(セクション内参照)
systemd 183 以降で実装されている、システムのシャットダウンやスリープを制御する仕組みです。 アプリケーション側からシャットダウンやスリープを遅らせたり、させないようにしたりすることができます。 最近の Linux では、 シャットダウンなどは logind が制御しており、D-Bus経由で logind と連携することにより間接的にシャットダウンを制御できます。 アプリケーション(今回はkubelet)は logind の Manager インターフェース(org.freedesktop.login1.Manager.Inhibit)を call し、inhibitor-lock を取得します。 inhibitor-lock には、図中にもある通り以下の4つのパラメータがあります。 ・ What : イベント(shutdown 等) ・ Who : ロックを取得するプロセス情報 ・ Why : ロック取得理由概要 ・ Mode : delay or block ・ delay : 指定秒数遅らせる ・ block : ロック解除まで待たせる なお、 Mode : block を指定すると、ロックしたプロセスがロックを解除するまでイベントが実行されませんので注意が必要です。多くの場合は delay を指定します。 delay の場合、inhibitor-lock が解除されるか、`InhibitDelayMaxSec` (デフォルト 5秒)が経過するまで logind はイベントの実行を遅らせます。 `InhibitDelayMaxSec` 自体は logind の設定です。 ( /etc/systemd/logind.conf 参照 ) kubelet は kubelet の `config.yaml` 設定に従い、 `/etc/systemd/logind.conf.d/99-kubelet.conf` を作成し、その中で `InhibitDelayMaxSec` を上書きします。[^99-kubelet.conf]kubelet の設定
- 設定対象ノードの kubelet の設定(
/var/lib/kubelet/config.yaml
)で下表の2つのパラメータを指定する - kubelet の再起動 (
systemctl stop kubelet ; systemctl start kubelet
)
パラメータ | 今回の設定値 | デフォルト | 概要 |
---|---|---|---|
shutdownGracePeriod | 60s | 0s | shutdown 開始までの総待機秒数 |
shutdownGracePeriodCriticalPods | 30s | 0s | Critical Pod 停止用の待機秒数 |
※kubelet の設定についての補足(セクション内参照)
- shutdownGracePeriod 内に shutdownGracePeriodCriticalPods 秒数が含まれます
- shutdownGracePeriod = RegularPod の待機秒数(nonCriticalPodGracePeriod) + CriticalPod の待機秒数(CriticalPodGracePeriod)です 6
- [Critical Pod][CriticalPod]
- Podの [PriorityClass] に指定する優先度設定(32bit Integer が有効値(最大2,147,483,647)、ユーザー設定は 10億以下の整数。大きいほど優先される)。デフォルトで以下の2つが設定されている。このどちらかが設定されている Pod が Critical Pod として扱われ、優先的にスケジュールされます
- system-cluster-critical : 2000000000 (20億)
- system-node-critical : 2000001000 (20億1000)
- Podの [PriorityClass] に指定する優先度設定(32bit Integer が有効値(最大2,147,483,647)、ユーザー設定は 10億以下の整数。大きいほど優先される)。デフォルトで以下の2つが設定されている。このどちらかが設定されている Pod が Critical Pod として扱われ、優先的にスケジュールされます
- Pod の GracefulShutdown パラメータ
pod.Spec.TerminationGracePeriodSeconds
が、nonCriticalPodGracePeriod
またはCriticalPodGracePeriod
以下の場合、pod.Spec.TreminationGracePeriodSeconds
の値が利用されます。7 - kubelet の設定ファイルは kubeadm で構築した場合は
/etc/kubernetes/kubelet.conf
です-
systemctl cat kubelet
等で確認できます
-
-
shutdown -h now
では動作しません(詳細は [KEP-2000] を参照ください)
#確認内容
Graceful Node Shutdown に関して、うまく動かない状況のため、
ワーカーノードを各種 OS に変更してみて、以下の項目を確認しました。
- kubelet の設定を変更した後、 inhibitor-lock が取得されるか?
- 各種停止、再起動コマンド実施時に PrepareForShutdown シグナルが D-Bus 経由で送付されるか?
- inhibitor-lock 取得状態で、コマンドで PrepareForShutdown シグナルを送り、kubelet が応答するか?
#実施ケース&結果
1. kubelet 設定変更後、 inhibitor-lock が取得されるか?
inhibitor-lock が取得されたかどうかは systemd-inhibit --list
で確認することができます。
# systemd-inhibit --list
Who: kubelet (UID 0/root, PID 918/kubelet)
What: shutdown
Why: Kubelet needs time to handle node shutdown
Mode: delay
※ubuntu では見え方が異なります
kubelet が shutdown イベント時に Mode: delay で inhibitor-lock を取得しています。8
各ワーカー OS について、 Minimum インストールの際に inhibitor-lock が取得されたかどうかを下表に記載します。
ワーカーOS | inhibitor-lock 取得 |
---|---|
CentOS 8 | 取得 |
CentOS 7 | 取得 |
RHEL 8 | 取得 |
RHEL 7 | 取得 |
ubuntu21 | 取得せず |
ubuntu20 | 取得せず |
2. PrepareForShutdown シグナルが送付されるか?
2点目の、「各種停止、再起動コマンド実施時に PrepareForShutdown シグナルが D-Bus 経由で送付されるか?」に関してです。
シグナルが送付されたかどうかは、D-Bus をモニターすることにより確認できます。
ターミナル上で、dbus-monitor
コマンドを利用することにより確認できます。
下記の例では、 PrepareForShutdown
が true
で送信されていることがわかります。
# dbus-monitor --system "type='signal',interface='org.freedesktop.login1.Manager'"
... (略) ...
signal time=1620471576.944039 sender=:1.12 -> destination=(null destination) serial=171 path=/org/freedesktop/login1; interface=org.freedesktop.login1.Manager; member=PrepareForShutdown
boolean true
上記の例は、 CentOS 8 で、 1つのターミナルで dbus-monitor
でモニターをしながら、他のターミナルで、 systemctl poweroff
を行って停止した際のシグナルです。
PrepareForShutdown (true)
シグナルが出ていることがわかります。
また、以下のコマンドで手動でシグナルを送信することも可能です。(実際にシャットダウンはしません)
kubelet は PrepareForShutdown : true
を受け取ると、inhibitor-lock を開放し、 false
を受け取ると inhibitor-lock を取得します。
# gdbus emit --system --object-path /org/freedesktop/login1 --signal org.freedesktop.login1.Manager.PrepareForShutdown true
下表は、各種コマンド発行時に PrepareForShutdown (true)
シグナルが出るかどうかを確認した結果です。
(systemctl や shutdown コマンドの仕様を丁寧に調べたらここまで確認する必要はなさそうな気もします。。。)
○:シグナル送付、×:シグナルなし
ワーカーOS | systemctl poweroff | systemctl reboot | shutdown -h now | shutdown -r now | halt -p -f | virsh destroy | virsh shutdown | gdbus(手動) |
---|---|---|---|---|---|---|---|---|
CentOS 8 | ○ | ○ | × | × | ×(*1) | ×(*1) | ○ | ○ |
CentOS 7 | × | × | × | × | ×(*1) | ×(*1) | × | ○ |
RHEL 8 | ○ | ○ | × | × | ×(*1) | ×(*1) | ○ | ○ |
RHEL 7 | × | × | × | × | ×(*1) | ×(*1) | × | ○ |
ubuntu21 | ○ | ○ | × | × | ×(*1) | ×(*1) | ○ | ○ |
ubuntu20 | ○ | ○ | × | × | ×(*1) | ×(*1) | ○ | ○ |
※当環境は KVM 上の VM ですので、 KVM ホスト側からの強制停止 virsh destroy
、virsh shutdown
も試してみています。
(*1) : ターミナル自体がハングしたため、ターミナル上に表示されていないだけの可能性あり(ファイル出力していても出力されていなかったため ×としています) halt のマニュアル上 -f
を1回指定した場合は system manager と連携しクリーンな shutdown が行われるという記載のため、PrepareForShutdown シグナルが出てもよいのでは、と思っていましたが、実際は出ていないようです。
2021/05/30 追記 --
@hodahgi さんにアドバイスいただきました。ありがとうございます!
shutdown コマンド実行時に PrepareForShutdown が出ないのは、 now
を引数にしているからで、
shutdown --r +1
など、遅延指定の場合は PrepareForShutdown が出るのではないか、という話でした。
下記のリンク先にも記載があります。
これについても、見てはいたのですが、さっと確認してダメだったので放置していましたが
特定の環境で出なかっただけだったようです。
アドバイスをいただき改めて確認してみました。
結果としては RHEL 7 / CentOS 7 では PrepareForShutdown が出ませんでしたが、
その他の環境では PrepareForShutdown シグナルが発生しました。
ワーカーOS | shutdown --r +1 |
---|---|
CentOS 8 | ○ |
CentOS 7 | × |
RHEL 8 | ○ |
RHEL 7 | × |
ubuntu21 | ○ |
ubuntu20 | ○ |
3.inhibitor-lock 取得状態で、コマンドで PrepareForShutdown シグナルを送り、kubelet が応答するか?
このケースに関しては、 kubelet の設定変更を行った際に、 inhibitor-lock が取得されることが前提のため、検証の対象は CentOS 7,8, RHEL 7,8 が対象になります。
このケースが、実際に Graceful Node Shutdown できるかどうか、というケースになります。
ワーカーOS | inhibitor-lock | Graceful Node Shutdown | 備考 |
---|---|---|---|
CentOS 8 | 取得済み | Pod が Shutdown に遷移 | systemctl poweroff でシグナル生成 |
CentOS 7 | 取得済み | Pod が Shutdown に遷移 | gdbus コマンドでシグナル生成 |
RHEL 8 | 取得済み | Pod が Shutdown に遷移 | systemctl poweroff でシグナル生成 |
RHEL 7 | 取得済み | Pod が Shutdown に遷移 | gdbus コマンドでシグナル生成 |
Graceful Node Shutdown が発生すると、下記のように Pod の STATUS が Shutdown
になります。
Node の STATUS は NotReady
になり、 Pod の STATUS は Shutdown
になっています。そして、 Shutdown
になった数と同じ数の Pod が ContainerCreating になっています。 ( 下のログは --replicas=6
のケース )
また、デフォルトでは、 Shutdown
状態の Pod は 5分経過後に Terminating を経て削除されます。
# kubectl get nodes,pods -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node/k8sctl20 Ready control-plane,master 120m v1.21.1 192.168.199.220 <none> Red Hat Enterprise Linux 3.10.0-1160.el7.x86_64 containerd://1.4.6
node/k8swkr20 NotReady <none> 118m v1.21.1 192.168.199.230 <none> Red Hat Enterprise Linux 3.10.0-1160.el7.x86_64 containerd://1.4.6
node/k8swkr21 Ready <none> 118m v1.21.1 192.168.199.231 <none> Red Hat Enterprise Linux 3.10.0-1160.el7.x86_64 containerd://1.4.6
node/k8swkr22 Ready <none> 118m v1.21.1 192.168.199.232 <none> Red Hat Enterprise Linux 3.10.0-1160.el7.x86_64 containerd://1.4.6
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/graceful-nginx-6cf96bc766-2wpn2 1/1 Running 0 7m23s 10.200.149.75 k8swkr22 <none> <none>
pod/graceful-nginx-6cf96bc766-9vb85 1/1 Running 0 7m23s 10.200.211.205 k8swkr21 <none> <none>
pod/graceful-nginx-6cf96bc766-d4ql9 1/1 Running 0 7m23s 10.200.149.76 k8swkr22 <none> <none>
pod/graceful-nginx-6cf96bc766-j6w9x 1/1 Running 0 7m23s 10.200.211.204 k8swkr21 <none> <none>
pod/graceful-nginx-6cf96bc766-82jn5 0/1 Shutdown 0 7m23s <none> k8swkr20 <none> <none>
pod/graceful-nginx-6cf96bc766-nmr5b 0/1 Shutdown 0 7m23s <none> k8swkr20 <none> <none>
pod/graceful-nginx-6cf96bc766-kcqlr 0/1 ContainerCreating 0 4s <none> k8swkr22 <none> <none>
pod/graceful-nginx-6cf96bc766-xlhmc 0/1 ContainerCreating 0 4s <none> k8swkr21 <none> <none>
なお、 Graceful Node Shutdown 設定なしで、 いきなり Node を shutdown -r now
などとすると、
Pod は Ready 状態のままになり、 Node 起動後、 Unknown ステータスとなります。
その後 Pod が再起動(起動?)されて Running になりました。
( Pod については Terminating ステータスのままで止まるケースもあるようです)
#終わりに
GracefulNodeShutdown を軽く試してみるつもりが、かなりはまってしまいいろいろと検証しました。
logind の inhibitor-lockの取得、PrepareForShutdownシグナルを受けて Pod の Shutdown といった
一連の流れをパーツごとに確認することができました。
検証前は、もっと Pod の Graceful Shutdown と密接に関連しているだろうと思っていたのですが、
nonCriticalPodGracePeriod や CriticalPodGracePeriod と pod.Spec.TreminationGracePeriodSeconds の短い方が使われる、というぐらいでしか関連していないのが意外でした。あえて疎な関係にしているのかもしれません。
ubuntu に関しては、 おそらく inhibitor-lock するようにする設定があり、
それを設定すれば正しく動くのだと思いますがそこまでたどり着けませんでした。
kubernetes だけの知識ではだめで Linux の知識も必要だなと痛感しました。
結局筆者の環境では問題ない形では動かすことができなかったのが残念ではありますが、良い勉強になりました。
また 他の機能についても試してみたいと思います。
参考)
■Graceful Node Shutdown
https://kubernetes.io/docs/concepts/architecture/nodes/#graceful-node-shutdown
■Graceful Node Shutdown Goes Beta
https://kubernetes.io/blog/2021/04/21/graceful-node-shutdown-beta/#:~:text=With%20graceful%20node%20shutdown%2C%20the,any%20resources%20they%20are%20holding.
■KEP-2000: Graceful Node Shutdown
https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2000-graceful-node-shutdown
■Pod Lifecycle
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
■Kubernetes: 詳解 Pods の終了
https://qiita.com/superbrothers/items/3ac78daba3560ea406b2
■ inhibitor-lock
https://www.freedesktop.org/wiki/Software/systemd/inhibit/
■ inhibitor-lock deep dive
https://trstringer.com/systemd-inhibitor-locks/
■kubernetes/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go
https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go
以下、ソースリンク
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go#L166-L187 ↩
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go#L72-L93 ↩
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go#L125-L158 ↩
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go#L221 ↩
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go#L269-L278 ↩
-
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go#L225
[CriticalPod]: https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical
[PriorityClass]: https://kubernetes.io/ja/docs/concepts/configuration/pod-priority-preemption/#priorityclass ↩ -
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/nodeshutdown_manager_linux.go#L241-L244
[KEP-2000]: https://github.com/kubernetes/enhancements/tree/masjter/keps/sig-node/2000-graceful-node-shutdown ↩ -
https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go#L74-L81 ↩