咱搞K8s的人十有八九都撞见过这玩意儿Get http://localhost:10248/healthz: dial tcp 127.0.0.1:10248: connect: connection refused.第一次见的时候真有点慌。看这个报错前面一个Get http://localhost:10248/healthz后面一个dial tcp ... connect: connection refused直觉就是“有个服务没起来或者端口没监听”。但问题是这玩意儿到底是谁在访问10248端口又是干嘛的为什么明明集群里其他Pod都正常偏偏就这个节点上冒出这么个日志这些疑问不搞清楚你就算把节点重启十遍该报错还是报错。这篇文章就把这个报错从头到尾扒一遍先说清楚它背后的机制再给一套可以直接照着做的排查思路和修复步骤最后整理一些我实际踩过、见过的高频坑。1. 认识这个报错10248端口到底是谁在访问先别急着动手排查得先弄明白这报错是从哪儿冒出来的。你看这个报错的格式——Get http://localhost:10248/healthz这明显是有人定期往这个URL发HTTP请求然后连接被拒了。在K8s体系里干这活儿的角色只有一个kubelet更准确地说是kubelet自己带的那个健康检查探针。1.1 kubelet的“自检机制”是怎么回事Kubelet是部署在每一个K8s工作节点上的核心代理负责管理本节点上所有Pod的创建、运行、销毁。但节点本身的状态比如kubelet进程活着不代表它真的“健康”——可能kubelet进程在但内部某个关键goroutine死锁了或者容器运行时Container Runtime通讯断了。为了防止这种“假活”状态kubelet内置了一个healthzHTTP端点监听的地址就是节点的localhost:10248。Kubelet定期请求自己这个健康检查端点如果返回200说明自身状态正常如果返回非200或者连接失败kubelet会进入异常状态并且对外报告“节点不健康”。这个自检探针存在的作用说白了就一句话区分“进程活着”和“工作正常”这两件事。这就像一个人站在那儿不动你喊他一声他没反应那他就不能算“状态良好”。1.2 连接被拒的两种典型语义这个报错的字面关键词是connection refused在TCP层面它和timeout完全是两码事connection refused意味着请求发送到了目标机器但目标端口上没有服务在监听或者有防火墙直接回复了RST包。翻译成人话就是你敲门了但屋里没人应门或者门铃被拆了。timeout意味着数据包发出去了但石沉大海没有收到任何回应。可能是网络不通、防火墙丢包、或者IP根本不存在。所以看到connection refused重点怀疑方向就两个10248端口上根本没有kubelet在监听kubelet没启动、崩溃了、或者启动时就没启用这个健康检查。有东西把连接直接reset了很少见在本地回环地址上基本不会发生除非被特殊安全策略拦截。顺着这个方向大部分问题都能定位到kubelet服务本身的状态上。2. 排查思路从服务状态到端口监听逐层深挖绝大多数人看到这个报错的第一反应是去查Pod状态、查集群事件但在节点层面的报错优先关注的应该是节点本身的服务状态。我一般的排查顺序是这样的一层层往下挖。2.1 第一步先看kubelet进程状态和日志第一时间要确认的是这个节点的kubelet到底有没有在跑、跑得稳不稳。# 查看kubelet服务的运行状态 systemctl status kubelet # 查看最近几条kubelet日志 journalctl -u kubelet -n 100 --no-pager如果systemctl status kubelet显示的是active (running)但依然有连接拒绝的报错那就可能是kubelet启动了但健康检查端点没正常工作。继续看日志常见情况是kubelet反复启动失败处于activating (auto-restart)状态这种情况下它根本没来得及监听任何端口。换个更直接的验证方式——直接手动请求一下这个地址curl -v http://127.0.0.1:10248/healthz如果curl输出类似Connection refused说明kubelet确实没有监听在这个端口上。如果curl有响应且返回非200说明kubelet在监听但健康状态异常那要看kubelet日志里的具体错误。2.2 第二步确认10248端口监听的配置来源Kubelet的健康检查端口不是写死不变的它是通过启动参数--healthz-port来控制的。在较新的K8s版本里这个参数默认是10248。如果kubelet是通过systemd管理的配置文件一般在/etc/systemd/system/kubelet.service.d/目录下。检查一下有没有被覆盖grep -r healthz-port /etc/systemd/system/kubelet.service.d/这里我遇到过一种情况团队里的某个初始化脚本改了kubelet的启动参数把--healthz-port改成了别的端口比如10249或者干脆设成了0但集群内其他组件的探活配置还指向10248导致健康检查请求全部打在了一个没有监听的端口上。改配置前一定要先确认参数和请求方是不是对得上。2.3 第三步用ss检查端口监听状态如果kubelet的systemd状态是正常的再确认一下端口监听是否真实存在ss -tlnp | grep 10248正常情况下会看到类似这样的输出LISTEN 0 4096 127.0.0.1:10248 0.0.0.0:*注意这里的监听地址是127.0.0.1而不是0.0.0.0。健康检查端点设计时只允许本机访问不对外暴露这在安全上是有意为之的。如果ss输出了监听信息但curl还是报connection refused那就要考虑是不是有网络层拦截或者socket backlog耗尽之类的极端情况。但坦白说在本地回环地址上遇到后者的概率极低。3. 实操过程不同根因下的三类修复方案我在实际项目中遇到的连接拒绝报错归纳起来不外乎三类根因kubelet服务没起来、健康检查端口参数不匹配、容器运行时异常导致kubelet自检失败。下面挨个说解决方案。3.1 场景一kubelet反复重启服务起不来这是最常遇到的情况。先看日志找出kubelet启动失败的原因journalctl -u kubelet -f常见的启动失败原因有这么几类配置解析失败/var/lib/kubelet/config.yaml里某个字段的语法不对或者引用了不存在的文件路径。检查配置文件的语法或者临时用kubelet --config /var/lib/kubelet/config.yaml在前台启动一次看能不能跑起来。证书过期或节点身份无法注册kubelet启动需要向API Server注册节点身份如果证书过期或bootstrap token失效会一直卡在Unable to register node with API server。这种情况要检查节点上的证书有效期或者查看CSRCertificateSigningRequest状态。容器运行时没就绪kubelet启动时会检查容器运行时比如containerd的socket。如果/run/containerd/containerd.sock不存在kubelet会一直等报Failed to connect to containerd。这种情况要先启动containerd再启动kubelet。解决顺序很直接先修根因再重启kubelet。systemctl daemon-reload systemctl restart kubelet重启完再确认端口ss -tlnp | grep 10248 curl -v http://127.0.0.1:10248/healthz3.2 场景二健康检查端口被改动前文提过--healthz-port参数可以被覆盖。有些发行版或者自定义部署脚本会把健康检查端口改成一个自定义的值。如果你手头的集群里报错指向的是10248但kubelet实际起在另一个端口上修法就是拉平配置。改/etc/systemd/system/kubelet.service.d/下的配置文件把--healthz-port10248加进去[Service] EnvironmentKUBELET_EXTRA_ARGS--healthz-port10248然后systemctl daemon-reload systemctl restart kubelet这里有个细节容易忽略如果你改了systemd配置但/var/lib/kubelet/config.yaml里也配置了healthzPort那个字段会被优先使用。所以改配置之前先把这两处的值对一遍别改了systemd文件没生效查半天才发现是config.yaml里写了个别的端口。3.3 场景三containerd或其它运行时异常还有一种很隐蔽的情况kubelet在监听端口但每次自检都会因为跟容器运行时通信失败而报错不断重启导致端口被反复释放和占用。你刚好在释放的间隙去访问就会出现connection refused。这种情况的典型日志长这样Failed to get check for pid 123: rpc error: code DeadlineExceeded desc context deadline exceeded排查方向转向容器运行时systemctl status containerd journalctl -u containerd -n 100 --no-pager如果是containerd本身卡死或者socket异常重启containerd再重启kubeletsystemctl restart containerd systemctl restart kubelet我有个习惯凡是要排查这类节点自检类问题先把kubelet和运行时的时间线对齐。journalctl两边翻一翻看看到底是谁在等谁。3.4 配合验证从节点状态看问题是否闭环修复之后光看端口监听还不够要确认节点在集群视野里恢复正常kubectl get node 节点名 -o wide # 查看节点状态的条件详情 kubectl describe node 节点名看Conditions字段里的Ready是不是为True。很多时候节点状态从NotReady恢复成Ready会有一个延迟因为kubelet要连续几次自检通过才会更新状态。如果等了五分钟还没恢复那就还要继续查kubelet日志。4. 常见问题与高频坑实测中这些情况最容易迷惑人排查逻辑不复杂但实际操作时坑很多。这里整理几个我亲测踩过、或者帮别人排查时见过的高频问题每一类都值得记在小本本上。4.1 端口通了但curl返回401/403你可能会遇到一种情况ss显示端口在监听curl请求发过去却返回了401 Unauthorized或者403 Forbidden。这不是端口问题而是kubelet的健康检查端点增加了认证/鉴权控制。K8s版本更新后健康检查接口对匿名请求的响应策略会有变化。底层的connection refused问题其实已经解决了但别人一看日志里还有HTTP错误码就以为没修好。处理方式很简单明确这个端点不是用来给外部做健康探测的不要指望它返回一个干净的200。真正的对外健康检查K8s用的是--healthz-port和--healthz-bind-address对内节点状态上报靠的是kubelet通过安全端口10250向API Server汇报。如果只是为自己手动确认可以加--insecure之类的参数绕过认证但这不是治本的做法。4.2 kubelet日志里大量刷这个报错但节点状态正常另一个迷惑性很强的情况日志里一直在刷Get http://localhost:10248/healthz: dial tcp 127.0.0.1:10248: connect: connection refused但是kubectl get node显示的却是Ready业务Pod也正常运行。这背后可能的原因是kubelet内部有多个组件在做健康检查不只是用来自检还可能是kubelet内置的HTTP探针周期性访问这个端点。节点状态正常不代表所有探针都正常。另一个可能某个监控组件或者集群外的探活程序把10248加进了采集目标它们周期性访问这个端口但访问时机恰好是kubelet启动早期或者端口被占用期间。遇到这种日志先确认日志来源是kubelet自身还是其他组件往这里发的请求。如果是监控组件在采集配置里去掉这个端口即可。如果日志来自kubelet自身但节点状态正常多半是自检时间窗和端口启动时间有短暂错位属于启动过程中的正常抖动不用过度处理。4.3 同一节点上多份kubelet配置互相覆盖生产环境里节点初始化方式五花八门配置文件经常被不同工具多次覆盖。/etc/systemd/system/kubelet.service.d/目录下可能有多个.conf文件systemd会按字母顺序加载后面的覆盖前面的。我遇到过的情况一个初始化脚本写了20-extra-args.conf里面带了--healthz-port10249另有一个部署工具又写了90-custom.conf想改回--healthz-port10248。但systemd加载优先级是90排在20后面理论上90生效——结果那个部署工具的路径写错了改了一个不存在的文件实际生效的依然是10249。排查这类问题没有一个统一的命令能一步到位只能靠cat /etc/systemd/system/kubelet.service.d/*.conf把目录下所有配置全部列出来自己人工排优先级。看完了再决定改哪个。4.4 端口被占用导致kubelet起不来在极少数情况下10248端口会被别的进程占用。比如某次测试残留了一个监听在10248的进程或者监控agent误接管了这个端口。kubelet启动时绑定端口失败日志会直接报Failed to listen on ... address already in use而这个报错又会连带引发后续的健康检查连接拒绝。排查命令不复杂lsof -i :10248找到占用进程确认用途kill掉再重启kubelet。这个坑很基础但真遇到的时候人在着急状态下最容易忽略。4.5 容器运行时socket路径配置错误另一个隐蔽场景kubelet配的--container-runtime-endpoint和实际运行时socket路径不一致。比如机器上装的是containerd默认socket在/run/containerd/containerd.sock但kubelet启动参数给的是/var/run/dockershim.sock。此时kubelet反复连不上运行时自检不通过端口的生命周期也不稳定报出连接拒绝。修法很直接统一运行时端点路径。# 确认当前运行时socket路径 ls -l /run/containerd/containerd.sock # 检查kubelet配置里的runtime-endpoint cat /var/lib/kubelet/config.yaml | grep runtime改完配置再重启kubelet这类问题在迁移运行时从docker切换containerd时尤其常见。5. 该提升的警惕学会从底层机制判断问题严重度排查这个报错的时候我一直觉得比报错本身更重要的是你对这个机制的理解深度。同样一个connection refused出现在初始化阶段和出现在运行稳定期严重程度完全不一样。5.1 区分启动抖动和持续异常如果节点刚重启完kubelet还在启动流程中这个报错属于正常现象。kubelet从启动到监听端口的窗口期很短但在高负载节点上可能被拉长。如果节点已经运行了好几天kubelet一直稳定突然开始刷这个报错那就要高度警惕了。这说明kubelet可能正在崩溃边缘、或者正在被重启。我的判断标准很简单看这个报错是不是连续性的。偶尔一条不用管连续出现就要顺着日志往上翻看kubelet在此之前经历了什么。5.2 结合节点状态变化判断处理优先级节点从Ready变成NotReady伴随这个报错的话说明kubelet向API Server的心跳上报已经出问题了。这种情况影响面很大因为调度器会把该节点上的Pod视为不可用触发其它机制来进行恢复取决于你的工作负载控制器配置。优先处理级别很高。节点状态一直是Ready只有日志报错那多半是探针或监控抓取的问题按常规排查即可。5.3 不要盲目重启节点遇到这个报错很多人的第一反应是重启节点。我不建议这么做。节点重启的代价很高而且大概率治标不治本——如果根因是配置被覆盖重启完该错还是错如果根因是运行时socket路径配错重启前不改配置重启多少次都一样。正确的姿势是先看日志定位到具体根因再针对性修复。节点重启只有在一种情况下值得考虑——kubelet反复崩溃且无法正常启动且日志里没有任何有效线索时重启节点让所有服务重新初始化有时候能绕过一些奇怪的状态残留。但这也是下策。6. 从日志追踪到自动化巡检这套思路可以沉淀下来排查这个连接拒绝报错的过程本质上是“从一条日志逆推一个机制”的训练。时间久了很多经验是可以固化成工具的。6.1 沉淀一个简单的巡检脚本我可以分享一个我实际在用的小脚本逻辑——它做的事情很简单检查kubelet健康检查端口的连通性同时交叉确认kubelet服务状态判断问题级别。#!/bin/bash # 检查kubelet服务状态 KUBELET_STATUS$(systemctl is-active kubelet) # 检查健康检查端口连通性 curl -s -o /dev/null -w %{http_code} --connect-timeout 2 http://127.0.0.1:10248/healthz CURL_EXIT$? if [ $KUBELET_STATUS active ]; then if [ $CURL_EXIT -ne 0 ]; then echo kubelet active, but healthz endpoint unreachable. Could be early startup or config mismatch. exit 1 else echo kubelet active and healthz endpoint reachable. fi else echo kubelet is not active. Current status: $KUBELET_STATUS fi这个脚本可以作为节点巡检的组成部分结合监控平台里的告警规则把connection refused这类日志纳入异常检测能在问题变成故障之前先暴露出来。6.2 节点层面的日志集中收集多节点环境里单机日志查起来太痛苦。有条件的情况下建议给每个节点配日志采集器把journalctl -u kubelet的日志集中到一个日志平台。这样遇到一次性多节点同时报错的情况下不需要逐台登录直接在日志平台里搜healthz关键字按节点维度聚合一下子就能看出是单点故障还是全局性配置问题。6.3 配置变更要有审计和版本管理回看好几个常见的坑根源都指向同一个问题kubelet的配置变更没有受控。改一行配置、重启一次服务太随意了。我的建议是kubelet的配置文件纳入版本管理变更时记录变更内容、变更时间和变更人。这看起来只是个管理习惯但长期跑集群的人都知道配置漂移是生产环境里最阴险的敌人之一。总结绕了这么一大圈其实想强调的是Get http://localhost:10248/healthz: dial tcp 127.0.0.1:10248: connect: connection refused这条报错本身不复杂它就是kubelet健康检查端点连不上的一个表层信号。真正有价值的不是记住“看到这个报错就重启kubelet”而是理解这个报错在什么条件下会出现、背后对应着哪些可能的故障链路、每一类根因之间如何区分、以及修复之后怎么验证闭环。就我个人而言排查这类节点的自检报错最有用的习惯是拿到一条日志先问三个问题——这个请求是谁发的这个端口应该由哪个进程监听这个进程现在的状态是什么把这三个问题回答清楚了绝大多数节点级故障都能在十分钟内定位。如果回答不了第二个问题那说明你对当前环境的配置还缺少了解这时候急着改配置或者重启只会把问题弄得更乱。以后你再看到healthz相关的连接拒绝先别慌。看一眼kubelet日志确认服务和运行时状态再决定怎么动手。这套路数能帮你少走很多弯路。