$ kubectl get po -o wide
...
sleep-6f7c8cc954-5bzsk ... 10.10.11.21
登录该节点,停止 Kubelet 制造一个 NotReady。使用 watch kubectl get nodes,pods 命令持续观察,会发现如前所述,首先节点进入 NotReady 状态,几分钟之后,Pod 发生如下变化:
$ kubectl get pods
sleep-6f7c8cc954-pqjj6 0/1 ContainerCreating 0 41s
sleep-6f7c8cc954-rcpnc 1/1 Terminating 0 8m44s
原有 Pod 进入 Terminating 状态,新创建了一个 Pod,但是新 Pod 会持续处于 ContainerCreating 状态,查看这个 Pod 的状态:
$ kubectl desribe po sleep-6f7c8cc954-pqjj6
...
Multi-Attach error for volume "pvc-2de7d17c-04c6-11eb-b22b-5254002d96de" Volume is already used by pod(s) sleep-6f7c8cc954-rcpnc
...
可以看到因为存储卷是独占的,导致 Pod 无法成功创建。是不是删除 Pod 就能解决了呢?因为节点不可用,删除是无效的,因此这里需要强行删除:
$ kubectl delete po sleep-6f7c8cc954-rcpnc --force --grace-period=0
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "sleep-6f7c8cc954-rcpnc" force deleted
然而 Pod 仍然无法创建,错误原因:
$ kubectl describe po sleep-6f7c8cc954-fhl8c
Warning FailedAttachVolume 18s attachdetach-controller Multi-Attach error for volume "pvc-2de7d17c-04c6-11eb-b22b-5254002d96de" Volume is already exclusively attached to one node and can't be attached to another
出现另一个错误,PV 已经被绑定到不可用节点。
ReadWriteOnce
resources:
requests:
storage: 1Gi
volumeName: pvc-manual
把 Deployment 也创建起来,使用新的 PVC,发现 Pod 保持在 ContainerCreating 状态,查看 Pod 信息会看到:
$ kubectl describe po sleep-6f7c8cc954-5hptw
Warning FailedMount 62s (x2 over 112s) kubelet, 10.10.11.22 MountVolume.WaitForAttach failed for volume "pvc-manual" : rbd image k8s/kubernetes-dynamic-pvc-3498797d-04c6-11eb-b6b6-4e0deb79a72b is still being used
Warning FailedMount 24s (x2 over 2m41s) kubelet, 10.10.11.22 Unable to mount volumes for pod "sleep-6f7c8cc954-5hptw_default(9d6caec9-04d1-11eb-afd2-525400c74ddd)": timeout expired waiting for volumes to attach or mount for pod "default"/"sleep-6f7c8cc954-5hptw". list of unmounted volumes=[pvc1]. list of unattached volumes=[pvc1 default-token-97tqr]
此处信息表明,RBD 镜像被占用,接下来我们去故障节点解除这个占用。
for dev in dev_list.keys():
docker_str = dev_list[dev]
for docker_process in docker_list:
if not docker_str in docker_process:
continue
docker_id = docker_process.split(" ")[0]
print "Dev: {}\tDocker ID: {}\n".format([dev, docker_id])
上面的脚本功能很简单,使用 mount 命令列出所有加载卷,然后过滤出 /dev/rbd\d+ 的加载,并识别其中是否符合 Pod 加载的特征,最终会用 容器 ID: 设备名称 的格式输出结果。
在 Kubernetes 节点发生故障时,在 40 秒内(由 Controller Manager 的 --node-monitor-grace-period 参数指定),节点进入 NotReady 状态,经过 5 分钟(由 --pod-eviction-timeout 参数指定),Master 会开始尝试删除故障节点上的 Pod,然而由于节点已经失控,这些 Pod 会持续处于 Terminating 状态。
一旦 Pod 带有一个独占卷,例如我现在使用的 Ceph RBD 卷,情况就会变得更加尴尬:RBD 卷被绑定在故障节点上,PV 映射到这个镜像,PVC 是独占的,无法绑定到新的 Pod,因此该 Pod 无法正确运行。要让这个 Pod 在别的节点上正常运行,需要用合适的路线重新建立 RBD Image 到 PV 到 PVC 的联系。
备份 大家都很清楚,数据相关的操作是高危操作,因此下面的任何步骤执行之前,首先要进行的就是备份。备份操作同样也需要沿着 RBD->PV->PVC 的线路完整进行。
kubectl get pvc,会输出 PVC 绑定的 PV,将 PV 和 PVC 的 YAML 都进行导出备份。 kubectl get pv -o yaml,其中的 spec.rbd.image 字段会指明对应的 RBD Image。使用 RBD 相关命令对 RBD Image 进行备份。 节点主机可用 有些情况下,节点作为 Kubernetes Node 的功能无法正常工作,但是节点本身是可用的,例如无法连接到 API Server 的情况。例如下面的工作负载:
apiVersion: apps/v1 kind: Deployment metadata: labels: app: sleep version: v1 name: sleep spec: selector: matchLabels: app: sleep version: v1 template: metadata: labels: app: sleep version: v1 spec: containers:
name: pvc1 persistentVolumeClaim: claimName: claim1
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claim1 spec: accessModes:
$ kubectl get po -o wide ... sleep-6f7c8cc954-5bzsk ... 10.10.11.21 登录该节点,停止 Kubelet 制造一个 NotReady。使用 watch kubectl get nodes,pods 命令持续观察,会发现如前所述,首先节点进入 NotReady 状态,几分钟之后,Pod 发生如下变化:
$ kubectl get pods sleep-6f7c8cc954-pqjj6 0/1 ContainerCreating 0 41s sleep-6f7c8cc954-rcpnc 1/1 Terminating 0 8m44s 原有 Pod 进入 Terminating 状态,新创建了一个 Pod,但是新 Pod 会持续处于 ContainerCreating 状态,查看这个 Pod 的状态:
$ kubectl desribe po sleep-6f7c8cc954-pqjj6 ... Multi-Attach error for volume "pvc-2de7d17c-04c6-11eb-b22b-5254002d96de" Volume is already used by pod(s) sleep-6f7c8cc954-rcpnc ... 可以看到因为存储卷是独占的,导致 Pod 无法成功创建。是不是删除 Pod 就能解决了呢?因为节点不可用,删除是无效的,因此这里需要强行删除:
$ kubectl delete po sleep-6f7c8cc954-rcpnc --force --grace-period=0 warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely. pod "sleep-6f7c8cc954-rcpnc" force deleted 然而 Pod 仍然无法创建,错误原因:
$ kubectl describe po sleep-6f7c8cc954-fhl8c Warning FailedAttachVolume 18s attachdetach-controller Multi-Attach error for volume "pvc-2de7d17c-04c6-11eb-b22b-5254002d96de" Volume is already exclusively attached to one node and can't be attached to another 出现另一个错误,PV 已经被绑定到不可用节点。
要解决这个问题,可以使用现有 PV 的 YAML 新建一个 PV,强制指向原有的 RBD Image:
apiVersion: v1 kind: PersistentVolume metadata: name: pvc-manual spec: accessModes:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claim1 spec: accessModes:
$ kubectl describe po sleep-6f7c8cc954-5hptw Warning FailedMount 62s (x2 over 112s) kubelet, 10.10.11.22 MountVolume.WaitForAttach failed for volume "pvc-manual" : rbd image k8s/kubernetes-dynamic-pvc-3498797d-04c6-11eb-b6b6-4e0deb79a72b is still being used Warning FailedMount 24s (x2 over 2m41s) kubelet, 10.10.11.22 Unable to mount volumes for pod "sleep-6f7c8cc954-5hptw_default(9d6caec9-04d1-11eb-afd2-525400c74ddd)": timeout expired waiting for volumes to attach or mount for pod "default"/"sleep-6f7c8cc954-5hptw". list of unmounted volumes=[pvc1]. list of unattached volumes=[pvc1 default-token-97tqr] 此处信息表明,RBD 镜像被占用,接下来我们去故障节点解除这个占用。
首先我们要查找绑定了这一镜像的容器,可以用如下脚本实现:
!/bin/env python2
import subprocess import re
print("Searching for docker instances mounting rbds") mount_list = subprocess.check_output("mount") dev_list = {} mount_list = mount_list.split("\n") regex = r"^(\/dev\/rbd\d+)\son\s.?\/pods\/([0-9a-z-]+)\/volumes.?$"
for mount_line in mount_list: mat = re.search(regex,mount_line) if mat is None: continue dev_list[mat.group(1)] = mat.group(2)
docker_list = subprocess.check_output(["docker", "ps"]) docker_list = docker_list.split("\n")
for dev in dev_list.keys(): docker_str = dev_list[dev] for docker_process in docker_list: if not docker_str in docker_process: continue docker_id = docker_process.split(" ")[0] print "Dev: {}\tDocker ID: {}\n".format([dev, docker_id]) 上面的脚本功能很简单,使用 mount 命令列出所有加载卷,然后过滤出 /dev/rbd\d+ 的加载,并识别其中是否符合 Pod 加载的特征,最终会用 容器 ID: 设备名称 的格式输出结果。
$ python2 show-rbd.py Searching for docker instances mounting rbds Dev: /dev/rbd0 Docker ID: 033b1185008c Dev: /dev/rbd0 Docker ID: b716592e5aae 停止并删除其中的容器,并调用 umount /dev/rbd0 卸载卷。最后使用 rbd unmap /dev/rbd0 命令解除关联。再次创建 Pod,会发现 Pod 成功运行。
节点主机不可用 这种情况和前面类似,但是需要在 Ceph 服务端断开关系。
首先查看对应镜像的状态:
$ rbd status kubernetes-dynamic-pvc-fa69dfa7-04d4-11eb-b6b6-4e0deb79a72b -p k8s Watchers: watcher=10.10.11.23:0/4208975345 client.364378 cookie=18446462598732840961 这里看到其中的关联关系。将对应 watcher 拉黑:
$ ceph osd blacklist add 10.10.11.23:0/4208975345 blacklisting 10.10.11.23:0/4208975345 until 2020-10-02T18:37:00.985286+0000 (3600 sec) 后记 整个过程中会涉及到多次删除、覆盖等操作,稍有差池都会导致重要损失,此处描述的步骤也难免有些疏漏,因此务必做好备份工作,这样即使是 RBD 镜像丢失,也可以通过重建 PV 的方式恢复服务。
别问我为啥用 Deployment 跑有状态应用。。
-- 引用 https://blog.fleeto.us/post/unbound-rbd-from-a-notready-node/