Skip to main content

Command Palette

Search for a command to run...

释放 Kubernetes 故障节点上的 RBD 卷

Updated
3 min read

在 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:
      - image: something/nginx:0.1
        imagePullPolicy: Always
        name: sleep
        volumeMounts:
          - name: pvc1
            mountPath: /data
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      volumes:
      - name: pvc1
        persistentVolumeClaim:
          claimName: claim1
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

提交到集群后,会创建一个 Deployment 和 PVC,查看一下所在节点:

$ 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:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Delete
  rbd:
    fsType: ext4
    image: kubernetes-dynamic-pvc-3498797d-04c6-11eb-b6b6-4e0deb79a72b
    keyring: /etc/ceph/keyring
    monitors:
    - 10.10.11.11:6789
    - 10.10.11.12:6789
    - 10.10.11.13:6789
    pool: k8s
    secretRef:
      name: ceph-secret
      namespace: ceph
    user: admin
  storageClassName: rbd
  volumeMode: Filesystem

会创建一个新的 PV,状态为 Available。接下来就创建一个新的 PVC,指向新创建的 PV:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
  - 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 镜像被占用,接下来我们去故障节点解除这个占用。

首先我们要查找绑定了这一镜像的容器,可以用如下脚本实现:

#!/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 跑有状态应用。。

More from this blog

龙虾恐慌:AIOps 又要改名了?

ChatGPT 开始,把 AI 拉近到普罗大众的面前,让无数人感受到 AI 的亲民魅力。而龙虾,则把大模型驱动的自动化能力,突然间变得水灵灵、活泼泼地走进千家万户。它不只是“风口上的猪”,而是风口本身。热度高到让 Mac mini 一度断货,不知道这在不在库克的预料之内。 每代人都有每代人的鸡蛋,春节期间,我就领了我的鸡蛋。翻出古老的 MacBook Air M1,充值各种大模型。当然了,这个工具

Mar 9, 20261 min read

再见 2025

我猜不少人以为这个号废了吧?并没有,只是今年变化有点大,一直有种抄起键盘,无从说起的感觉,所以一直偷懒到今天,2025 的最后一天。 今年是我的第四个本命年,去年末一期播客里,大内说本命年不是灾年,是变化年,有危也有机。可是讲真啊,只看到危,没看到机。 各种因缘际会,从鹅厂跳槽到前东家,已经接近四年,第一个合同期已经进入尾声。除了前两年还在云原生领域嗷嗷叫,后两年基本都是些鸡零狗碎的东西了,用老东家的术语说是——偏离主航道,可谓是前景暗淡了。 一旦确定要滚蛋,反倒心思轻松起来,每天骑着我的小红车...

Jan 5, 20261 min read

辅助编程?dora 说:我知道你很急可是请你别急

从 OpenGPT 把大模型的火烧旺了之后,这三年来,相信很多组织或摩拳擦掌、或躬身入局,希望借助聪明能干的大模型,或想偿还技术宅,或想降本增效,或想弯道超车。一时间,沉寂许久的 AIxx 又活过来了,LLM Ops、Vibe Coding、中医大模型、GPT 算命等等,全都老树发新芽,焕发了勃勃生机。那么视角拉回从业者最关注的饭碗相关的领域之一——AI 辅助开发,产生了什么触动,应该如何拥抱呢? DORA 的年度报告中给出了很有意思的结论——强者恒强。 执行摘要部分总结了几个有趣的点: 问题...

Oct 6, 20251 min read

[译]dora:ai 辅助软件开发状态报告

执行摘要 在 2025 年,科技领导者面临的核心问题已不再是“是否要采用 AI”,而是“如何实现其价值”。 DORA 的研究基于超过 100 小时的定性访谈和来自全球近 5,000 名技术专业人士的问卷调查。研究揭示了一个关键事实:AI 在软件开发中的主要角色是“放大器”。它会放大高效能组织的优势,也会凸显组织的缺陷。 关键结论:AI 是放大器 AI 投资的最大回报并非来自工具本身,而是来自组织底层系统的战略性建设: 高质量的内部平台 清晰的工作流 团队的协同能力 缺少这些基础,AI ...

Oct 2, 202514 min read

僭越了,有人在用 Rust 写 Kubernetes

一个新语言问世,最爱做的事情之一,就是重写存量软件了。 云原生喝酒 SIG 重点扶持项目——rk8s(https://github.com/rk8s-dev/rk8s) 也可以归在这个范畴里,只不过这个项目重写的东西比较大,是 Kubernetes。 从 2025 年 1 月第一个 Commit 开始,到现在有了 200 多次 Commit,十几万行代码。当然距离 Kubernetes 的几百万行代码还差得远——老马就是喜欢整这种大无畏项目。 另外该项目也是国内第一个脱离 Cargo 转向使用 ...

Sep 27, 20253 min read

【伪】架构师

342 posts