跳转到内容

HomeLab

starlightBlog.tags.count

K8s 存储踩坑记:强拆 Longhorn '僵尸 Pod' 钉子户

在折腾 K3s + Longhorn 搭建我的赛博影音堡垒时,我遇到了一个极其让人血压升高的玄学 Bug。

整个流水线突然停工,应用 Pod 疯狂报错无法启动,而罪魁祸首,居然是一个在系统里“死活退不出来”的僵尸进程。

一切起因于我修改了媒体应用(Radarr/Sonarr)的配置文件并重新部署。新 Pod 迟迟无法启动,查看事件日志,赫然飘着那句经典的存储死锁报错:

Multi-Attach error for volume ... Volume is already exclusively attached to one node and can't be attached to another

意思是:存储卷正被旧节点死死锁着,新 Pod 拿不到权限。

顺藤摸瓜,我发现底层负责管理挂载的 longhorn-csi-plugin Pod 一直卡在 Terminating(正在终止)状态,时长超过了 20 分钟!查看它的详细日志,看到了这句致命的超时宣告:

error killing pod: failed to "KillContainer" for "longhorn-liveness-probe" ... rpc error: code = DeadlineExceeded desc = context deadline exceeded

🧠 赛博验尸报告:为什么它“杀不死”?

Section titled “🧠 赛博验尸报告:为什么它“杀不死”?”

表面上看是 Kubernetes 出了 Bug,但深入探究,这其实是 Linux 内核底层的自我保护机制导致的。

  1. I/O 阻塞:由于之前尝试跨网段挂载 NFS 导致了网络超时,底层的存储进程卡在了“等待磁盘响应”的状态。
  2. 内核级锁死 (D-state):在 Linux 中,当进程卡在底层 I/O 等待时,内核会将其标记为 不可中断睡眠 (Uninterruptible Sleep, D状态)。在这个状态下,进程会无视任何外部信号(包括 kill -9)。
  3. Kubelet 懵逼:Kubernetes 的包工头 Kubelet 给容器引擎下达了“拔管”指令,但底层容器因为 D 状态根本不鸟它。Kubelet 苦等 2 分钟后无奈宣布超时(DeadlineExceeded),于是这个 Pod 就成了一个永远卡在 Terminating 的“钉子户”,并且牢牢霸占着 Longhorn 的存储锁。

对付这种级别的钉子户,常规的清理已经没用了。必须采取暴力强拆手段。

第一板斧:K8s 逻辑强抹除(本次生效的绝招)

Section titled “第一板斧:K8s 逻辑强抹除(本次生效的绝招)”

既然正常的优雅退出走不通,那就直接在 K8s 的记录本上把它强行划掉,不给任何宽限时间。

执行以下命令强杀卡死的 Pod:

Terminal window
kubectl delete pod <卡住的Pod名称> -n longhorn-system --force --grace-period=0

效果:命令敲下的瞬间,Pod 从列表中消失,Longhorn 控制平面终于意识到锁可以释放,随后新 Pod 瞬间挂载存储卷成功,业务恢复!

第二板斧:重启底层容器引擎(备用方案)

Section titled “第二板斧:重启底层容器引擎(备用方案)”

如果在执行完第一步后,kubectl get pods 里确实没它了,但应用依然报 Multi-Attach error,说明底层 Linux 进程依然在霸占资源。此时需要重启 K3s 的 agent 服务来重置容器状态:

Terminal window
# 登录出问题的 K3s 工作节点执行
sudo systemctl restart k3s-agent
# 如果是主节点则执行 restart k3s

第三板斧:物理重启(终极毁灭)

Section titled “第三板斧:物理重启(终极毁灭)”

如果前两招都打完,存储依然处于死锁状态,说明 Linux 内核的 I/O 队列已经彻底崩溃。别犹豫,直接 sudo reboot 重启该节点所在的物理机或虚拟机,这是清理 D 状态进程最彻底的方案。

这次踩坑不仅让我深入理解了 Kubernetes 和底层操作系统的联动关系,也总结出了一条铁律:

在 HomeLab 环境中,对于独占存储(ReadWriteOnce)的单副本有状态应用(如 qBittorrent、数据库等),在 deployment.yaml 中务必将更新策略设置为重建!

spec:
replicas: 1
strategy:
type: Recreate # 强制先杀旧,再启新,绝不抢锁

遇到 Terminating 不要慌,找准病根,果断拔管!

K3s 高可用集群踩坑实录:节点加入失败死锁?四步“核弹级强拆”彻底清理脏数据

大家好,我是赛博包工头。欢迎回到赛博工地。

最近在给 HomeLab 打 K3s 高可用(HA)集群地基的时候,遇到了一个非常搞心态的坑:新加入的控制节点(比如 Master 03)死活连不上主集群,一直处于 NotReady 或者疯狂重启的状态。

如果你去查日志(journalctl -u k3s),会发现满屏都在刷类似这样的报错:

rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: context deadline exceeded" failed to get etcd status

很多兄弟遇到这种情况,第一反应是:“卸载重装!” 结果跑完官方的卸载脚本,再重新安装,发现问题依旧,还是同样的报错!

今天,我们就来扒一扒这个坑的底层原因,并给出一套“挫骨扬灰”级别的强拆重装方案。


🔍 案情分析:为什么卸载重装没用?

Section titled “🔍 案情分析:为什么卸载重装没用?”

罪魁祸首在于:K3s 官方提供的卸载脚本 /usr/local/bin/k3s-uninstall.sh “太温柔了”

为了防止用户手残误删掉宝贵的生产数据,这个官方脚本在卸载程序时,故意保留了 /var/lib/rancher/k3s 这个核心数据目录(里面存着本地的 ETCD 数据库文件)。

