跳转到内容

troubleshooting

starlightBlog.tags.count

🚨 赛博排障实录:CNPG 密码“脑裂”与内核级破门指南

⚠️ 事故现场:完美的图纸,打不开的大门

Section titled “⚠️ 事故现场:完美的图纸,打不开的大门”

在部署 pgAdmin 接入 CloudNativePG (CNPG) 时,即便你严格遵守了 GitOps 的每一行配置,提取了最新的 K8s Secret 密码,依然可能遇到那个令人抓狂的报错:

pgAdmin 4 - Connection Error
FATAL: password authentication failed for user "postgres"

这种“钥匙(Secret)打不开自家大门(Database)”的现象,在 Operator 模式下被称为 “状态撕裂” (State Desync),俗称 “脑裂”


🧠 深度复盘:为什么会被“天坑”算计?

Section titled “🧠 深度复盘:为什么会被“天坑”算计?”

这本质上是 Kubernetes 的声明式状态数据库的物理持久化状态 发生了脱节。

CNPG 管家只在集群 第一次初始化 (Bootstrap) 的那一瞬间,会生成随机密码并同时写入 K8s Secret 和数据库底层引擎。

2. K8s 与物理存储 (Longhorn) 的脱节

Section titled “2. K8s 与物理存储 (Longhorn) 的脱节”

如果你曾删除过 CNPG 实例但保留了 Longhorn 数据卷,当你再次拉起集群时:

  • K8s 视角:这是一个新任务,它会生成一个全新的 Secret(新钥匙)。
  • Postgres 视角:挂载旧硬盘后,它依然守着第一次建库时的旧密码(老锁芯)。
  • 结果:外面显示的密码是 A,但门里认的密码是 B。

🔨 赛博暴力破门方案:潜入内核强行修正

Section titled “🔨 赛博暴力破门方案:潜入内核强行修正”

既然外面的钥匙失效了,作为赛博堡垒的造物主,我们不再纠结于 Secret,直接利用 Socket 直连 潜入数据库心脏,从内部强制重置密码。

利用 kubectl exec 绕过网络认证,以本地超级用户身份进入数据库 Pod:

Terminal window
# 进入主节点终端并直连 psql
kubectl exec -it -n database homelab-db-cluster-1 -- psql

进入 postgres=# 提示符后,执行 SQL 语句进行“物理修正”:

-- 强行将 postgres 用户的锁芯更换为你预设的密码
ALTER USER postgres WITH PASSWORD '你的新密码';
-- 退出内核
\q

👑 总结:首席承包商的终极底牌

Section titled “👑 总结:首席承包商的终极底牌”

这次排障告诉我们一个真理:在云原生时代,GitOps 是流程,但 kubectl 是真理。

不管外面的声明式配置如何演变,只要你能以系统造物主的身份进行“内核级干预”,你就永远拥有整座赛博堡垒的最终解释权。

  • 重建集群时:若要彻底重置密码,必须连同 Longhorn 的 PVC 一起清理。
  • 排障优先级:当应用层认证失败时,优先检查物理存储是否包含陈旧状态。

🗺️ 脑裂排障逻辑图 (Troubleshooting Flow)

Section titled “🗺️ 脑裂排障逻辑图 (Troubleshooting Flow)”

ArgoCD 密码重置:如何恢复遗忘的 admin 登录凭证

在日常运维中,如果遗忘了 ArgoCD Web 面板中 admin 账户的自定义登录密码,将导致无法通过图形界面管理应用。

ArgoCD 将 admin 账户的密码哈希值存储在名为 argocd-secret 的 Kubernetes Secret 资源中。当我们在 UI 界面修改密码时,系统实际上是在更新该 Secret 内部的 admin.password 字段。如果该字段被清空或移除,ArgoCD 将自动回退,允许使用系统在初始化时生成的默认密码进行登录。

基于这一底层逻辑,我们可以通过命令行强制重置密码状态。


请确保你的终端已连接到目标 K3s 集群,且具备操作 argocd 命名空间的高级权限。

