Kubernetes端口不通排查:从telnet超时到Service与Endpoints链路 Pod 状态 Running日志干干净净容器里的进程明明在监听 8080外部 telnet 上去却只看到光标停住十几秒然后一句 Connection timed out 把它打发走——这个场景我见过太多次横跨 Java、Go、Python 各种服务横跨自建集群和托管集群。Kubernetes 中端口无法使用 telnet 连接表面看是网络不通实际上从客户端到容器进程之间横着至少五道关卡任何一道没对上表现都是同一句超时。这篇内容就把这条链路彻底掰开每一层的判定依据是什么、怎么用最短的命令定位到具体是哪一层出问题、以及那些文档里不会写的坑。适合刚接手集群运维的同学也适合写了几年业务但一直没搞懂 Service 到底怎么转发流量的后端开发按顺序读下来基本能建立一套自己的排查肌肉记忆。1. 先搞清楚telnet 不通到底卡在谁身上1.1 从客户端到容器进程的那条完整链路很多人一上来就kubectl get pods看到 Running 就认为服务没问题然后开始在容器里netstat找原因。这个顺序其实是反的。telnet 一个 IP 加端口数据包走的路是客户端发 SYN → 到达目标节点的网卡 → 被 iptables 或 ipvs 规则拦截改写如果目标地址是 Service 的 ClusterIP→ 转发到某个后端 Pod 所在节点 → 进入 Pod 的网络命名空间 → 命中容器内进程的 socket。这条链上任何一环做错了决定SYN 包就会被静默丢弃对端要么回 RST要么干脆不回表现出来就是 telnet 卡住直到超时或者秒被拒绝。区分超时和拒绝是排查的第一刀价值极高。收到 Connection refused说明包成功到达了目标机器但目标端口没有进程监听或者被显式的 REJECT 规则拒绝——问题在应用层或本机防火墙。收到 Connection timed out说明包在下游某个地方被丢弃了通常是路由、转发规则、安全策略层面。这两种现象指向的排查路径几乎不重叠先把这一刀切准后面能省掉的尝试少说一半。我自己习惯先在客户端用nc -zv -w 3 IP PORT或者telnet IP PORT做一次快速判定同时观察耗时。3 秒超时和 30 秒超时是两个不同的信号短超时往往意味着中间设备主动丢包长超时更像是路由黑洞。1.2 五层模型把问题拆到可以单独验证的粒度我一般把排查面拆成五层从外往里推客户端与网络可达性、Service 的转发配置、Endpoints 是否有效、Pod 网络与策略、容器内进程监听状态。每一层都有独立的验证命令验证通过就往下走一层不通过就在当前层深挖。这套顺序的好处是不需要靠猜每一步都有明确的是非判断。举个实际例子。某公司的订单服务在测试环境好好的上到预发集群后前端一直报连不上。第一层验证从集群内另一个 Pod telnet Service 的 ClusterIP超时从客户端 telnet NodePort也超时。这就把范围压到了集群内部。第二层kubectl describe svc一看Endpoints那一栏是空的。原因立刻清晰——Pod 的 readinessProbe 一直没通过Pod 不 Ready自然不会被塞进 Endpoints。改探针的initialDelaySeconds之后一切正常。整个过程不到五分钟因为没在错误的方向上乱试。注意排查顺序比排查工具重要。工具再好用错地方就是浪费时间。1.3 把能否 self-test作为第一道防线在怀疑转发之前一定要先在 Pod 内部自测端口是否真的可用。kubectl exec -it pod -- sh进去netstat -lntp或ss -lntp看监听地址和端口然后再curl 127.0.0.1:8080或者nc -zv 127.0.0.1 8080。这一步只要能通过就说明进程本身没问题问题一定在容器外部通不过说明应用压根没起来跟 Kubernetes 网络一点关系都没有别浪费时间去查 Service。这一步最容易被跳过因为大家默认日志没报错就等于起来了。但实际上进程因为端口冲突、配置文件路径错误、环境变量拼错而启动失败的情况太常见了尤其是在多副本滚动更新的场景下旧 Pod 还占着端口新 Pod 起不来滚动更新卡在 50% 在那儿转圈。2. Service 与 Endpoints端口映射里最容易错的那三个字段2.1 port、targetPort、nodePort 到底各自管什么这是我在带新人时反复讲的一点。一个 Service 里有三个跟端口相关的字段各司其职错一个就直接表现为 telnet 不通。portService 自身在 ClusterIP 上暴露的端口集群内部访问用的就是它。targetPort流量最终落到容器上的哪个端口必须是容器内进程真实监听的端口。nodePort每个节点上的宿主机端口范围默认 30000–32767供集群外部访问。关键在于port和targetPort完全可以不一样而且经常不一样。一个人写了apiVersion: v1 kind: Service metadata: name: order-api spec: selector: app: order-api ports: - name: http port: 80 targetPort: 8080 protocol: TCP type: ClusterIP这段的意思是集群内部通过ClusterIP:80访问转发到容器的 8080。如果容器其实监听的是 8000那 telnet Service 的 80 端口就会一直超时因为 SYN 被转发到一个没人监听的端口上了——注意通常是超时而不是拒绝这是集群网络里一个容易误导人的细节。还有一种更隐蔽的错法用了端口名字。targetPort: http这种写法要求 Pod 里对应的 containerPort 也叫http而且容器内的进程也必须监听这个名字背后代表的端口。名字对不上Endpoints 就可能拿不到正确的端口映射。2.2 Endpoints 为空九成是这几类原因kubectl get endpoints svc-name -n ns或者新版本的kubectl get endpointslices这是判断 Service 到底有没有后端的唯一权威手段。输出里ENDPOINTS那一列如果是none后面怎么查转发规则都是白费力气。导致 Endpoints 为空的原因我归纳过几类命中率从高到低现象成因验证方式ENDPOINTS 为 noneselector 的 label 跟 Pod 的 label 不匹配kubectl get pod --show-labels对比ENDPOINTS 部分缺失部分 Pod 未 Readykubectl get pods -o wide看 READY 列全部缺失且 Pod 是 Ready命名空间不一致检查 Service 与 Pod 是否同 namespace端口为 0 或缺失targetPort 引用了不存在的 containerPort 名字kubectl get pod -o yaml看 ports 段label 不匹配是经典事故。开发在 Deployment 上写app: order-api-v2Service 上还留着app: order-api改完镜像发版之后整个服务对外没反应界面报 502后端日志一条请求都没有。这类改动看着无害实际上直接把 Service 与 Pod 之间的关系切断了。readinessProbe 也会拖这个后腿。探针连续失败超过failureThreshold之后Pod 进入 NotReadyEndpoint 控制器会把它从 Endpoints 里踢掉。这时候 Pod 还是 Running日志还是正常的kubectl get pods也只是 READY 列显示0/1很容易被忽略。我一般会加一条kubectl get pods -o wide顺便看一眼重启次数和就绪状态比单看 Running 靠谱得多。2.3 用完整体检命令替代零散猜测与其一条条命令试不如准备一套能一次性把 Service 链路全部校验完的流程。我在实际工作中常用下面这个顺序# 1. Service 基本信息与端口映射 kubectl -n prod get svc order-api -o wide # 2. 后端 Endpoints 是否完整 kubectl -n prod get endpointslices -l kubernetes.io/service-nameorder-api # 3. 目标 Pod 的 label 与就绪状态 kubectl -n prod get pods -l apporder-api --show-labels # 4. 从集群内部起一个临时 Pod 直连测试 kubectl -n prod run nettest --rm -it --imagebusybox:1.36 --restartNever \ -- nc -zv -w 3 order-api.prod.svc.cluster.local 80第 4 步的临时 Pod 是关键它从集群内部发起连接跳过了外部网络、安全组、NodePort NAT 这些干扰因素能直接把问题锁定在集群内转发这一层。如果这一层通问题就在节点外部这一层不通就继续往 Endpoints 和容器内查。用 FQDN 而不用短名也是有讲究的。短名order-api只在 Pod 与 Service 同命名空间时才能解析跨命名空间会因为 search domain 的拼接顺序而解析失败表现出来也是 telnet 卡住——但根因是 DNS不是端口。这个细节后面还会展开。3. 容器侧监听绑错地址是隐形杀手3.1 0.0.0.0 与 127.0.0.1 的一字之差如果说 Endpoints 为空是最常见的问题那进程只绑了 127.0.0.1就是第二常见、也最难自己发现的问题。原因很简单在 Pod 内部curl 127.0.0.1:8080完全正常进程日志显示 listening on 127.0.0.1:8080一切看起来都挺好但只要外部流量进来就超时。这个原理不复杂。Pod 有自己独立的网络命名空间和 IP从外部包括 kube-proxy 转发过来的流量、同节点其他 Pod、节点上的进程进入这个命名空间的包目标地址是 Pod IP不是 127.0.0.1。如果进程的 socket 只听在 loopback 上内核收到目标为 Pod IP 的包之后找不到匹配的监听 socket直接丢弃telnet 端看到的就是超时。很多框架默认只听 localhost这是出于安全考虑的设计但在容器环境里必须显式改掉# 典型需要改成 0.0.0.0 或空字符串的启动参数 --host 0.0.0.0 --bind-address0.0.0.0 server.address0.0.0.0Node 生态的 express 用app.listen(port)默认就是全接口反而是 Go 里写http.ListenAndServe(127.0.0.1:8080, nil)或者 Java 里配了server.addresslocalhost会踩这个坑。我在实际排查里最常用的判定方式是kubectl -n prod exec -it order-api-xxx -- ss -lntp看Local Address:Port那一列。显示0.0.0.0:8080或*:8080或:::8080才是对的显示127.0.0.1:8080就是问题所在。IPv6 双栈集群里::通常也意味着同时接受 IPv4但::1只等于 loopback这个别搞混。提示在 Pod 内部用curl Pod IP:8080自测比curl 127.0.0.1:8080更能暴露绑定地址问题。前者失败后者成功几乎就是绑 loopback 了。3.2 探针配置反过来把服务探死了readinessProbe 的初衷是让不健康的后端不接流量但配置不当会变成一个自伤机制。我遇到过三种典型情况。第一种探针的initialDelaySeconds太短。Java 应用冷启动要四十秒探针 10 秒后开始探连续三次失败Pod 被标记 NotReady。这时候如果 Deployment 设了maxUnavailable: 0滚动更新直接卡死新版本一个都没起来老版本被逐步下线最后服务整体不可用。telnet 上去的体验就是时通时不通非常迷惑。第二种httpGet的 path 写错了比如探的是/health但应用实际暴露的是/actuator/health返回 404。404 在探针语义里算失败Pod 永远不 Ready。第三种更隐蔽探针用了host: 127.0.0.1或者没起作用但探针端口跟业务端口不是同一个探的是管理端口业务端口其实已经挂了。这种情况 Pod 一直 Ready但业务请求全都在超时。我的经验是探针配置要跟启动脚本一起 review尤其是initialDelaySeconds、timeoutSeconds、failureThreshold这三个参数的组合。对于启动慢的应用用startupProbe兜底比无限调大initialDelaySeconds更优雅——startupProbe 成功之前liveness 和 readiness 都不会执行等于给了一个专门的启动窗口。3.3 容器端口声明与实际的解耦陷阱有一个容易被误解的点containerPort在绝大多数情况下只是信息性声明它不会真的去监听端口也不会限制你能监听的端口范围。你在 Deployment 里写containerPort: 8080但进程监听的是 9000Pod 照样能跑只是 Service 的targetPort如果指向了 8080 就转发不到。更麻烦的是用端口名字的时候。如果 Pod 里写了ports: - name: web containerPort: 8080Service 里写targetPort: web那 EndpointSlice 里的端口就会取 8080。这时候如果进程实际上听在 9000你怎么 telnet 都不通而且kubectl describe svc里显示的端口看起来完全正确。这种情况我一般会强制要求团队在 Deployment 里保持containerPort与实际监听端口严格一致并且在 CI 阶段加个检查用一次性的容器启动测试来验证。4. 网络插件与策略层那些看不见的拦截点4.1 NetworkPolicy 是一把可以静默断网的双刃剑如果集群启用了支持 NetworkPolicy 的网络插件一条默认拒绝的规则能让整个命名空间的入向流量全断而且不报错、不打日志telnet 就是超时。kubectl get networkpolicy -n ns先看有没有策略存在有的话再kubectl describe看podSelector、policyTypes、ingress三段。常见误区是以为 policy 只对指定了 selector 的 Pod生效就万事大吉。实际上如果同一命名空间里有一条podSelector: {}的策略它匹配的是所有 Pod只要policyTypes包含Ingress那么所有入向流量默认拒绝除非其他规则显式放行。多个策略之间是叠加关系最终结果是所有策略允许范围的并集但默认拒绝这个基线来自任意一条策略的存在。放行规则里最容易漏的是 DNS。只放行了业务端口却忘了 53Pod 里应用启动时解析依赖的域名失败表现出来是各种奇怪的下游超时。我的习惯是在命名空间的默认策略里永远先放行 DNSapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress namespace: prod spec: podSelector: {} policyTypes: - Ingress --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns-egress namespace: prod spec: podSelector: {} policyTypes: - Egress egress: - ports: - protocol: UDP port: 53 - protocol: TCP port: 53注意NetworkPolicy 是白名单模型写一条拒绝所有很容易写对只放行需要的很难。上线前务必在同命名空间开一个测试 Pod 跑一遍连通性。4.2 kube-proxy 模式差异带来的排查盲区集群用 iptables 模式还是 ipvs 模式排查手段完全不一样。iptables 模式下 Service 的转发规则体现为KUBE-SERVICES到KUBE-SEP-*的一串链式跳转规则多的时候同步延迟明显大规模集群里新建 Service 后几十秒内不通是正常的。ipvs 模式下则是一张虚拟服务器表ipvsadm -Ln能看到每个 Service 的 VIP 和后端列表性能更好但要求内核模块齐全。判断当前模式kubectl -n kube-system get cm kube-proxy -o yaml | grep mode如果输出里没有mode或者显示空就是默认的 iptables。排查时可以到节点上直接看# iptables 模式 iptables -t nat -L KUBE-SERVICES -n | grep ClusterIP # ipvs 模式 ipvsadm -Ln | grep -A 5 ClusterIP我踩过的一个坑是某台节点上的 kube-proxy 因为资源紧张被 OOM 掉长期处于 CrashLoopBackOff但 Pod 本身没受影响同节点上访问其他 Pod 的 IP 是通的只有访问 ClusterIP 不通。表现极具迷惑性——NodePort 通、Pod IP 直连通、ClusterIP 不通。后来kubectl -n kube-system get pods | grep kube-proxy一眼就看到了。所以排查 ClusterIP 不通时先确认所有节点的 kube-proxy 都健康这个前置检查能省掉后面大量无用功。4.3 conntrack 表满与端口复用引发的间歇性失败高并发场景下还有一个隐蔽问题conntrack 表打满。内核会记录每条连接的跟踪状态表满了之后新的 SYN 包会被丢弃表现出来就是间歇性 telnet 超时重试有时能通。这个现象特别像网络抖动很容易误导人往物理网络方向查。在节点上检查cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max如果count逼近max基本就能确认。解法是调大nf_conntrack_max同时缩短nf_conntrack_tcp_timeout_established避免大量长连接把表占满。另外检查一下应用侧有没有大量短连接、TIME_WAIT 堆积这些往往是根因。还有一个相关的坑如果用了sessionAffinity: ClientIP同一客户端 IP 会被固定到同一个后端 Pod。客户端集中在一个出口 IP 后面比如一个代理网关负载就会全压到一个 Pod 上那个 Pod 的连接数打满之后新连接被拒其他客户端能通只有走这个出口的不通。这种部分人能用部分人不能用的问题最容易让人怀疑人生。5. 分场景排查流程与常见问题速查5.1 按现象分流的排查决策表把前面几节的内容压缩成一张可以贴在工位上的表遇到问题先对号入座现象优先怀疑第一步命令telnet 秒被拒绝端口无监听 / 本机 REJECTPod 内ss -lntptelnet 卡住超时集群内Endpoints 空 / targetPort 错kubectl get endpointslicestelnet 卡住超时集群外安全组 / NodePort / 防火墙检查节点安全组规则集群内通、集群外不通Service type / externalTrafficPolicykubectl get svc -o yaml部分客户端通、部分不通sessionAffinity / 出口 IP 集中看 svc 的 affinity 配置时通时不通探针抖动 / conntrack 满查 Pod READY 与 conntrack_count刚部署完不通过一会自己好了Endpoints 同步延迟 / 探针启动窗口观察 1 分钟后再测外部访问还多一层云上的节点安全组或自建环境的宿主防火墙必须放行 NodePort 范围或者放行 LoadBalancer 映射的端口。这一层不在 Kubernetes 的管控范围内经常被遗漏。我遇到过团队改完 Service 配置一切正常但外部还是不通最后发现是安全组只放行了 443NodePort 随机分到了 31123当然进不来。建议在生产环境显式指定 nodePort别依赖随机分配方便安全组统一配置。5.2 三个典型复盘从现象到根因的完整路径案例一Go 服务只监听 loopback。某开发者的订单服务容器内自测完美外部访问超时。ss -lntp显示127.0.0.1:8080。代码里写死了http.ListenAndServe(localhost:8080, nil)改成:8080之后正常。这个案例值得记的原因是它太典型了——本地开发环境一直没问题因为本地是直接curl localhost进了容器才暴露。案例二探针把 Pod 判死了。某公司的报表服务上线后前端一直 502kubectl get pods显示0/1 Running重启次数 0。describe pod看到 readiness probe failed: connection refused。原因就是initialDelaySeconds: 5对启动要三十几秒的 JVM 来说远远不够。换成startupProbe加failureThreshold: 30之后稳定。案例三一条 NetworkPolicy 断了一个命名空间。安全整改时加了默认拒绝所有入向流量的策略结果新部署的服务全部连不上。查了半天 Endpoints 正常、Pod 正常、监听正常最后kubectl get networkpolicy才发现。补了放行规则、按 label 精确匹配后恢复。教训是策略变更必须跟部署流程一起灰度不能一次性全命名空间生效。5.3 一份可以直接抄的排查清单把顺序固定下来能避免在压力下乱试明确现象超时还是拒绝用nc -zv -w 3测。明确范围Pod IP 直连、ClusterIP、NodePort、外部域名四个维度分别测。容器内自测ss -lntp看监听地址与端口。查 Endpointskubectl get endpointslices为空则查 label 与探针。查 Service 端口port与targetPort是否对得上容器。查策略kubectl get networkpolicy看有没有默认拒绝。查节点组件kube-proxy 是否全部健康ipvsadm或iptables规则是否存在。查外部安全组、防火墙、负载均衡后端健康检查。这套流程走完99% 的端口不通问题都能定位。真正难以定位的是最后 1%——涉及特定内核版本、特定 CNI 插件的兼容性问题那种情况就得抓包了。抓包我一般用tcpdump -i any -nn port port -c 100在 Pod 所在节点和客户端两侧同时抓对比 SYN 是否到达、是否有回包、回包是 RST 还是 SYN-ACK基本能一锤定音。提示在容器里抓包先确认镜像里有 tcpdump。生产环境更稳妥的做法是在宿主机上抓用-i指定 veth 网卡配合 Pod 的 IP 过滤。5.4 预防胜于排查的三条工程实践第一把端口约定固化下来。Deployment 的containerPort、Service 的targetPort、应用配置里的监听地址这三者必须一致且用 0.0.0.0 绑定。在 CI 里加一个静态检查比对配置文件里的端口和部署清单里的端口能挡住一大批低级错误。第二探针配置模板化。为不同技术栈准备三套标准模板——快速启动型、JVM 型、后台任务型新人直接套用不要每次都从零写。模板里startupProbe是标配readinessProbe的 path 用统一约定比如所有服务都必须提供/healthz。第三变更前先跑连通性用例。每次改 Service、改 NetworkPolicy、改探针先在一个临时命名空间里起两三个 Pod 互相 telnet 一遍通过了再动生产。这个习惯听着啰嗦但真正救过我的时候不止一次。我个人在实际操作中的体会是端口不通这个问题本身不难难的是在故障现场保持排查顺序不被打乱。压力越大越容易病急乱投医改一堆配置反而把真正的问题掩盖了。所以我现在都强迫自己先花三十秒把上面那张决策表过一遍确定范围和现象再动手。另外一个小技巧养成每次变更后立刻用kubectl get endpointslices -w盯着的习惯几秒钟就能看到后端列表是否更新比等告警快得多。