这就导致了一个致命的逻辑死锁:

  1. 你的节点之前因为某种原因加入集群失败,本地生成了一份残缺或错误的 ETCD 数据。
  2. 你执行了官方卸载脚本,卸载了 K3s 程序。
  3. 你重新执行加入命令。
  4. 新安装的 K3s 程序一启动,直接读取到了上一波残留的“脏数据”。它拿着这套旧的、错误的身份凭证去向主集群报到,立刻又被主集群的安保机制拒之门外,当场死锁崩溃。

所以,想要真正重装,我们必须把这台机器上的残骸彻底炸平


请严格按照以下 4 步流程操作,少一步都不行

第一步:执行官方卸载(停机熄火)

Section titled “第一步:执行官方卸载(停机熄火)”

首先,在出故障的节点(例如 Master 03)上,把 K3s 进程停掉并执行常规卸载:

Terminal window
/usr/local/bin/k3s-uninstall.sh

(如果是 Agent 节点,执行 /usr/local/bin/k3s-agent-uninstall.sh)

第二步:手动清理物理残骸(🔥 最关键的一步)

Section titled “第二步:手动清理物理残骸(🔥 最关键的一步)”

这是官方脚本漏掉的,也是破除死锁的核心。我们必须把它的老巢彻底炸平。 依然在 故障节点(Master 03) 上执行:

Terminal window
sudo rm -rf /var/lib/rancher/k3s
sudo rm -rf /etc/rancher/k3s
sudo rm -rf /var/lib/kubelet

⚠️ 警告:执行完这一步,该节点上的所有 K3s 本地数据将彻底灰飞烟灭,不可恢复。

第三步:去主节点注销“僵尸户口”

Section titled “第三步:去主节点注销“僵尸户口””

因为 Master 03 之前卡死过,主集群可能还保留着它的“僵尸档案”。为了防止主集群出于安全防范拒绝它重新加入,我们需要去主节点把它踢掉。

登录到健康的 主节点(例如 Master 01,10.0.10.10),执行:

Terminal window
kubectl delete node k3s-master-03

第四步:重新注入灵魂(满血复活)

Section titled “第四步:重新注入灵魂(满血复活)”

现在,Master 03 已经是一张纯洁的白纸,主集群里也没有了它的黑历史。 回到 故障节点(Master 03),重新粘贴你规划好的加入命令


执行完第四步后,在终端里等个大概 30 秒。然后切回你的本地电脑或主节点,敲一下查岗命令:

Terminal window
kubectl get nodes

PVE 宿主机实战:4T USB 硬盘挂载与 NFS 权限配置指南

在 PVE 环境中,将大容量 USB 硬盘挂载至宿主机并开启 NFS 共享,是实现跨虚拟机数据共享的高效方案。但在实际操作中,磁盘识别错误会导致数据丢失,网段配置不当会导致访问受阻。本文记录了从底层格式化到权限调优的标准流程。


在执行任何格式化操作前,必须核对物理磁盘。

Terminal window
lsblk

注意: 观察磁盘容量(如 3.7T)和类型。通常系统盘为 sda,外挂盘可能为 sdbsdc。请在后续步骤中将 /dev/sdb 替换为你实际识别到的磁盘代号。一旦选错,数据将不可恢复。

对于超过 2T 的硬盘,必须使用 GPT 分区表:

Terminal window
# 使用 fdisk 工具(以 /dev/sdb 为例,请按实际修改)
fdisk /dev/sdb
# 交互指令:
# 输入 g (创建 GPT 分区表)
# 输入 n (创建新分区,起始和结束扇区均按回车保持默认)
# 输入 w (写入并退出)
# 格式化分区为 ext4
mkfs.ext4 /dev/sdb1

使用 UUID 挂载可以防止 USB 插拔后驱动器路径改变。

Terminal window
# 获取 UUID
blkid /dev/sdb1
# 记录 UUID="xxxxxxxx-xxxx-..." 部分
# 创建挂载点
mkdir -p /mnt/nfs
# 修改系统挂载表
nano /etc/fstab
# 在末尾添加(替换为你实际的 UUID):
UUID=你的UUID /mnt/nfs ext4 defaults 0 2
# 生效挂载
mount -a

二、 NFS 服务安装与核心权限配置

Section titled “二、 NFS 服务安装与核心权限配置”
Terminal window
apt update && apt install -y nfs-kernel-server

编辑配置文件:nano /etc/exports,添加如下配置:

/mnt/nfs 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash,insecure)
  1. 网段地址 (10.0.0.0/16)必须按实际修改。这里的地址定义了谁能访问此共享。
    • 如果你的 IP 是 192.168.1.x,请填入 192.168.1.0/24
    • 使用 /16 掩码(如 10.0.0.0/16)可以覆盖 10.0.x.x 范围内的所有 IP,适合多子网环境。
  2. insecure (关键): 默认 NFS 要求客户端使用 1024 以下端口,而 Kubernetes/K3s 等容器化平台挂载时常使用高位随机端口。不加此参数会导致 Access Denied
  3. no_root_squash: 允许客户端以 root 权限写入,这对于需要持久化存储的应用(如数据库、媒体库)至关重要。

修改配置后必须手动刷新导出表:

Terminal window
# 强制重新导出所有路径
exportfs -ra
# 查看当前生效的完整配置
exportfs -v

验收标准:exportfs -v 的输出中,确认包含了你设置的路径和网段,并且权限列表中出现了 insecure


  • 磁盘休眠:部分 USB 硬盘盒会自动进入休眠,可能导致 Pod 首次挂载超时。
  • 网络防火墙:若挂载失败,请检查 PVE 节点是否开启了数据中心防火墙,确保 2049 端口已对目标网段放行。