使用 kubectl patch 命令,强行从存储配置的 Secret 中剔除自定义密码记录。这相当于清除了系统中后续添加的身份验证覆写规则。

Terminal window
kubectl patch secret argocd-secret -n argocd --type=json -p='[{"op": "remove", "path": "/data/admin.password"}]'

执行成功后,终端应返回 secret/argocd-secret patched

密码字段移除后,需要重启 ArgoCD 的前端 Server 组件,使其强制重新读取底层的 Secret 状态。

Terminal window
kubectl rollout restart deploy argocd-server -n argocd

等待几秒钟,确保新的 Pod 成功启动并接管流量。

此时,ArgoCD 已经回退到出厂安全状态。我们需要从专门存储初始密码的 Secret (argocd-initial-admin-secret) 中提取密码的 Base64 原文并进行解码。

Terminal window
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo

请复制终端输出的这串随机字符串,这是你目前的唯一登录凭证。

  1. 浏览器访问你的 ArgoCD 面板。
  2. 账号输入 admin,密码粘贴刚刚提取的初始字符串。
  3. 登录成功后,导航至页面左侧的 User Info 选项卡。
  4. 点击 Update Password,输入当前密码(初始密码)并设定一个新的、已妥善记录的自定义密码。

跑路但不拆楼:如何安全卸载 ArgoCD 且保留所有业务应用

🏗️ 核心概念:非级联删除 (Non-Cascading Deletion)

Section titled “🏗️ 核心概念:非级联删除 (Non-Cascading Deletion)”

在赛博堡垒中,有时我们只想重装或卸载 ArgoCD 本身(比如迁移 GitOps 工具,或者清理测试环境),但绝不希望它把正在运行的业务应用(比如我们的存储大坝、可视化面板)一起删掉。

ArgoCD 在部署应用时,默认会给资源打上一个叫 resources-finalizer.argocd.argoproj.io 的终结器(相当于一根引爆线)。如果你直接执行 kubectl delete namespace argocd,它会顺着这根线把底层真实的 Pod 和 Service 全部“爆破”掉,甚至会导致整个命名空间卡死在 Terminating 状态。

要做到**“监工跑路,大楼照常营业”**,我们必须在拆除指挥中心前,偷偷剪断所有的引爆线。


✂️ 无损拆除流程 (Safe Teardown Steps)

Section titled “✂️ 无损拆除流程 (Safe Teardown Steps)”

为了绝对的安全,我们将采用**“暗杀级”**施工法:先切断监工的大脑,再从容拆除。

步骤 1:瘫痪指挥中心大脑 (停止控制器)

Section titled “步骤 1:瘫痪指挥中心大脑 (停止控制器)”

这是应对复杂 GitOps 架构最关键的一步。我们必须先把 ArgoCD 的应用控制器副本数缩容为 0,让其失去对集群状态的监听和自愈能力。

Terminal window
kubectl scale statefulset argocd-application-controller -n argocd --replicas=0

(💡 监理提示:执行后,ArgoCD 变成了“瞎子和聋子”,绝对不会再产生任何反扑动作。)

步骤 2:从容剪断所有应用的“引爆线”

Section titled “步骤 2:从容剪断所有应用的“引爆线””

大脑死机后,我们就可以毫无顾忌地强制抹除所有 Application 对象的 Finalizer。

打开终端,执行这行“批量剪线”脚本:

Terminal window
kubectl get apps -n argocd -o name | xargs -I {} kubectl patch {} -n argocd -p '{"metadata": {"finalizers": null}}' --type merge

(💡 监理提示:执行完后,这些应用就已经彻底脱离 ArgoCD 的“同归于尽”机制了。)

步骤 3:销毁 GitOps 档案 (删除 Application 对象)

Section titled “步骤 3:销毁 GitOps 档案 (删除 Application 对象)”

既然引爆线已经没了,且没有控制器来阻挠,我们现在就可以安全地删掉 ArgoCD 里的那些“项目档案”了。

Terminal window
kubectl delete apps --all -n argocd

这条命令会瞬间执行完毕,而你集群里的真实业务应用完全不会受到任何影响,它们已经变成了 K8s 的原生脱管应用。

