使用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のデータが破壊される可能性があります。この問題を回避するには、

    1. 将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。

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 相关的注释。
现有注释的说明在这里,但我们将使用以下注释。

annotation名設定値説明fencing/enabledtrueノードを kube-fencing の対象にするかどうか。既定値 truefencing/id各ノードの VM 名vboxmanage コマンドの引数に指定するVM名fencing/modetaintノード 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实现完全的自我修复是可行的。当前的非优雅节点关闭可以看作是实现这一目标的第一步。

广告
将在 10 秒后关闭
bannerAds