news 2026/10/14 23:48:16

Kubernetes控制面雪崩:从Connection Refused到磁盘满的排查与恢复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kubernetes控制面雪崩:从Connection Refused到磁盘满的排查与恢复

作为一个常年跟Kubernetes 和 KubeSphere 打交道的人,我最怕看到的告警其实不是“节点离线”,而是控制台突然打不开、kubectl 敲什么都是connection refused。这种故障表面上看是网络问题,但排查到最后,十次里有六七次都不是网络本身的问题。KubeSphere 无法连接、API Server 崩溃,再加上磁盘满,这三个词凑在一起,基本就是一个非常典型的控制面雪崩事故。

这篇文章不打算写那种“官方文档复读机”,就结合我实际踩过的一个案例,把从Connection Refused到磁盘写满、再到 API Server 崩溃的完整链路、排查手法和恢复操作都捋一遍。如果你也管着 K8s 集群,这篇文章值得花十分钟读完,关键时候能救命。

1. 故障表象拆解:Connection Refused 到底在说什么

1.1 同样是连不上,refused 和 timeout 是两码事

遇到“无法连接”,第一步不是慌,是分清楚报错类型。很多人看到connection refused就以为是防火墙、安全组、负载均衡的问题,这是最常见的误判。

connection refused这个报错的意思是:TCP 请求到达了对端,但对端没有进程在监听这个端口,于是内核直接回了 RST。也就是说,网络链路大概率是通的,问题出在“端口后面根本没有活着的服务”。

而如果是timeout,说明数据包发出去就石沉大海了,可能被防火墙丢弃、路由不可达、负载均衡后端全挂,或者源和目标之间出现了网络黑洞。

拿 kubectl 来说,如果你执行:

kubectl get nodes

报错是:

Unable to connect to the server: dial tcp 192.168.10.101:6443: connect: connection refused

这就说明:你的客户端能到192.168.10.101,但是 6443 端口上已经没有 kube-apiserver 在监听了。

而 KubeSphere 控制台通常是靠 NodePort 30880 暴露的,如果控制台打不开,报错可能是502 Bad Gateway或者干脆拒绝连接。这时候先别去折腾 ingress、防火墙,先确认后端服务是不是已经挂了。

这里有个快速分层判断的技巧:

报错类型排查方向
connection refused进程未监听、服务崩溃、端口被占用
timeout网络链路、防火墙、负载均衡后端状态
502/504上游服务存在但不可用、服务崩溃或超时
证书错误证书过期、CA 不匹配(K8s 组件间常见)

1.2 KubeSphere 的“连接”不是只有一个入口

KubeSphere 的用户侧入口确实有三个层级,排查的时候别只盯一个。

第一层是浏览器访问的控制台入口,走 30880 NodePort,流量路径是:浏览器 -> NodeIP:30880 -> ks-console Service -> ks-apiserver -> Kubernetes API Server。

第二层是 kubectl 直连 API Server 的入口,走 6443,流量路径是:kubectl -> MasterIP:6443 -> kube-apiserver -> etcd。

第三层是在集群内访问的 Service ClusterIP,比如各命名空间里的服务通过 K8s Service DNS 名互相调用。

这三个入口如果同时失效,或者至少控制台和 kubectl 同时失效,那基本可以断定问题出在公共的“底座”,也就是 kube-apiserver 或者它后面的 etcd,而不是某一个上层服务的问题。

那次故障的现场就是这种状况:控制台打不开,kubectl 报connection refused,同时集群里其他业务也开始出现超时。当多个入口同时不正常,就别再纠结网络了,赶紧把视角转向控制面本身。

2. 从“端口不通”到定位 API Server 崩溃的排查链路

2.1 第一步:直接探活本机 API Server

既然公网和 NodePort 都不行,那就先绕过网络链路,直接在 Master 节点上看 API Server 到底还活着没有。

登录任意 Master 节点,先测试本机的 HTTPS 健康检查接口:

curl -k https://127.0.0.1:6443/healthz

注意,这个链接是 HTTPS 的,-k是忽略证书校验。如果返回:

ok

说明 kube-apiserver 进程在本机是活着的,那问题大概率出在负载均衡、浮点 IP 或者前置代理上。

但如果返回:

curl: (7) Failed to connect to 127.0.0.1 port 6443: Connection refused

那就实锤了:6443 端口根本没有进程在监听,API Server 已经退出或处于 CrashLoopBackOff 状态。