步骤 4:拆除主体建筑 (反向执行安装脚本)

Section titled “步骤 4:拆除主体建筑 (反向执行安装脚本)”

清空了档案后,我们就可以按图索骥,用当初浇筑时的官方图纸进行反向拆除,把 ArgoCD 的 Redis 缓存、API 服务等组件清理掉:

Terminal window
kubectl delete -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

步骤 5:深度清理残骸 (删除 CRD 与命名空间)

Section titled “步骤 5:深度清理残骸 (删除 CRD 与命名空间)”

最后一步,把划拨给 ArgoCD 的专属园区,以及它留在 K8s 底层的那些自定义资源定义(CRD)连根拔起。

1. 彻底粉碎专属 CRD: (⚠️ 警告:这会删除全集群所有命名空间下的 ArgoCD 相关资源,确保你没有其他的 ArgoCD 实例在使用它们!)

Terminal window
kubectl delete crd applications.argoproj.io appprojects.argoproj.io applicationsets.argoproj.io

2. 抹平命名空间:

Terminal window
kubectl delete namespace argocd

🧰 进阶排障:应对“强拆”中的钉子户 (Troubleshooting)

Section titled “🧰 进阶排障:应对“强拆”中的钉子户 (Troubleshooting)”

在实际的集群拆迁中,K8s 的垃圾回收机制偶尔会发生死锁。如果你在执行上述步骤时卡住了,请使用以下“重火力”工程手段。

1. 漏网之鱼:AppProject 的终结器卡死

Section titled “1. 漏网之鱼:AppProject 的终结器卡死”

除了 Application,ArgoCD 的“项目对象” (AppProject) 也带有防删机制。如果你发现删除一直卡住,请再补上这一刀:

Terminal window
# 剪断所有项目的引爆线
kubectl get appprojects -n argocd -o name | xargs -I {} kubectl patch {} -n argocd --type json -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null

2. 清扫游荡的幽灵:全局权限残留

Section titled “2. 清扫游荡的幽灵:全局权限残留”

ArgoCD 拥有控制整个集群的极高权限,这意味着它创建的某些 ClusterRole 是不受命名空间限制的。拆除后,我们可以用标签(Label)进行一次全局扫荡:

Terminal window
kubectl delete clusterrole -l app.kubernetes.io/part-of=argocd 2>/dev/null
kubectl delete clusterrolebinding -l app.kubernetes.io/part-of=argocd 2>/dev/null

3. 终极黑魔法:强杀卡死的 Namespace

Section titled “3. 终极黑魔法:强杀卡死的 Namespace”

如果所有的图纸都已经清理完毕,但最后执行 kubectl delete namespace argocd 时依然无限卡在 Terminating 状态。这意味着 K8s 底层的 API Server 陷入了死循环,通常是因为残留的无法解析的资源阻塞了垃圾回收。

请掏出这段极其危险但也极其有效的 API 强制注销指令(需要宿主机已安装 jq 工具):

Terminal window
kubectl get namespace argocd -o json | \
jq '.spec.finalizers = []' | \
kubectl replace --raw "/api/v1/namespaces/argocd/finalize" -f -

这条指令会直接绕过正常的垃圾回收流程,粗暴地把该园区的终结器全部清空,Namespace 瞬间就会灰飞烟灭!


至此,ArgoCD 已经被你彻底从集群中抹除,你可以使用 kubectl get pods -A 巡查一圈。

你会发现,argocd 命名空间已经彻底消失,且没有任何幽灵权限残留。但你之前通过它部署的真实业务,依然在各自的命名空间里稳健运行。现在,你可以随时重新安装一个崭新的 ArgoCD,再把它们重新“收编”回流水线中!

赛博拆迁办:安全定向爆破 Longhorn 分布式存储大坝

在 Kubernetes 的世界里,无状态应用(如之前部署的 Headlamp)卸载起来就像拔掉 U 盘一样简单。但是,存储组件是集群的“承重墙”

