敲下kubectl get node,终端回你一句The connection to the server 192.0.2.10:6443 was refused - did you specify the right host or port?,这个场景我经历过不止一次。第一次看到它的时候,我的第一反应是去重启 kubelet,结果重启完还是没有用;第二次我学乖了,先看端口,发现是 kube-apiserver 自己没起来;第三次是在一套高可用环境里,问题根本不在控制面,而是客户端 kubeconfig 指到了一个已经下线的地址。同样是这一行字,背后的根因可以差出十万八千里。
这篇内容讲的就是6443 端口拒绝连接这件事的完整排查方法。它面向的是已经能日常使用 k8s、但一遇到控制面故障就有点发怵的同学,也适合刚接手集群运维、需要一套可复现排查链路的人。我会把这条报错拆成"它到底在说什么"、"链路怎么分段验证"、"我自己踩过的一个真实坑怎么复盘"、"六类高频根因怎么对症下药"、"恢复之后怎么加固"以及"几个特别容易误判的现象"这几个部分,所有命令都可以直接抄去用。看完你至少能做到:下次再看到这行字,不会盲目重启,而是知道第一步该敲什么。
1. 报错信息本身在说什么:6443 拒绝连接的三层含义
很多人把这行报错当成一个笼统的"集群挂了",其实它信息量很大。connection refused是操作系统层面的一个非常明确的信号:TCP 三次握手的第一包到达了目标主机,但目标主机上没有进程在监听那个端口,于是内核回了一个 RST。换句话说,网络大概率是通的,问题出在"没人接电话"。这个判断非常重要,它直接把你从"查网络"这条岔路上拽了回来。
1.1 先把 refused 和 timeout 分清楚
这两个词在排查里是完全相反的方向,我见过太多人混着看。
| 报错形态 | 底层发生了什么 | 优先排查方向 |
|---|---|---|
| connection refused | 目标主机回了 RST,端口无监听或被策略拒绝 | 进程是否存活、端口是否在听、本机策略 |
| i/o timeout / dial tcp ... timeout | 包被丢弃,没有任何回应 | 路由、安全组、中间设备、后端健康状态 |
| no route to host | 链路层/路由不可达 | 网段、网关、网卡状态 |
| x509: certificate has expired | TLS 握手阶段失败 | 证书有效期、系统时间 |
| EOF / connection reset by peer | 握手后被对端断开 | apiserver 负载过高、连接数打满 |
refused来得特别快,几乎是瞬间返回,这也是一个体感上的判断依据:如果命令卡了十几秒才报错,那多半是 timeout 而不是 refused,哪怕报错文案看起来相似。这个"快慢"的体感,在只有一台跳板机、没有监控可看的时候特别管用。
1.2 这条报错出现在哪一侧,决定了你该查哪里
同一个错误字符串,出现在不同组件日志里,指向的责任方完全不同。这一点是我认为最值得先建立起来的认知。
- 出现在你本地终端的 kubectl 输出里:先怀疑客户端到服务端的路径,包括 kubeconfig 里的地址、跳板机到控制面的网络、以及控制面本身。
- 出现在某个节点的 kubelet 日志里:说明这个节点连不上 apiserver,此时集群里其他节点可能是好的,属于"局部失联"。
- 出现在 controller-manager 或 scheduler 日志里:通常意味着控制面内部通信出问题,比如它们连的是
kubernetes.default.svc,而 Service 背后的 Endpoints 空了。 - 出现在监控采集端或某个 Operator 的日志里:往往只是那个组件的 kubeconfig 或 ServiceAccount 出了问题,集群本身可能是健康的。
所以我的习惯是,看到这行字先问一句:这句报错是从谁的嘴里说出来的。如果是同事贴过来的截图,我一定会追问他是从哪台机器、哪个目录下执行的。
1.3 kubeconfig 里的 server 地址是怎么被选中的
排查客户端问题时,搞清 kubectl 到底读了哪份配置,能省掉大量无用功。kubectl 查找配置的优先级大致是:命令行--kubeconfig参数 >KUBECONFIG环境变量(多个路径用冒号分隔,会做合并)> 用户目录下的~/.kube/config。很多人机器上同时存在好几份配置,切换环境时忘了改环境变量,就会出现"我明明修好了集群,本地还是连不上"的假象。
# 看当前实际生效的配置来源和聚合结果 kubectl config view --raw # 只看当前上下文指向的服务端地址 kubectl config view -o jsonpath='{.clusters[?(@.name=="'"$(kubectl config current-context)"'")].cluster.server}' echo "$KUBECONFIG"还有一种更隐蔽的情况:配置里写的是域名或者负载均衡的虚拟地址,而不是某一台 master 的真实 IP。这时候refused可能来自中间那层负载均衡设备,而不是 apiserver 本身。判断方法是拿配置里的地址和实际控制面节点 IP 逐一比对,如果对不上,问题就不在 k8s 里。
2. 把链路拆成四段:从客户端一路验证到 etcd
我现在的做法是把整条链路固定切成四段,从外往里一段一段过。这样做的最大好处是:即使你对这套集群不熟,也能在十分钟内把范围缩小到某一层,而不是在各个组件之间来回猜。
2.1 第一段:客户端到节点,包能不能到
先用最朴素的方式确认网络可达性和端口状态。这里注意,nc的成功与否比ping更有意义,因为很多环境是禁 ICMP 但放行 TCP 的。
# 确认目标主机可达 ping -c 2 192.0.2.10 # 确认 6443 端口是否可建立 TCP 连接(-w 3 表示 3 秒超时) nc -vz -w 3 192.0.2.10 6443 # 直接打健康检查接口,绕过证书校验 curl -k -m 5 https://192.0.2.10:6443/healthz/healthz这个接口特别值得记住:apiserver 活着的时候它会返回ok,而且不需要认证。如果nc通了但curl返回的是证书错误或者 401,说明 apiserver 是活的,问题在别处;如果nc直接 refused,那就往下走第二段。另外要注意执行位置——在跳板机上 refused,不等于在 master 本机上也 refused,很多时候是中间的安全策略只对特定网段放行。
2.2 第二段:本机上 6443 到底有没有人在听
登上目标节点,直接看端口监听情况。这一步是分水岭:有监听说明 apiserver 进程活着,问题在它内部或下游;没监听说明进程根本没起来,或者刚刚退出。
# 看 6443 是否处于 LISTEN 状态,以及是哪个进程 ss -lntp | grep 6443 # 老系统没有 ss 时用 netstat netstat -lntp | grep 6443如果没有输出,就往容器层面看。kube-apiserver 在主流部署方式里是一个静态 Pod,由 kubelet 直接根据/etc/kubernetes/manifests目录下的清单文件拉起,不经过 Deployment 那一套。这意味着:kubelet 不工作,apiserver 就起不来;apiserver 起不来,kubectl 就什么都干不了。这是一个很典型的循环依赖,也是新手最容易懵的地方——你想用 kubectl 去修集群,但集群正好坏在 kubectl 依赖的那个组件上。
2.3 第三段:kube-apiserver 这个静态 Pod 是死是活
这一步是核心。静态 Pod 的生命周期由 kubelet 管,所以查的顺序是:先看 kubelet 服务本身,再看容器运行时里有没有对应的容器,最后看容器日志。
# kubelet 自身状态,重点看是不是 active (running) systemctl status kubelet --no-pager -l # 最近的 kubelet 日志,静态 Pod 起不来时这里会有明确原因 journalctl -u kubelet -n 200 --no-pager # 通过容器运行时直接看控制面容器,不依赖 apiserver crictl ps -a | grep -E 'kube-apiserver|etcd|kube-controller|kube-scheduler' # 看某个容器的日志,容器 ID 从上面命令拿 crictl logs --tail 100 <container-id> # 确认静态 Pod 清单文件还在、内容没被改坏 ls -l /etc/kubernetes/manifests/crictl ps -a里的-a很关键,因为崩溃的容器在ps里是看不到的,只能靠-a看到 Exited 状态。我见过好几次"apiserver 明明配置没动",结果-a一查发现容器在反复重启,日志里写着 etcd 连接超时——问题其实在更下面一层。
2.4 第四段:apiserver 背后的 etcd 和证书
apiserver 不是孤岛,它强依赖 etcd 和一堆证书。这两样出问题,表现都是 apiserver 起不来或者起来就崩,端口自然也就不听。
# etcd 健康状态,注意要带上证书参数 etcdctl --endpoints=https://192.0.2.10:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ endpoint health --cluster # 看 apiserver 证书什么时候到期 openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -subject -dates # 看系统时间是否漂移,证书校验强依赖这个 timedatectl证书这块有个特别容易被忽略的点:证书是按域名和 IP 签的。如果你给控制面换了 IP,或者给集群加了新的访问入口,而 apiserver 证书的 SAN 里没有这个新地址,那么客户端就会报证书错误而不是 refused。这两类报错要分清,否则会往完全错误的方向查。下面这张表是我自己常用的对照表,四段走完基本能定位到层:
| 检查项 | 正常表现 | 异常时最可能的根因 |
|---|---|---|
nc到 6443 | 立即成功 | 网络策略、路由、节点宕机 |
ss -lntp | grep 6443 | 有 LISTEN 和进程号 | apiserver 未启动或被 OOM 杀掉 |
crictl ps -a | 控制面容器 Running | 容器运行时故障、静态 Pod 被驱逐 |
curl -k /healthz | 返回 ok | 证书、准入控制、下游 etcd 异常 |
etcdctl endpoint health | 全部 healthy | 集群失去多数派、磁盘慢、时间漂移 |
3. 一次完整复盘:磁盘水位把控制面整个带走了
这一段是我印象最深的一次。当时的情况是:一套自建的小规模集群,某天开始所有节点陆续变成 NotReady,用 kubectl 一敲就是The connection to the server ...:6443 was refused。第一反应当然是登上 master,结果发现 master 上ss -lntp | grep 6443什么都没有,crictl ps -a里 kube-apiserver 是 Exited 状态,日志最后几行是写入失败。
3.1 症状表现为什么这么"全面"
这次故障的表现很有欺骗性:不是某一台节点异常,而是整个集群同时失联。表面上看起来像网络大面积故障,但实际上是因为所有节点的 kubelet 都要向 apiserver 上报状态,apiserver 一挂,全部节点自然就 NotReady 了。所以判断一个集群故障时,"全挂"反而是一个好消息,它说明根因很可能是单点:控制面。
顺着这个逻辑往下推:控制面单点挂了,而控制面是由 kubelet 拉起的静态 Pod,那么 kubelet 本身大概率是活的(否则你根本看不到容器 Exited 记录),问题就更可能出在 kubelet 启动容器的条件上。这个推理链条我建议你记下来,它能帮你跳过一堆无关的猜测。
3.2 从 kubelet 日志里挖出真正的拒绝原因
journalctl -u kubelet的输出里,关键信息往往藏在靠后的位置。这次我看到的是类似"failed to create container"、"no space left on device"这样的字样,同时还伴随一些驱逐相关的记录。到这里基本就锁定了:磁盘满了。
继续挖,看是哪个分区满:
df -h df -i # 按目录大小排序,找出真正的空间大户 du -sh /var/lib/containerd/* 2>/dev/null | sort -rh | head -20 du -sh /var/log/* 2>/dev/null | sort -rh | head -20结果/var/lib/containerd和/var/log都在告急,根分区使用率 100%。df -i顺便还能看一下 inode 是否耗尽,这是一个比磁盘容量更隐蔽的坑:容量还有,但小文件太多把 inode 用光了,表现和磁盘满是几乎一样的。
3.3 根因链条是怎么串起来的
这里值得把因果讲透,因为它解释了为什么"清一下磁盘就好了"是不够的。链条大致是这样:
- 日志和镜像长期堆积,把根分区写到 100%。
- kubelet 检测到磁盘压力,进入 disk pressure 状态,开始做镜像回收和 Pod 驱逐。
- 静态 Pod 虽然不受调度器管,但 kubelet 在资源不足时同样会放弃重建它们,或者重建后容器立刻因写入失败而退出。
- kube-apiserver 容器退出,6443 端口消失。
- 所有节点上报失败,全集群 NotReady;而你手里的 kubectl 打过去直接被 refused。
理解这条链以后,处置思路就很清楚了:先腾出空间,再让 kubelet 把静态 Pod 拉起来,最后验证链路。顺序不能反,空间没腾出来就去重启 kubelet,只会再多一次失败记录。
3.4 处置动作与验证顺序
清理动作要克制,我个人有一条底线:在控制面不健康的时候,不要顺手删未知目录、不要动 etcd 数据目录。只清明确可以再生的大户。
# 把系统日志压缩到只保留最近 200M journalctl --vacuum-size=200M # 清理容器运行时里无标签的悬空镜像和已停止容器 crictl rmi --prune # 删除日志目录下明确是历史轮转的旧文件(先看再删) find /var/log -type f -name "*.log.*" -mtime +7 -ls清理完先确认水位降下来了(建议留出至少 15%~20% 余量再操作),然后重启 kubelet,观察静态 Pod 重建:
df -h / systemctl restart kubelet # 持续观察,直到看到 apiserver 容器 Running watch -n 2 'crictl ps | grep kube-apiserver' # 另一路验证,不要只信一个指标 curl -k -m 5 https://192.0.2.10:6443/healthz kubectl get cs 2>/dev/null || kubectl get --raw='/readyz?verbose'这次我是先看到/healthz返回 ok,再敢去敲 kubectl 的。经验上,/healthz通了但 kubectl 报错,多半是认证或 kubeconfig 的问题;两者都通,才算真正恢复。恢复之后我立刻做了一件事:给磁盘水位加了告警阈值,80% 提醒、90% 警告,这个动作后面救过我第二次。
4. 六类高频根因,对照着往下查
同样是 6443 refused,绝大多数场景都能归到这六类里。我把它整理成一张对照表,方便你先定性再定量,避免漫无目的地翻日志。
| 根因类别 | 关键判别特征 | 处置要点 |
|---|---|---|
| kubelet 未运行或反复重启 | systemctl status kubelet非 running | 先修 kubelet 自身配置或依赖,再谈 apiserver |
| 证书过期或时间漂移 | 日志出现 x509 相关错误 | 校准时间、续期证书、重启控制面 |
| etcd 失去多数派 | endpoint health部分或全部不健康 | 优先恢复 etcd 副本,别急着删数据 |
| 端口被占用或安全策略拦截 | ss显示被其他进程占用 | 找出占用者,或调整放行规则 |
| kubeconfig 指向错误地址 | 本机 curl 正常但 kubectl 报错 | 核对 server 字段与当前控制面地址 |
| 高可用后端健康检查失败 | 直连单台正常,走入口异常 | 检查入口层的健康探测配置 |
4.1 kubelet 自身不工作:最常见也最容易被跳过
很多人一上来就查 apiserver,忘了静态 Pod 的"母体"是 kubelet。kubelet 起不来的原因又特别杂:配置文件语法错误、容器运行时套接字路径不对、--kubeconfig指向的证书过期、cgroup 驱动不匹配等等。
systemctl status kubelet --no-pager -l journalctl -u kubelet -n 300 --no-pager | grep -iE 'error|failed|listen' # 检查 kubelet 配置语法 kubelet --config=/var/lib/kubelet/config.yaml --version 2>&1 | head这里的经验是:kubelet 的日志要往前看,不要只看最后几行。因为 kubelet 会周期性重试,最后几行往往是"重试失败的结论",真正的原因在更早的位置。我一般会往上翻到出现第一个 error 的地方为止。
4.2 证书过期:时间点特别有规律
证书问题有一个非常讨喜的特征:它往往发生在"某个整数周年"附近,比如一年、两年。所以如果这套集群已经跑了很久没动过,突然某天早上挂掉,我会优先怀疑证书。
# 批量看所有证书的到期时间,按到期日排序 for f in $(find /etc/kubernetes/pki -name "*.crt"); do echo "$(openssl x509 -in "$f" -noout -enddate | cut -d= -f2) $f" done | sort同时别忘了系统时间。虚拟机长时间运行出现时钟漂移是常事,一旦时间偏差超过证书容忍的窗口,TLS 校验就会失败,而报错信息有时候并不会直白地告诉你"时间不对"。timedatectl看一眼 Current time 和 synchronized 状态,两秒钟的事。
4.3 etcd 失去多数派:最需要冷静的一类
三节点 etcd 挂掉两个,集群就写不了了,apiserver 会因为无法访问 etcd 而反复崩溃或拒绝服务。这类故障最危险的不是挂,而是处置时的误操作。我见过有人一看 etcd 不健康,直接把数据目录删了重建,结果数据全没了。
判别方法很直接:
etcdctl --endpoints=https://192.0.2.10:2379,https://192.0.2.11:2379,https://192.0.2.12:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ endpoint status --cluster -w table如果只是单个节点掉队,优先做的是把这个节点的 etcd 拉回来(检查磁盘、时间、网络),而不是动其他健康节点。多数派还在的时候,一切操作都要以"不减少健康成员数"为原则。
4.4 端口被占用与安全策略拦截
ss -lntp有输出但进程名不是 apiserver,说明被别人占了。这种情况在装了多个运行时或做过端口映射的机器上偶有发生。另外,本机防火墙或上游安全策略只放行了部分网段,表现是"某些机器能连、某些不能",用refused还是 timeout 可以辅助判断——通常是 DROP 导致 timeout,REJECT 导致 refused。
4.5 kubeconfig 指错地址:最冤的一类
这类问题排查起来最浪费时间,因为集群明明是好的。典型场景:控制面 IP 变更过、旧配置留在本地、环境变量里塞了另一套配置。我现在的固定动作是先跑一遍kubectl config view --raw,把 server 地址打出来,跟实际控制面地址对比。这一步花十秒,能省半小时。
4.6 高可用架构下的入口层问题
如果你访问的是虚拟地址或负载均衡入口,那么"直连某台 master 正常、走入口异常"就说明问题在入口层。重点看两件事:健康探测是不是探测了错误的路径或端口;后端列表里是不是所有节点都被摘掉了。这类问题在控制面重启期间特别常见,因为探测失败会导致后端被批量摘除,而摘除之后即使 apiserver 恢复了,也需要一段时间才能重新加回去。
5. 恢复之后该做的事:把这次的教训固化下来
故障恢复那一刻是人最松懈的时候,但恰恰是最该动手的时候。我这几年形成的习惯是:每次处理完这类问题,必须留下至少一条能自动报警的规则或一份能复用的脚本,否则下次还得重新踩一遍。
5.1 备份与到期巡检放在最前面
etcd 快照是控制面的最后一道保险,尤其在你要做任何"看起来很简单"的操作之前。我的做法是把它做成定时任务,每天一次,保留最近若干份,并且定期做一次真实恢复演练——没演练过的备份,约等于没有备份。
# 打一份快照,落盘前先确认目录空间充足 ETCDCTL_API=3 etcdctl --endpoints=https://192.0.2.10:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-$(date +%F-%H%M).db etcdctl snapshot status /backup/etcd-$(date +%F-%H%M).db -w table证书巡检则更简单,写个每天跑一次的脚本,把 30 天内到期的证书挑出来发通知。这件事的成本极低,收益极高。
5.2 给控制面加"外部视角"的拨测
只靠集群内部的监控是不够的:集群整体挂掉时,内部监控往往也一起挂了,没人给你发告警。所以需要一个不依赖被监控对象的拨测,从外部定期请求/healthz或直接探测 6443 端口,连续失败就报警。这个探针跑在哪都行,一台独立的小机器就够,关键是它和集群不能是同一套依赖。
5.3 磁盘与 inode 双指标阈值
那次磁盘满的教训让我把阈值设得比较保守:根分区和/var/lib各自独立监控,容量 80% 预警、85% 告警,inode 使用率同样设阈值。这两条规则比事后清磁盘便宜太多。顺带一提,日志轮转策略也要一并确认,很多环境的默认轮转配置是按天切分但不限总量,跑上几个月就是几十个 G。
5.4 把四段检查写成一个脚本
这是我最推荐的一个动作。把前面那四段检查固化成一个只读脚本,出事时一条命令跑完,输出一份体检报告,人不用在紧张状态下回忆命令。
#!/usr/bin/env bash # 只读体检脚本,不做任何变更操作 API_HOST="${1:-192.0.2.10}" API_PORT="${2:-6443}" echo "=== 1. 网络与端口可达性 ===" nc -vz -w 3 "$API_HOST" "$API_PORT" echo "=== 2. 本机监听情况 ===" ss -lntp | grep "$API_PORT" || echo "未发现监听" echo "=== 3. 控制面容器状态 ===" crictl ps -a 2>/dev/null | grep -E 'kube-apiserver|etcd|kube-controller|kube-scheduler' echo "=== 4. 健康接口 ===" curl -k -s -m 5 "https://${API_HOST}:${API_PORT}/healthz" || echo "健康检查失败" echo "=== 5. 磁盘水位 ===" df -h / /var/lib 2>/dev/null脚本刻意保持只读,避免在慌乱中误操作。真正需要变更加速的时候,人再手动介入。
6. 几个特别容易把人带偏的现象
排查过程中,有些现象看起来是线索,其实是干扰项。我把踩过的几个记在这里,省得你重走一遍。
6.1 端口通了,但 kubectl 依然报错
nc成功只证明 TCP 层能连上,不代表 apiserver 的业务逻辑正常。准入控制插件异常、etcd 响应极慢、连接数打满,都会出现"端口在听但请求失败"的情况。这时候要看的是curl -k https://ip:6443/readyz?verbose的详细输出,它会逐项告诉你哪个检查点没过,比/healthz的信息量大得多。
6.2 单台 master 恢复了,其他组件还在报错
控制面是多组件协作的,apiserver 起来了不代表 controller-manager 和 scheduler 立刻就好。它们有自己的选主和重连节奏,通常需要几十秒到几分钟。这个窗口期内看到零星的连接错误是正常的,不要因为看到报错就立刻再重启一轮——在恢复窗口内反复重启,是让故障时间变长的最快方式。
6.3 重启 kubelet 之后 apiserver 反而没起来
这个现象特别唬人。原因往往是:kubelet 重启后先做的不是拉起静态 Pod,而是做一轮状态同步和清理,如果此时磁盘还是紧的,或者容器运行时自己还没就绪,静态 Pod 就会启动失败。正确的顺序是:先确认容器运行时接受请求(crictl info能看到状态),再确认磁盘有余量,最后才重启 kubelet。跳过前两步,重启多少次都是同样的结果。
6.4 从节点的报错不代表从节点有问题
最后一条是思路层面的。当你在某台 worker 节点上看到连不上 6443,绝大多数情况下这台机器是无辜的,真正的问题在控制面。判断依据很简单:多台节点同时报同样的错,就去看控制面;只有一台报错,才去查这台节点的网络和 kubelet。这个"数量判断法"帮我省掉了太多在单机上瞎折腾的时间。
最后分享一个我个人的小习惯:每次对控制面动手之前,先在心里把"我只改了什么、改完之后我期望看到什么现象"说一遍,然后打一份 etcd 快照再开始。这套流程听起来有点繁琐,但它保证了我这几年没有因为手快把一套集群彻底弄丢过。6443 refused 这个报错本身不可怕,可怕的是在不知道根因的情况下反复重启,把一次十分钟的小故障拖成一小时的大事故。