当时我用ss -lntp | grep 6443时发现端口被释放,再用systemctl status kube-apiserver一看,服务处于failed状态,下面写着kube-apiserver.service: Main process exited, code=exited, status=1/FAILURE。

到了这一步,网络问题的嫌疑基本洗清了,接下来就是看进程为什么退出的问题。

2.2 第二步:翻系统日志找到崩溃原因

高版本的 K8s 集群如果在二进制部署模式下,kube-apiserver 是 systemd 管理的,日志直接进 journald。

排查命令:

journalctl -u kube-apiserver --since "30 minutes ago" -n 200

这里强烈建议先看最后 100-200 行,重点找panic、fatal、error、disk、etcd这些关键词。如果日志滚得特别快,可以用:

journalctl -u kube-apiserver -f

实时追一下。

在我经历的那次故障中,日志里反复出现这么几行:

storagebackend: etcd3: failed to watch ...: rpc error: code = ResourceExhausted desc = etcdserver: mvcc: database space exceeded

以及:

etcdserver: server is likely overloaded

还有更直接的:

error: failed to open file /var/lib/etcd/member/snap/db: no space left on device

看到no space left on device的时候,我心里其实已经清楚了一半:这不是 API Server 自己写代码写崩了,是底层存储爆了,etcd 写不进去数据,进而把 API Server 拉下了水。

2.3 第三步:确认 etcd 健康状态和磁盘水位

Kubernetes 的所有状态都存放在 etcd 里,API Server 是 etcd 唯一的客户端(严格说是主要的)。如果 etcd 落不了盘,API Server 的所有读写请求都会失败,包括它自己最核心的 List-Watch 机制。

检查 etcd 服务:

systemctl status etcd

或者直接用 etcdctl 探活:

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem endpoint health

注意:不同集群的证书路径不一样,实际环境以你自己的部署为准。

与此同时,敲下这两个命令看水位:

df -h df -i

df -h看的是容量占用,df -i看的是 inode 占用。我当时跑完df -h的结果是:

/dev/sda2 50G 50G 12K 100% /

根分区 100% 满,etcd 数据目录如果在根分区下面,基本等于被判了死刑。

这里有个坑:df -i显示的 inode 满了也会导致写入失败,但df -h看着还有剩余空间。两者都要看,任何一项满都会产生连锁反应。

3. 磁盘满导致的 API Server 崩溃机制复盘

3.1 为什么磁盘满,先死的是 etcd 和 API Server

先说结论:磁盘满 -> etcd 无法写 WAL 和快照 -> API Server 读写 etcd 全部失败 -> API Server 探活失败、panic、被重启 -> 控制面瘫痪。

etcd 是一个强一致性的 KV 存储,它写数据的时候必须调用 fsync 把日志落盘。如果磁盘满了,fsync 就返回ENOSPC,etcd 会立刻把这次写入标记为失败,并且对客户端返回错误。

API Server 对 etcd 的模型基本是“所有操作都要过 etcd”,包括创建 Pod、更新 Deployment、发放证书、记录 Node 状态。etcd 一拒绝写,API Server 的很多核心循环就会出问题。最典型的是事件记录器的 Event 写不进去,这会导致kubectl describe查不到新事件,但其实 API Server 的 apiserver loop 已经开始畸形运行。

更致命的是 kubelet 和 controller-manager 都依赖 API Server 来心跳上报。API Server 一卡,kubelet 的NodeHeartbeat发不出去,节点就会被标记为 NotReady,然后触发 workload 驱逐,形成更大的雪崩。

这里有一个容易误会的点:API Server 本身可能并没有“崩”,而是因为 etcd 的database space exceeded错误导致大量 List-Watch 直接报错,最终 API Server 进入apierrors.NewInternalError的循环,然后触发 panic。此时 systemd 会尝试把它重启,但重启后它又去连 etcd,结果还是失败,于是进入 CrashLoopBackOff。从外部看,就是端口没人监听,表现成connection refused。

3.2 磁盘里到底被什么塞满了

定位到磁盘满之后,接下来的一步就是找出“凶手”。执行:

du -x --max-depth=1 -h / 2>/dev/null | sort -hr | head -20

-x参数很关键,它表示不跨越文件系统,避免把 /proc、/sys 也扫进去,导致误判和卡死。