Longhorn 作为底层存储大坝,掌管着所有容器的命脉数据。为了防止新手误操作导致“删库跑路”的悲剧,Longhorn 官方在底层上了一道极其死板的“物理锁”。如果你直接暴力执行卸载,整个 longhorn-system 命名空间会永远卡在 Terminating(挂起)状态,你的 K8s 集群将陷入无尽的死锁僵局。

今天,包工头就结合官方的卸载与排错指南,带你按照正规的“定向爆破协议”,一步步安全、干净地拆除这座大坝,并解决拆除过程中可能遇到的所有疑难杂症。

⚠️ 拆除前置警告:清退所有租客!

Section titled “⚠️ 拆除前置警告:清退所有租客!”
极度危险

在执行任何卸载命令前,为了防止损坏集群,官方强烈建议: 你必须手动删除所有正在使用 Longhorn 卷的 Kubernetes 工作负载(包括 PersistentVolume, PersistentVolumeClaim, StorageClass, Deployment, StatefulSet 等)。 一旦拆除开始,所有基于 Longhorn 创建的虚拟硬盘数据将瞬间灰飞烟灭!请务必提前做好数据备份。


  1. 解除数据自毁保护锁 (The Safety Catch)

    这是卸载 Longhorn 最核心、最不可省略的一步。默认情况下 deleting-confirmation-flag 是关闭的,卸载任务会直接报错拦截。我们需要强制告诉 Longhorn 控制器:“我确定要销毁一切”。

    在终端敲入这行指令,修改核心配置,允许删除操作:

    Terminal window
    kubectl -n longhorn-system patch -p '{"value": "true"}' --type=merge lhs deleting-confirmation-flag

    预期输出:setting.longhorn.io/deleting-confirmation-flag patched。保护锁现已解除。

  2. 呼叫 Helm 拆迁队 (Uninstall Release)

    保护锁解除后,我们就可以正常呼叫 Helm 执行反向拆除了。它会自动释放 Cilium 分配的 LoadBalancer IP,并遣散所有节点的存储引擎容器。

    Terminal window
    helm uninstall longhorn -n longhorn-system

    稍等片刻,直到终端提示 release "longhorn" uninstalled

  3. 清理大坝废墟 (Delete Namespace)

    确认 Helm 卸载完毕后,我们将整个存储专区物理抹除:

    Terminal window
    kubectl delete namespace longhorn-system
  4. 打扫物理宿主机的残渣 (Node Data Cleanup)

    注意:这一步需要到你所有的 3 台 Ubuntu 物理机/虚拟机上分别执行! 虽然 K8s 里的组件删除了,但 Longhorn 之前在你的宿主机磁盘上生成的物理数据块依然保留着。为了彻底归还磁盘空间,直接使用 rm 大法:

    Terminal window
    sudo rm -rf /var/lib/longhorn
  5. 恢复 Linux 内核秩序 (可选)

    如果你确定这几台机器以后再也不碰基于 iSCSI 的存储,可以把之前我们立下的“开机规矩”撤销掉。

    Terminal window
    sudo rm /etc/modules-load.d/longhorn.conf
    sudo systemctl disable --now iscsid

🛠️ 疑难杂症与抢修指南 (Troubleshooting)

Section titled “🛠️ 疑难杂症与抢修指南 (Troubleshooting)”

在赛博工地,意外总是难免的。如果你在卸载过程中遇到了卡死、报错,或者突然“手滑”后悔了,请参考以下官方急救方案:

💊 症状 1:手滑卸载了,但我不想删!(取消卸载)

Section titled “💊 症状 1:手滑卸载了,但我不想删!(取消卸载)”

如果你不小心执行了 helm uninstall(且当时没开保护锁导致它卡在 uninstalling 状态),你可以利用 Helm 的时光机功能紧急回档。

抢修指令:

Terminal window
# 1. 查找 Longhorn 卸载前的最后一个正常版本号 (REVISION)
helm list -n longhorn-system -a
# 2. 假设查到上一个正常版本是 1,执行强制回滚:
helm rollback longhorn 1 -n longhorn-system

提示 Rollback was a success! 代表大坝抢修成功,数据保住了!

