使用kube-fencing自动化K8s v1.24的非平缓节点关闭
这篇文章是三部系列的第三篇。前面的两篇如下。
-
- Kubespray+Vagrant で かんたん Kubernetes デプロイ
- K8s v1.24 の non-graceful node shutdown を試す
首先
我在上一个版本的K8s v1.24上手动给故障节点添加了node.kubernetes.io/out-of-service taint标记,确认了StatefulSet的PV附加Pod会被移动到其他节点。但是,为什么要手动操作呢?这样做并不能算作自动修复。
这是出于无奈的原因。例如,节点变为NotReady的原因有很多。
-
- ノードの電源が OFF になる
-
- ノードの OS がフリーズする
-
- ワーカーノード上の kubelet が止まる
-
- ワーカーノードとマスターノード間のネットワークが不通になる
- マスターノード上の apiserver のサーバ証明書の更新がワーカーノード上に反映されていない
以下に挙げたのはただの例ですが、他にもさまざまな理由が考えられます。ただし、これらの中にはPodが継続して稼働可能なケースもあります。そのため、k8s上でノードの状態がNotReadyになっているだけでは、ワーカーノード上のPodが本当に停止しているかどうかは分かりません。もし障害が発生したノード上でパフォーマンスに影響を与えないPodが稼働し続けており、PV(永続ボリューム)のデータを更新し続けている場合、そのまま放置したまま別のノードで同じPVを使用するPodを作成すると、PVのデータが破壊される可能性があります。この問題を回避するには、
-
- 将Kubernetes集群外部的运维监视系统创建一个机制,用于强制停止故障节点(或者由运维人员手动停止)。
- 类似Pacemaker的STONITH设备,实现Kubernetes自身强制停止(或完全隔离)故障节点的功能(Fencing)。
需要执行其中一种操作来彻底停止故障节点,并确保更新绝对不会发生。
在 KEP 2268: 非优雅节点关闭中,我们决定不实现对 Kubernetes 本身的围栏功能,而是将其委托给系统管理员或外部系统(=1)。此外,在 KEP 中提出了 Node Fencing (=2)作为替代方案,并在本 KEP 中解释了不采用此方法的原因。
这种方法作为一种树内解决方案似乎过于侵入性。由于这个 KEP 的范围被缩小到处理实际节点关机情景,我们不需要节点隔离的方法。我认为在这个 KEP 中所提出的方式也不会妨碍我们将来支持节点分区情况。在提议的解决方案实施之后,我们可以重新考虑这种方法。
因此,我在寻找代替Kubernetes中出现NotReady节点的Fencing OSS时,找到了以下选项。
kube-fencing
Pacemaker でも使われている fence-agents パッケージを使っています。物理サーバや各種ハイパーバイザ上の VM を強制的に停止・再起動できます。
node-fencing
由于个人认为 kube-fencing 看起来正合适,所以我在自己的账户上 fork 了 kube-fencing,并实现了在节点强制停止后附加 node.kubernetes.io/out-of-service taint 的功能。
安装
首先,我们将获取改造版的kube-fencing。
yosshy@nuc2:~$ git clone https://github.com/yosshy/kube-fencing.git
yosshy@nuc2:~$ cd kube-fencing
接下来,应用包含 kube-fencing 相关资源的清单。请注意,命名空间将设置为 kube-fencing,如果需要,请使用helm指定其他命名空间。
yosshy@nuc2:~/kube-fencing$ kubectl apply -f deploy/kube-fencing.yaml
namespace/kube-fencing created
serviceaccount/fencing-controller created
serviceaccount/fencing-switcher created
clusterrole.rbac.authorization.k8s.io/fencing-controller created
clusterrole.rbac.authorization.k8s.io/fencing-switcher created
clusterrolebinding.rbac.authorization.k8s.io/fencing-controller created
clusterrolebinding.rbac.authorization.k8s.io/fencing-switcher created
role.rbac.authorization.k8s.io/fencing-controller created
rolebinding.rbac.authorization.k8s.io/fencing-controller created
daemonset.apps/fencing-switcher created
deployment.apps/fencing-controller created
yosshy@nuc2:~/kube-fencing$
让我们来查看 kube-fencing-agent 容器的 PodTemplate 配置文件 deploy/examples/virtualbox.yaml。
apiVersion: v1
kind: PodTemplate
metadata:
name: fencing
template:
spec:
restartPolicy: OnFailure
containers:
- name: fence
image: ghcr.io/yosshy/kube-fencing/kube-fencing-agent:latest
command: ["fence_vbox", "-a", "172.18.8.1", "-l", "$(FENCING_USER)", "-p", "$(FENCING_PASSWORD)", "-n", "$(FENCING_ID)", "-o", "off"]
env:
- name: FENCING_ID
valueFrom:
fieldRef:
fieldPath: metadata.annotations['fencing/id']
- name: FENCING_USER
valueFrom:
secretKeyRef:
name: fencing-user
key: username
- name: FENCING_PASSWORD
valueFrom:
secretKeyRef:
name: fencing-user
key: password
kube-fencing 在 Fencing Job 容器中安装了 fence-agents 包中的各种 Fencing 命令,并设置在容器启动时执行其中一个命令。在此用于 VirtualBox 的 PodTemplate 中,将执行 fence_vbox 命令。
此外,我们在这里将主机的 IP 地址设定为 172.18.8.1。如有需要,请将其更改为合适的 IP 地址。
为了从 Fencing Job 容器远程登录到主机,我们将在后续的 secret 中指定帐户名和密码。
每个节点的不同虚拟机名称将在节点的 fencing/id 注释中指定。
Fencing 模式已设置为 off(强制停止)。如果将其设置为 reboot,虚拟机将重新启动,但在完全删除 Pod 之前,希望故障节点重新启动并变为 Ready 状态在安全性方面并不理想,因此我们暂时将其设置为 off(故障节点的停止和重新启动取决于系统的故障恢复策略)。
注册用于kube-fencing-agent容器的PodTemplate。
yosshy@nuc2:~/kube-fencing$ kubectl -n kube-fencing apply -f deploy/examples/virtualbox.yaml
podtemplate/fencing created
yosshy@nuc2:~/kube-fencing$
在 PodTemplate 中设置要使用的 secret。
username
ホスト上で vagrant up を実行したユーザのアカウント名password
同パスワードyosshy@nuc2:~/kube-fencing$ kubectl -n kube-fencing create secret generic fencing-user --from-literal='username=yosshy' --from-literal='password=P@ssw0rd'
secret/fencing-user created
yosshy@nuc2:~/kube-fencing$
我们将为每个工作节点注册与 kube-fencing 相关的注释。
现有注释的说明在这里,但我们将使用以下注释。
fencing/enabled
true
ノードを kube-fencing の対象にするかどうか。既定値 true
fencing/id
各ノードの VM 名vboxmanage コマンドの引数に指定するVM名fencing/mode
taint
ノード fencing 後の Pod, PV の扱い。将这些配置设置到 k8s-2、k8s-3 节点上。
yosshy@nuc2:~/kube-fencing$ vboxmanage list vms
"generic-ubuntu1804-virtualbox_1655701800174_67684" {7e7d8758-7362-41f7-9c04-21d09c879c44}
"kubespray_k8s-1_1665804234747_39746" {a3512cf1-2cd1-492b-9ec9-5b5bf517b583}
"kubespray_k8s-2_1665804309774_65270" {28725bfd-7387-4489-9b09-57c5b8522f10}
"kubespray_k8s-3_1665804382129_19421" {a9f75c2a-33b7-4a32-9b5e-f3a996ad3f2c}
yosshy@nuc2:~/kube-fencing$ kubectl annotate nodes k8s-2 fencing/id=kubespray_k8s-2_1665804309774_65270
node/k8s-2 annotated
yosshy@nuc2:~/kube-fencing$ kubectl annotate nodes k8s-2 fencing/mode=taint
node/k8s-2 annotated
yosshy@nuc2:~/kube-fencing$ kubectl annotate nodes k8s-3 fencing/id=kubespray_k8s-3_1665804382129_19421
node/k8s-3 annotated
yosshy@nuc2:~/kube-fencing$ kubectl annotate nodes k8s-3 fencing/mode=taint
node/k8s-3 annotated
yosshy@nuc2:~/kube-fencing$
准备工作已经完成。
我試著看看。
上次我们在节点上通过 VM 暂停来进行虚拟故障,但这次想要通过 kube-fencing 实现 VM 关闭,所以我们将尝试通过停止 kubelet 来模拟故障。
yosshy@nuc2:~/kube-fencing$ cd ~/csi-driver-nfs
yosshy@nuc2:~/csi-driver-nfs$ kubectl create ns kep2268
namespace/kep2268 created
yosshy@nuc2:~/csi-driver-nfs$ kubectl -n kep2268 apply -f deploy/example/statefulset.yaml
statefulset.apps/statefulset-nfs created
yosshy@nuc2:~/csi-driver-nfs$ kubectl get pods -n kep2268 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
statefulset-nfs-0 1/1 Running 0 64s 10.233.66.9 k8s-3 <none> <none>
statefulset-nfs-1 1/1 Running 0 58s 10.233.65.14 k8s-2 <none> <none>
statefulset-nfs-2 1/1 Running 0 51s 10.233.64.8 k8s-1 <none> <none>
statefulset-nfs-3 1/1 Running 0 44s 10.233.66.10 k8s-3 <none> <none>
statefulset-nfs-4 1/1 Running 0 38s 10.233.65.15 k8s-2 <none> <none>
statefulset-nfs-5 1/1 Running 0 33s 10.233.64.9 k8s-1 <none> <none>
statefulset-nfs-6 1/1 Running 0 27s 10.233.66.11 k8s-3 <none> <none>
statefulset-nfs-7 1/1 Running 0 20s 10.233.65.16 k8s-2 <none> <none>
yosshy@nuc2:~/csi-driver-nfs$
我决定停止K8S-3的kubelet。首先,我会确认节点的详细信息。
yosshy@nuc2:~/csi-driver-nfs$ kubectl describe node k8s-3
Name: k8s-3
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8s-3
kubernetes.io/os=linux
Annotations: csi.volume.kubernetes.io/nodeid: {"nfs.csi.k8s.io":"k8s-3"}
fencing/enabled: true
fencing/id: kubespray_k8s-3_1665804382129_19421
fencing/mode: taint
flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"aa:d2:74:8e:8f:7b"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 172.18.8.103
kubeadm.alpha.kubernetes.io/cri-socket: unix:////var/run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 15 Oct 2022 12:46:47 +0900
Taints: <none>
(中略)
打开另一个终端,使用该终端连接到 k8s-3 节点,并停止 kubelet 服务。
yosshy@nuc2:~$ cd kubespray
yosshy@nuc2:~/kubespray$ vagrant ssh k8s-3
Last login: Mon Oct 17 04:01:26 2022 from 10.0.2.2
-bash: warning: setlocale: LC_ALL: cannot change locale (ja_JP.UTF-8)
vagrant@k8s-3:~$ sudo systemctl stop kubelet
vagrant@k8s-3:~$ top
在最初的终端上监控k8s-3节点的状态变化。
yosshy@nuc2:~/csi-driver-nfs$ kubectl get nodes -w
NAME STATUS ROLES AGE VERSION
k8s-1 Ready control-plane 2d v1.24.6
k8s-2 Ready <none> 2d v1.24.6
k8s-3 Ready <none> 2d v1.24.6
(中略)
k8s-3 NotReady <none> 2d v1.24.6
k8s-3 NotReady <none> 2d v1.24.6
(^c で中断)
在节点状态变为NotReady后,立即检查k8s-3的详细信息(如下所示)。
由于节点停机,将自动附加节点上kubernetes.io/unreachable:NoExecute和kubernetes.io/unreachable:NoSchedule污点(符合Kubernetes先前的规范)。此外,kube-fencing-controller已添加fencing/state: started注解,表明已创建Fencing Job从PodTemplate中。
yosshy@nuc2:~/csi-driver-nfs$ watch kubectl describe node k8s-3
Every 2.0s: kubectl describe node k8s-3 nuc2: Mon Oct 17 13:03:47 2022
Name: k8s-3
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8s-3
kubernetes.io/os=linux
Annotations: csi.volume.kubernetes.io/nodeid: {"nfs.csi.k8s.io":"k8s-3"}
fencing/enabled: true
fencing/id: kubespray_k8s-3_1665804382129_19421
fencing/mode: taint
fencing/state: started
flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"02:eb:a3:af:b2:d1"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 172.18.8.103
kubeadm.alpha.kubernetes.io/cri-socket: unix:////var/run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 15 Oct 2022 12:46:47 +0900
Taints: node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unreachable:NoSchedule
Unschedulable: false
Lease:
当 Fencing Job 完成后,fencing/state 将变为 fenced(如下所示)。此时重新打开的终端上的 SSH 登录已经结束,返回到原始主机。
同时,通过改进的 kube-fencing-controller 自动添加了 node.kubernetes.io/out-of-service=nodeshutdown:NoExecute taint。
Name: k8s-3
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8s-3
kubernetes.io/os=linux
Annotations: csi.volume.kubernetes.io/nodeid: {"nfs.csi.k8s.io":"k8s-3"}
fencing/enabled: true
fencing/id: kubespray_k8s-3_1665804382129_19421
fencing/mode: taint
fencing/state: fenced
flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"02:eb:a3:af:b2:d1"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 172.18.8.103
kubeadm.alpha.kubernetes.io/cri-socket: unix:////var/run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 15 Oct 2022 12:46:47 +0900
Taints: node.kubernetes.io/out-of-service=nodeshutdown:NoExecute
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unreachable:NoSchedule
Unschedulable: false
Lease:
让我们确认一下 Pod 是否已经移动。
(^c で中断)
yosshy@nuc2:~/csi-driver-nfs$ kubectl get pods -n kep2268 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
statefulset-nfs-0 1/1 Running 0 12s 10.233.64.10 k8s-1 <none> <none>
statefulset-nfs-1 1/1 Running 0 5m53s 10.233.65.14 k8s-2 <none> <none>
statefulset-nfs-2 1/1 Running 0 5m46s 10.233.64.8 k8s-1 <none> <none>
statefulset-nfs-3 1/1 Running 0 9s 10.233.65.19 k8s-2 <none> <none>
statefulset-nfs-4 1/1 Running 0 5m33s 10.233.65.15 k8s-2 <none> <none>
statefulset-nfs-5 1/1 Running 0 5m28s 10.233.64.9 k8s-1 <none> <none>
statefulset-nfs-6 1/1 Running 0 5s 10.233.64.11 k8s-1 <none> <none>
statefulset-nfs-7 1/1 Running 0 5m15s 10.233.65.16 k8s-2 <none> <none>
yosshy@nuc2:~/csi-driver-nfs$
正在根据预期,k8s-3上的Pod正在移动到另一个节点上。
让我们来收拾残局吧。
yosshy@nuc2:~/csi-driver-nfs$ vboxmanage list vms
"generic-ubuntu1804-virtualbox_1655701800174_67684" {7e7d8758-7362-41f7-9c04-21d09c879c44}
"kubespray_k8s-1_1665804234747_39746" {a3512cf1-2cd1-492b-9ec9-5b5bf517b583}
"kubespray_k8s-2_1665804309774_65270" {28725bfd-7387-4489-9b09-57c5b8522f10}
"kubespray_k8s-3_1665804382129_19421" {a9f75c2a-33b7-4a32-9b5e-f3a996ad3f2c}
yosshy@nuc2:~/csi-driver-nfs$ vboxmanage startvm kubespray_k8s-3_1665804382129_19421 --type headless
Waiting for VM "kubespray_k8s-3_1665804382129_19421" to power on...
VM "kubespray_k8s-3_1665804382129_19421" has been successfully started.
yosshy@nuc2:~/csi-driver-nfs$ kubectl get nodes -w
NAME STATUS ROLES AGE VERSION
k8s-1 Ready control-plane 2d v1.24.6
k8s-2 Ready <none> 2d v1.24.6
k8s-3 NotReady <none> 2d v1.24.6
(中略)
k8s-3 Ready <none> 2d v1.24.6
k8s-3 Ready <none> 2d v1.24.6
(^C で中断)
yosshy@nuc2:~/csi-driver-nfs$ kubectl taint nodes k8s-3 node.kubernetes.io/out-of-service=nodeshutdown:NoExecute-
node/k8s-3 untainted
yosshy@nuc2:~/csi-driver-nfs$ kubectl describe node k8s-3
Name: k8s-3
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8s-3
kubernetes.io/os=linux
Annotations: csi.volume.kubernetes.io/nodeid: {"nfs.csi.k8s.io":"k8s-3"}
fencing/enabled: true
fencing/id: kubespray_k8s-3_1665804382129_19421
fencing/mode: taint
flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"aa:d2:74:8e:8f:7b"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 172.18.8.103
kubeadm.alpha.kubernetes.io/cri-socket: unix:////var/run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 15 Oct 2022 12:46:47 +0900
Taints: <none>
(中略)
yosshy@nuc2:~/csi-driver-nfs$ kubectl delete ns kep2268
namespace "kep2268" deleted
yosshy@nuc2:~/csi-driver-nfs$
汚染标记已完全删除,并且具备自动施加的fencing/state注释也被删除。现在恢复原样了。
总结
在本次非正常节点关闭过程中,管理员(或运营管理系统)被视为责任方。
-
- NotReady になった障害ノードが電源 OFF である事を保証
- 電源 OFF の障害ノードに node.kubernetes.io/out-of-service taint を付与
我们证明了通过改装版的kube-fencing可以实现对操作的自动化。但是,你觉得这个功能在哪些使用案例中是有用的呢?
如果在某个IaaS上存在Kubernetes集群,则作为IaaS的功能,检测、删除和重新创建故障节点VM相对比较容易,而且已经在AWS EKS等平台上实现了。在这种情况下,非优雅地关闭节点的方法实际上是不必要的。
另一方面,在不使用IaaS的本地环境、边缘计算的边缘部分、嵌入式应用等相对较小规模的服务器环境上的Kubernetes中,重新创建这些故障节点并不容易。在这种情况下,目前需要利用像kube-fencing这样的增强解决方案实现Kubernetes集群故障节点的自我修复。
此外,Kubernetes本身可以实现类似kube-fencing的功能,从而以单独的Kubernetes实现完全的自我修复是可行的。当前的非优雅节点关闭可以看作是实现这一目标的第一步。