那次环境里扫出来的结果,常见的大头基本集中在这么几个地方:

目录常见原因
/var/log/journalsystemd 日志堆积
/var/lib/docker/containers容器标准输出日志无限制增长
/var/lib/docker/overlay2镜像层和容器可写层堆积
/var/lib/etcd/memberetcd 数据、快照、WAL 文件膨胀
/tmp临时文件未被清理
/var/lib/docker/tmpDocker 构建缓存或临时文件

印象最深的两个元凶:一个是某个服务的容器日志一晚上写了几十 GB,另一个是 etcd 快照文件因为长期没做 compaction,整个/var/lib/etcd目录膨胀得非常夸张。

容器日志之所以可怕,是因为 Docker 默认只是把容器 stdout 输出写到/var/lib/docker/containers/{container_id}/*-json.log,如果应用代码里有大段大段的日志打印,这个文件会以惊人的速度增长,不做轮转就是灾难。

3.3 清理前必须知道的安全边界

磁盘满了以后,很多人会急眼,找到大文件就rm -rf。这个习惯在 K8s 环境里特别危险,有三个坑必须避开:

第一个坑是删了文件但空间不释放。很多日志文件正被进程持有句柄,你rm掉之后,文件系统里看不到这个文件了,但进程还在写,空间要等进程关闭文件句柄才真正释放。正确做法是用truncate清空文件内容而不是删除文件:

truncate -s 0 /var/lib/docker/containers/xxx/xxx-json.log

第二个坑是不能乱删 overlay2 里的东西。overlay2 目录下面的 layer 和容器可写层,跟运行中的容器强关联,直接删会导致容器异常甚至启动失败。能不碰就不碰。

第三个坑是 etcd 数据目录不能直接删文件。etcd 的数据文件有自己的格式和管理方式,你哪怕手贱删了某个snap文件,都可能直接破坏整个 etcd 集群的数据一致性。要清理 etcd 的空间,得靠它自己的 compaction 和 defrag,不是靠rm。

4. 紧急恢复实操:三步把集群从死亡线上拉回来

4.1 止血:优先释放根分区空间

当时的第一目标是让根分区从 100% 降到 80% 以下,给 etcd 一点喘息的空间。

优先处理最安全的日志文件,因为日志数据是纯文本,丢一点不影响业务完整性(顶多查不到历史日志)。

容器日志逐个清:

find /var/lib/docker/containers -name "*-json.log" -type f -exec truncate -s 0 {} \;

journald 日志做一次收缩清理:

journalctl --vacuum-size=500M

这条命令会把老的 journal 日志删到只保留最近 500MB,在清理日志的同时不影响 systemd 服务的当前日志查看。执行完后再看:

df -h

只要根分区从 100% 掉下来,尤其是留出至少 10%-20% 的空余空间,etcd 就有机会恢复写入。

这里还要补一个操作:如果发现/var/log或者/var/lib/docker是单独挂载的分区,而满的是根分区,要把/var/log下的历史归档文件也清理一下,比如log.1、压缩包之类。还有/tmp目录下的临时文件,可以安心清理。

4.2 复苏:让 etcd 和 API Server 恢复读写

磁盘空间释放后,要观察 etcd 是否自动恢复。先用 etcdctl 再看一次健康状态,如果还是 unhealthy,考虑重启 etcd 服务:

systemctl restart etcd

这里有一个动作我是强烈建议先做再重启的:检查 etcd 的磁盘空间告警是否被触发过。

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem alarm list

如果返回 NOSPACE,单纯重启不会消除告警,需要执行:

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem alarm disarm

然后再重启 etcd。

之后重启 kube-apiserver:

systemctl restart kube-apiserver

并且立刻看端口:

ss -lntp | grep 6443

正常情况下,这时候 6443 应该恢复监听。再用本机 curl:

curl -k https://127.0.0.1:6443/healthz

返回ok,说明 API Server 已经活过来了。

此时在 Master 节点上执行:

kubectl get nodes

如果节点状态还是 NotReady,大概率是 kubelet 的心跳积压导致的,需要手动重启每个 NotReady 节点上的 kubelet:

systemctl restart kubelet

4.3 恢复应用:等 Pod 调度和 KubeSphere 控制台

API Server 和节点都恢复正常后,接下来就是让 Kubernetes 自己把状态收敛回来。

用kubectl get pods -A看一下,会发现不少 Pod 处于Pending、ImagePullBackOff或者OOMKilled状态。Pending 的原因多半是节点资源不足或者污点未消除;ImagePullBackOff 可能是磁盘空间不够导致镜像拉取失败。

重点检查 KubeSphere 相关组件:

kubectl get pods -n kubesphere-system

ks-apiserver、ks-controller-manager、ks-console 这几个 Pod 是控制台的骨干,确认它们经过重启后处于 Running 状态。然后再访问一次 30880 端口,控制台就能正常打开。

这里要注意,KubeSphere 控制台的 Session 存储、Redis 缓存如果也被磁盘满影响,会出现能打开但提示异常的情况,一般重启对应 Pod 就能自愈。个别情况下需要清掉浏览器端的 Cookie,重新登录。

4.4 现场救活后的第一个动作:备份所有组件状态

集群恢复运行后,我最想提醒你的是:别急着收工,立刻对关键配置做一次快照备份。

哪怕集群只有一个 Master,也建议用 etcd 的快照功能备份一次完整状态:

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem snapshot save /backup/etcd-snapshot.db

同时把所有静态 Pod 配置、systemd 服务文件、证书目录做一次备份,用 tar 打包存放。这个动作成本很低,但能让你在后续操作里多一道保险。

5. 预防复盘:磁盘告警与日志轮转的落地配置

5.1 为什么监控里有磁盘指标,却没拦住这场事故

故障解决后,我让同事去查监控平台,发现磁盘使用率其实早就超过 90% 了,但告警阈值设的是 95%,而且告警只发到了某个没人看的群里,错过了抢救窗口。

这其实比故障本身更值得警惕。磁盘空间不像流量突增那样“秒级变化”,它是一个缓慢侵蚀的过程。真正有效的做法是分层设防:

层级阈值动作
预警磁盘使用率 70%检查大文件,启动日志压缩
告警磁盘使用率 85%立即通知运维值班人员
严重磁盘使用率 92%自动清理可安全回收的日志和临时文件
紧急磁盘使用率 97%页面值班 + 电话告警,强制人工介入

如果你的监控平台支持节点级告警,那就把node_filesystem_avail_bytes或者类似指标接进来,至少要把告警发给两个以上的人,不能只挂一个接受人。

5.2 Docker 容器日志轮转配置

这是所有 K8s 集群里性价比最高的配置之一。在/etc/docker/daemon.json里加上:

{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "5" } }

max-size=100m表示单个容器日志文件最大 100MB,max-file=5表示最多保留 5 个文件,超过就轮转。注意,这个配置对已存在的容器不生效,需要重启容器或重启 Docker 才能生效,所以最好是配合发布流程逐步滚动重启。

5.3 systemd 日志限制

在/etc/systemd/journald.conf里:

SystemMaxUse=500M MaxFileSec=7day

然后重启 journald:

systemctl restart systemd-journald

这样 journal 日志会被限制在 500MB,超过 7 天的自动清理。这个配置我们自己集群都加了,效果立竿见影,实测几个月没再出现根分区被 journald 塞满的情况。

5.4 etcd 的压缩与碎片整理

etcd 的存储空间问题,除了物理磁盘满,还有一个隐藏坑:逻辑空间不够。如果你看到报错是mvcc: database space exceeded,但df -h显示磁盘还有空间,那八成是 etcd 的 compaction 策略设置得太保守,历史版本太多。

推荐开启自动压缩:

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem compaction --physical 600000

这样每10分钟的历史版本会被压缩。压缩后数据库内部还有碎片,需要 defrag 才能把空间真正还给磁盘:

ETCDCTL_API=3 etcdctl --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem defrag

注意:defrag 会阻塞 etcd 的写能力,建议在业务低峰期执行,或者逐个节点轮流执行。

6. 常见问题排查速查与个人心得

6.1 故障排查速查表

现象排查命令常见原因处理建议
kubectl connection refusedss -lntp | grep 6443API Server 进程未运行查看 kube-apiserver 日志
API Server CrashLoopBackOffjournalctl -u kube-apiserver -n 200etcd 连接失败 / 磁盘满先恢复磁盘空间
etcd 报 database space exceededetcdctl alarm listetcd 历史版本积压compaction + defrag
节点 NotReadyjournalctl -u kubelet -n 100kubelet 心跳中断重启 kubelet
控制台 502kubectl get pods -n kubesphere-systemks-apiserver 异常查看并重启相关 Pod
磁盘满了但不见大文件df -iinode 耗尽清理小文件数量

6.2 个人踩坑记录:几个“早知道”的瞬间

第一次遇到这种故障时,我犯了一个错误:一直盯着防火墙和负载均衡排查,因为在界面上看到的是Connection Refused。后来被有经验的同事点了一句:“本机 curl 一下 healthz 看看”,这才少走了半小时弯路。现在我的习惯是遇到 refused 永远先做本机探活,能瞬间区分“网络层问题”和“服务层问题”。

第二个踩过的坑是直接对 json 日志文件执行rm。当时节点上的 Docker 还在持续向这个文件写日志,删了之后空间没有立刻释放,我一度以为清理没生效,折腾了半天才想起来是文件句柄没释放,被迫用truncate -s 0解决。如果当时用lsof | grep deleted查看,一眼就能发现问题。

第三个印象深刻的教训,是那次以为随便删个 etcd 的备份文件不影响什么,结果导致一个节点上的 etcd 数据不一致,花了更长时间来恢复。k8s 控制面的文件,尤其是 etcd 目录下的东西,最好只通过官方工具操作,绝不要手痒直接rm。

6.3 最后再分享一个实用小习惯

现在我管理的每个集群,都会在节点上部署一个简单的巡检脚本,每 15 分钟检查一次根分区和 etcd 分区的磁盘使用率,超过 85% 就往运维群的机器人推一条消息。这个脚本大约 30 行,效果却比很多重量级监控系统更直接。

判断集群健康不能只看“网页能打开”,磁盘、网络、etcd、kubelet 任何一个环节出了小问题,都可能演变成控制面雪崩。先看磁盘,再看 etcd,最后才查网络,这个顺序在排查 Kubernetes 连接类故障时基本不会错。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 23:48:10

梦幻花园调研:三消+建造闭环与关卡难度曲线设计

1. 这次调研的起点:为什么盯着梦幻花园不放 做游戏调研这些年,我有个习惯:每隔一阵子,就会挑一款在榜单上待了很久、但你平时未必会认真玩的产品,强迫自己以普通玩家的身份,从零开始体验一遍。不是看它的活…

作者头像 李华
网站建设 2026/10/14 23:47:22

前后端分离验证码设计:Redis原子消费与防刷最佳实践

前后端分离做了三四年,验证码这块我一直觉得是“细节多、坑不少”的活儿。最近在给一个内部管理系统做登录改造时,又把这套东西从头到尾捋了一遍,包括图形验证码的生成、存储、校验,以及前端组件的封装,过程中踩了不少…

作者头像 李华
网站建设 2026/10/14 23:46:43

ERP进销存系统源码实战:MySQL表设计、库存流转与部署避坑

简介:这是一套适用于中小企业业务流程信息化建设的开源ERP进销存系统源码包,整合采购、销售、库存与财务模块,并基于MySQL提供数据支撑。系统采用Web前后端交互架构,源码可自由二次开发,适合Java、JSP方向学习者深入理…

作者头像 李华
网站建设 2026/10/14 23:45:36

昇腾AI训推一体化:打通大模型从训练到上线的最后一公里

大模型落地的过程里,很多团队都卡在同一个地方:训练环境和推理环境完全割裂。模型在训练集群上跑得好好的,到了推理阶段又要重新适配硬件、重做算子优化,甚至因为显存和算力分配不合理,导致训练完的模型迟迟上不了线。…

作者头像 李华
网站建设 2026/10/14 23:45:25

Axure RP CRX 0.6.3 Chrome插件:让本地原型交互不失效

简介:这是一份面向原型设计师、产品经理及前端开发者的 Axure RP Chrome 插件资源包,版本为 0.6.3。Axure RP 常用于快速构建线框图和交互原型,而通过这款 CRX 插件,可将原型预览直接嵌入谷歌浏览器,省去在编辑器和浏览…

作者头像 李华
网站建设 2026/10/14 23:45:21

Windows下Apache Superset安装汉化全攻略:避坑指南与实践

简介:基于Windows环境的SuperSet安装与汉化说明文档,面向需要快速搭建BI可视化平台的数据分析师、开发工程师及运维人员。文档以步骤化方式,覆盖Python/pip依赖准备、pip install superset、管理员账号创建、数据库升级初始化、示例数据加载、…

作者头像 李华