💊 症状 2:Namespace 一直卡在 Terminating,CRD 删不掉

Section titled “💊 症状 2:Namespace 一直卡在 Terminating,CRD 删不掉”

这是 Kubernetes 卸载存储组件最常见的恶疾:Finalizer 幽灵锁。因为底层引擎已经被你删了,K8s 还在傻傻等待底层引擎来确认删除这些 CRD(自定义资源),从而形成死锁。

抢修指令(暴力清除所有 Longhorn 状态): 执行以下脚本,它会遍历所有 Longhorn 的 CRD,强行抹除它们的 Finalizer,然后连根拔起。

Terminal window
# 批量强拆:剥夺所有 Longhorn 资源的终结器
for crd in $(kubectl get crd | grep longhorn.io | awk '{print $1}'); do
kubectl get $crd -n longhorn-system -o name 2>/dev/null | xargs -I {} kubectl patch {} -n longhorn-system -p '{"metadata":{"finalizers":null}}' --type merge 2>/dev/null
done

💊 症状 3:执行清理脚本时报错 Webhook 找不到

Section titled “💊 症状 3:执行清理脚本时报错 Webhook 找不到”

如果你在执行上面那个清理脚本时,K8s 抛出了类似这样的错误: Internal error occurred: failed calling webhook "validator.longhorn.io"... service "longhorn-admission-webhook" not found

这是因为残缺的卸载过程把 Webhook 服务删了,但注册表里还留着它的名字,导致 K8s 每次修改资源都想去请求一个不存在的验证服务。

抢修指令: 删除这些拦截请求的幽灵配置,为清理脚本放行:

Terminal window
kubectl delete ValidatingWebhookConfiguration longhorn-webhook-validator
kubectl delete MutatingWebhookConfiguration longhorn-webhook-mutator

删除这两个配置后,重新执行症状 2中的 CRD 清理脚本,即可丝滑通关。


历经波折后,执行最后的扫尾质检:

Terminal window
kubectl get crd | grep longhorn
kubectl get ns | grep longhorn

如果上述命令没有任何输出,恭喜你,这座赛博存储大坝已经被完美定向爆破,所有的顽疾和锁链都已被彻底斩断!

K3s + Cilium 踩坑实录:Longhorn 插件无限重启?揪出 eBPF 路由脑裂的幕后黑手

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

在咱们上一期对 K3s 故障节点进行了“核弹级强拆”并重新加入集群后,本以为可以顺利盖起 Longhorn 存储大坝了。结果,Longhorn 的核心存储特派员 longhorn-csi-plugin 却一直在无限崩溃重启(CrashLoopBackOff)。

表面上看是存储插件坏了,但经过一顿抽丝剥茧,最后发现:这根本不是存储的锅,而是一起典型的网络大动脉断裂案!

今天这篇排错实录,我们就来复盘一下如何解决这种“查号台还活着,但去查号台的路被炸断了”的底层网络脑裂问题。


💥 案发现场:伪装成存储故障的网络崩塌

Section titled “💥 案发现场:伪装成存储故障的网络崩塌”

当时查看 longhorn-csi-plugin 的崩溃遗言(Logs),发现了极其关键的一行报错:

E0419 00:36:26.346354 1 main.go:167] "Error connecting to CSI driver" err="context deadline exceeded"
time="2026-04-19T00:36:30.790662906Z" level=warning msg="Failed to initialize Longhorn API client Get \"http://longhorn-backend:9500/v1\": dial tcp: lookup longhorn-backend on 10.61.0.10:53: read udp 10.60.2.244:53943->10.61.0.10:53: i/o timeout. Retrying"

日志翻译: Longhorn 插件启动时,需要找集群内部的“114查号台”(CoreDNS,IP 为 10.61.0.10:53)去查询总控中心的地址。结果请求发出去后,如同石沉大海,直接报了 i/o timeout(连接超时)。因为拿不到总控地址,插件干脆拒绝启动,最后被 Kubelet 当做死进程给毙了。


🕵️‍♂️ 抽丝剥茧:派出侦察兵探路

Section titled “🕵️‍♂️ 抽丝剥茧:派出侦察兵探路”

为了验证是不是真的连不上 DNS,我跑到后台看了一眼 CoreDNS 的状态,发现它分明是 1/1 Running,活得好好的,安安稳稳地跑在 k3s-master-02 节点上。

接着,我拉起了一个带 nslookup 工具的 busybox 侦察兵进行极限测试:

Terminal window
kubectl run -i --tty --rm debug-net --image=busybox --restart=Never -- nslookup kubernetes.default

返回的结果非常绝望:

;; connection timed out; no servers could be reached

结论:查号台活得好好的,但去找查号台的路,全断了。


🧠 底层逻辑:大动脉为什么会断?

Section titled “🧠 底层逻辑:大动脉为什么会断?”

这要归咎于咱们这套集群的高级架构。

在安装 K3s 时,我们加上了 --disable-kube-proxy 参数。这意味着 Kubernetes 传统的基于 iptables 的内部流量转发机制被废弃了。全集群的 Service IP(包括内部 DNS 地址 10.61.0.10),全靠 Cilium 的 eBPF 机制在 Linux 内核底层进行拦截和高效转发。

但是!咱们刚才对 k3s-master-03 执行了“核弹级强拆”,导致整个集群的 Cilium 路由映射表(BPF Maps)出现了严重的数据断层和脑裂。

Cilium 彻底迷失了方向,不知道该怎么把 UDP 53 端口的请求跨节点发给 k3s-master-02 上的 CoreDNS,于是网络包在内核层直接被无情丢弃。底层网络一断,上层依赖内部域名的 Longhorn 存储插件自然跟着全军覆没。


🛠️ 抢修方案:网络管线“大换血”

Section titled “🛠️ 抢修方案:网络管线“大换血””

既然病根在底层的路由表缓存,咱们不需要重装,只需要强制集群对网络管线进行一次滚动重启(Rollout Restart),让 eBPF 重新绘制整张全网路由表即可。

请依次执行这排雷三把斧

第一斧:强制重建 Cilium 网络映射表

Section titled “第一斧:强制重建 Cilium 网络映射表”

让所有节点的 Cilium 特派员重新扫描集群,重写内核里的路由规则:

Terminal window
kubectl rollout restart ds cilium -n kube-system

(敲完后喝口水等个半分钟,让 3 个节点的 Cilium Pod 挨个重启完成)

第二斧:顺手重启查号台 (CoreDNS)

Section titled “第二斧:顺手重启查号台 (CoreDNS)”

为了防止 CoreDNS 本身残留什么僵尸缓存,给它也来一记还我漂漂拳:

Terminal window
kubectl rollout restart deploy coredns -n kube-system

第三斧:再次派出侦察兵验证验收

Section titled “第三斧:再次派出侦察兵验证验收”

等上面的网络和 DNS Pod 都处于 Running 状态后,重新敲一遍之前的测试命令:

Terminal window
kubectl run -i --tty --rm debug-net --image=busybox --restart=Never -- nslookup kubernetes.default

💡 预期结果: 只要这次没有报 timed out,而是瞬间返回了类似 Server: 10.61.0.10Address: 10.61.0.10:53 的解析结果(即便带了 NXDOMAIN 的小 Bug 也不影响),就说明全集群的大动脉彻底打通了!

大动脉一通,之前卡在 CrashLoopBackOfflonghorn-csi-plugin 就会瞬间找回组织,自动停止报错并挂载成功。

包工头施工笔记: 排查 K8s 故障时,如果上层应用报“连接超时”,不要急着重装应用。拔出网络排错“侦察兵”测一下底层连通性,往往能事半功倍。“遇到 eBPF 脑裂,一键 Rollout 重启网络插件”,这是赛博工地里必须掌握的保命神技!

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

架构师的外科手术:破解 kube-vip 启动死循环

💡 前置联动说明 本文是对 K3s 高可用集群浇筑 (HA Setup) 章节中“补丁 2”配置的深度原理解析。

  • 🕵️ 发现异常:如果不加该补丁,仅照抄基础部署清单,kube-vip 容器会无限 CrashLoopBackOff,日志疯狂报错 Timeout(连接 API Server 超时)。
  • 🧠 根本原因:底层网络插件(Cilium)未完全就绪,或者 Kube-proxy 被卸载时,组件向集群内部虚拟 IP(10.61.0.1)寻址产生的“先有鸡还是先有蛋”的死循环。
  • 🛠️ 解决方案:通过注入宿主机物理回环地址 (127.0.0.1) 强行搭桥,实现控制面与虚拟网络的彻底解耦。

在打造这套赛博堡垒的高可用地基时,如果直接照搬基础的部署清单,我们大概率会在 kube-vip 的启动阶段摔得头破血流。今天,包工头就带你从底层扒开这个“补丁”的硬核逻辑,复盘我们是如何斩断这个死循环的。


💣 灾难复盘:为什么会疯狂 Timeout?

Section titled “💣 灾难复盘:为什么会疯狂 Timeout?”

在默认情况下,Kubernetes 集群里的任何 Pod(包括 kube-vip)想要呼叫“总部”(API Server),都会去请求一个内部的虚拟 IP(比如我们配置的 10.61.0.1 这个 ClusterIP)。

但这就产生了一个致命的死循环

  1. kube-vip 启动了,它需要和另外两台机器商量谁当“带头大哥”(Leader Election),这必须读写 API Server 的数据。
  2. 于是 kube-vip 向上级汇报,把请求发给了虚拟网关 10.61.0.1
  3. 但是! 此时底层的 Cilium 网络插件可能正在重启,或者还没完全就绪;又或者我们在浇筑阶段强行卸载了 kube-proxy,导致这个虚拟 IP 10.61.0.1 根本没人给它做底层转发。
  4. 结果kube-vip 找不到总部,一直苦苦等待响应,最后只能抛出绝望的 Timeout 并崩溃重启。大门失守,高可用形同虚设。

🗝️ 破局之法:精准搭桥 (外科手术)

Section titled “🗝️ 破局之法:精准搭桥 (外科手术)”

为了打破这个死循环,我们在 kube-vip.yaml 中通过 sed 命令强行注入了这两行环境变量:

- name: KUBERNETES_SERVICE_HOST
value: "127.0.0.1"
- name: KUBERNETES_SERVICE_PORT
value: "6443"

它的作用,就是kube-vip 塞了一张“本地特权 VIP 通行证”!

由于我们在部署时声明了 --inCluster 并开启了 hostNetwork: true(共享宿主机网络),当我们强制注入 127.0.0.16443 后,kube-vip 就不再去那个虚无缥缈的 10.61.0.1 绕弯子了。

它会直接“敲隔壁的门”——通过主机的本地回环网卡(Localhost),呼叫本台物理机上正在运行的 K3s API Server! 管你底层的 Cilium 瘫没瘫痪,管你 K8s 的虚拟网络通没通,我们走的是物理机的地下通道,绝对畅通无阻!这相当于给我们的赛博堡垒建立了一条最高安全级别的消防通道

我们的 KUBERNETES_SERVICE_HOST 注入是在做精密的外科手术。通过注入本地回环地址,实现控制面组件与虚拟网络的硬解耦,彻底斩断网络启动期的死循环锁。

赛博工地的“五分钟定律”:揭秘节点宕机后的 Pod 驱逐真相

在我们的 K3s 高可用堡垒中,我进行了一次“暴力断电”演习:直接关掉了 k3s-master-03 节点。

当时的情况如下:

  • 业务中断:原本运行在 03 节点上的 Headlamp(单副本)瞬间无法访问。
  • 状态迷惑:控制台输入 kubectl get pods -o wide,发现该 Pod 居然还显示为 Running,且依然赖在已经失联的 03 节点上。
  • 没有漂移:想象中的“毫秒级瞬移到其他节点”并没有发生。

这种“僵尸 Pod”现象并非 Bug,而是触发了 Kubernetes 调度架构中极其核心的机制:基于污点的驱逐(Taint-based Eviction)与五分钟定律

📖 官方原典参考:本文底层逻辑均来源于 Kubernetes 官方文档:污点和容忍度


要理解这五分钟,必须先看懂 K8s 官方定义的一对博弈属性,我们直接用命令在案发现场求证:

1. 污点 (Taint) —— 节点的“逐客令”

Section titled “1. 污点 (Taint) —— 节点的“逐客令””

03 节点断电失联时,K8s 控制面会迅速给该节点打上严厉的污点。 🕵️ 求证指令:我们怎么确定节点真的被打上污点了?

Terminal window
# 查看 03 节点的污点状态
kubectl describe node k3s-master-03 | grep Taints

输出结果:你会看到 Taints: node.kubernetes.io/not-ready:NoExecute, node.kubernetes.io/unreachable:NoExecute。 这就是节点的逐客令。NoExecute 效果意味着:不仅新 Pod 不能来,已经在上面的 Pod 必须立刻被驱逐!

2. 容忍度 (Toleration) —— Pod 的“特权通行证”

Section titled “2. 容忍度 (Toleration) —— Pod 的“特权通行证””

面对驱逐令,Pod 能否抗旨?这就看它自身有没有配置对应的容忍度。


🕵️ 破案:这 300 秒是从哪来的?

Section titled “🕵️ 破案:这 300 秒是从哪来的?”

既然污点是 NoExecute (立刻驱逐),为什么 Headlamp 还能在上面赖 5 分钟?这是因为 API Server 在创建普通 Pod 时,默认、悄悄地给它们注入了限时的容忍度。

🕵️ 求证指令:抓出 Pod 身上被暗中注入的“宽限期”

Terminal window
# 查看 Headlamp 这个 Pod 的详细配置
kubectl describe pod <你的-headlamp-pod-名字> -n kube-system | grep -A 5 Tolerations

输出结果:你会赫然发现这样两行配置:

node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

官方文档解读:“这些自动添加的容忍度意味着 Pod 可以在检测到对应的问题之一时,在 5 分钟内保持绑定在该节点上。” 即:长官让我滚,但我有 300 秒的宽限期。


🏗️ 架构思考:DaemonSet 为什么永远不漂移?

Section titled “🏗️ 架构思考:DaemonSet 为什么永远不漂移?”

Ciliumkube-vip 这类组件,即便过了 5 分钟也不会去别的节点重新拉起。

🕵️ 求证指令:看看 DaemonSet 的容忍度有何不同?

Terminal window
# 使用 -A 10 确保能把 DaemonSet 身上长长的一串特权全打印出来
kubectl describe pod -l app.kubernetes.io/name=kube-vip-ds -n kube-system | grep -A 10 Tolerations

输出结果:你会发现它们也有 not-readyunreachable 的容忍度,但是没有 for 300s 这个时间限制!

官方文档明确指出:“DaemonSet 中的 Pod 被创建时,针对不可达污点添加的 NoExecute 容忍度,将不会指定 tolerationSeconds。这保证了出现上述问题时 DaemonSet 永远不会被驱逐。”


⚡ 极客进阶:掌控驱逐倒计时的“快与慢”

Section titled “⚡ 极客进阶:掌控驱逐倒计时的“快与慢””

作为集群的架构师,你可以通过在 YAML 的 spec.tolerations 中显式声明,来覆盖这默认的 300 秒:

  • 场景 A(无状态前端):想让面板瞬间漂移?配置 tolerationSeconds: 30(警告:容易引发网络抖动时的重建风暴)
  • 场景 B(有状态存储):数据库绑了本地硬盘?必须延长保命!配置 tolerationSeconds: 6000 死等网络恢复。

  1. 架构侧:核心业务增加副本数(Replicas >= 2)配合反亲和性,是应对这 5 分钟断档期的最优雅解法。
  2. 应急侧:演练时不想等待?直接执行强制抹除:kubectl delete pod <POD_NAME> -n <NAMESPACE> --force --grace-period=0
  3. 排查侧:想要系统性掌握这套机制的排查手法?👉 点击查看《赛博工地巡检手册:污点与容忍度排查指令集》