1. 故障现场Pod运行一切正常但Service端口就是不通某个工作日的中午某系统A同事带着一脸茫然来找我“容器都Running了CPU内存也正常没有任何重启但业务入口一直在超时。”我登录集群看了一眼kubectl get pod输出的确很干净所有Pod都是Running状态READY也是1/1没有CrashLoopBackOff没有ImagePullBackOff连Warning事件都没几个。但当我尝试访问Service暴露的端口时请求就像打进黑洞一样等来的只有客户端超时。这就是我在Kubernetes排障实战系列里遇到的相当有代表性的一个场景资源层面完全正常端口层面却彻底不通。Kubernetes的资源状态反映的是生命周期和探针结果并不代表网络路径真的可达。尤其是当“Invalid Container Port”这类端口声明与实际监听不一致的问题出现时资源看起来正常恰恰是最有迷惑性的。因为Kubernetes认为这个Pod是健康的Service也找到了Endpoint所有台词都准备好了可流量就是送不到正确的门牌号上。接手之后我快速整理了四个排查方向Service的selector是否真的匹配到了目标Pod、Pod里的业务进程是否真的在监听目标端口、Service的targetPort和实际监听端口是否一致、kube-proxy这层转发链路是否正常。当时的直觉是这个问题大概率出在配置错位上而不是资源或网络基础设施故障。后面的事实也证明这就是一个非常经典的“声明端口”与“实际监听端口”脱节的案例只是它伪装成资源正常的样子出现在我们面前。1.1 症状与初步判断先说说当时的现场细节。出问题的是一个内部Web服务通过ClusterIP类型的Service对外提供服务端口配置为8080。访问链路大致是客户端 → Service ClusterIP:8080 → Pod IP:8080 → 容器内业务进程。按照A同事的描述Pod状态看起来一切正常但无论从哪个客户端访问结果都是连接超时。我做的第一个动作是快速核对Service和Endpoint。kubectl get svc输出显示Service存在类型ClusterIPCLUSTER-IP有值PORT列显示8080/TCP。kubectl get endpoints输出显示这个Service后面确实挂了Pod IPEndpoint列表不是空的。到这里为止最基础的Kubernetes服务发现链路是完整的——Service能找到PodPod也在运行中。但这里要提醒一句Endpoint存在只说明Pod被成功纳入了Service的后端列表绝不代表这个端口真的能被访问。Service的Endpoint生成逻辑是selector匹配只要Pod的标签和Service的selector一致无论Pod里的进程到底在监听什么端口Endpoint都会把Pod IP和targetPort填进去。所以Endpoint正常只是前提不是结论。1.2 为什么“资源正常”会误导排障方向很多人遇到“端口不通”的第一反应是看资源状态看到Pod Running之后就转向怀疑网络插件、节点网络、甚至是公有云的安全组规则。诚然这些因素确实可能导致端口不通但“资源正常”只能排除资源类故障剩下的可能性仍然很多。“资源正常”和“端口通”之间隔着好几层逻辑。以Pod为例只要容器没有被杀掉、没有探针失败Pod就会一直显示Running。但如果容器内的应用启动时因为配置错误压根没有监听声明端口Kubernetes的调度器和kubelet都不会给你任何提示——containerPort只是一个描述性字段它不会去验证容器内是否有进程在监听这个端口也不会在监听失败时报错。这也是“Invalid Container Port”这类问题最难查的地方一切元数据都正常但真正干活的进程缺位。所以排障一定要从“链路”而不是“状态”入手。这也是这一期排障实战系列想重点分享的思路。2. 排障思路拆解先定位“不通断在哪一跳”做网络类排障最忌讳的就是东敲一下、西敲一下没有章法。我习惯把从客户端到应用进程的完整链路画出来然后一段一段做连通性验证。“端口不通但资源正常”这种情况下通路段一般在五个位置Service转发、kube-proxy规则、Pod网络命名空间、容器内进程监听、安全策略拦截。只要确定了断点在第几跳就自然知道往哪个方向深挖。2.1 连通性验证的三个层级我把验证动作分成三层从外到内一层层收敛第一层是“Service到后端”。在集群内随便找一个Pod访问Service的ClusterIP和端口看能不能通。这层通了就说明Service本身没问题不通也不代表一切完了还需要继续往下切。第二层是“Pod IP直连”。绕过Service直接访问Endpoint列表里的Pod IP和对应端口。注意这里访问的端口要和Service的targetPort一致而不是和Service的port一致。如果这一层通了说明Pod到Service这段没问题问题大概率出在Service定义上如果也不通说明问题在Pod自身或者容器网络。第三层是“进入Pod看真相”。通过kubectl exec进入容器内部回到进程视角直接查看应用到底在监听哪个地址哪个端口。这里才算真正接触到问题的本质。这个顺序的意义在于每做一步验证都能排除或确认一个层级不会在猜测中反复横跳。2.2 动手验证从Service到Pod直连的逐步收敛实际操作中我用一个临时busybox Pod作为测试客户端避免在生产Pod里塞命令kubectl run curl-test --imagebusybox:1.36 --rm -it -- sh进入测试Pod后先试Service的ClusterIP。假设ClusterIP是10.96.100.10端口是8080wget -O- --timeout5 http://10.96.100.10:8080/结果如预期超时。接着从Endpoints里找到真实的Pod IP假设是10.244.30.7还是用8080端口wget -O- --timeout5 http://10.244.30.7:8080/同样超时。这一步非常关键Pod IP直连超时意味着问题不在Service转发层而在Pod容器网络或应用进程内部。在这个时间点我已经把排查范围从“整个集群网络”缩小到了“特定Pod内部”。接下来就是进入第三层也就是下一章的核心内容。3. 深入容器内部进程监听的端口才是硬事实3.1 进入Pod查看监听端口既然Pod IP:8080不通我需要确认8080在容器内到底有没有进程在监听。最直接的方式是进入Pod的命名空间看端口占用情况kubectl exec -it demo-app-7d8c9f5d6c-abcde -n demo-ns -- sh进入容器后第一步自然是查看监听端口。一般的容器镜像里不一定有netstat但多数发行版会带ss我通常用下面这条命令ss -tlnp如果容器里没有ss可以通过/proc/net/tcp查看IPv4的监听信息或者临时安装iproute2/net-tools。但要注意生产环境不建议在容器里随意安装软件能省则省。这次执行ss -tlnp后看到的输出让我眼前一亮。容器内监听列表里只有一行和业务相关的State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:18080 0.0.0.0:* users:((java,pid7,fd26))8080端口根本没有进程在监听。所有Deployment和Service声明里写的是8080但容器里真正被占用的是18080。3.2 根因确认端口声明与应用实际监听错位看到18080这个端口再回查应用启动方式答案就浮出水面了。这个应用默认监听8080但启动脚本里读取了一个环境变量PORT来覆盖默认端口。查看Deployment定义后发现env: - name: PORT value: 18080也就是说应用启动时把PORT18080带进来了于是进程乖乖地监听在18080上。而Service的targetPort一直是8080Deployment里的containerPort也一直写着8080探针如果有的话也在探测8080——但这里更关键的是Kubernetes的containerPort字段只是一个声明它不会在容器里帮你创建任何端口也不会验证端口是否真实存在。应用实际监听哪个端口完全由进程自己决定。这个时候标题里的“Invalid Container Port”就变得很形象了容器端口声明是8080但实际进程监听的是18080。从Kubernetes配置看这个端口声明是“有效”的可它指向了一个没有人应门的地址。这就是一类经典的配置漂移问题环境变量、启动参数、默认值三者之间没有对齐最终把整个Service带进了一个“看起来正常、实际不通”的僵局。围绕这个根因还有两个细节值得展开说。第一为什么Pod状态始终是Running因为如果Pod没有配置探针或者探针探测的是业务里另一个健康接口kubelet永远不会发现端口错位。第二为什么没有收到任何Kubernetes报错因为containerPort不属于kubelet强制校验的字段它不会主动检查容器里的端口占用情况。这也解释了为什么“资源正常”会成为一个坑。4. 交叉复核kube-proxy、iptables与网络策略是否干净虽然根因已经基本锁定在Pod内部但我在给出最终结论前还是把外层链路复核了一遍。这样做有两个原因一是怕自己漏看二是端口错位虽然能解释当前现象但万一同时存在其他网络层面的问题修复完端口还是会翻车。交叉验证是排障的保险丝。4.1 kube-proxy转发链路的常规检查kube-proxy负责把Service的ClusterIP流量转发到后端Pod。在iptables模式下Service、Endpoint和Pod之间的映射关系最终都会落到NAT规则里。检查思路是找一个节点登录上去执行iptables -t nat -nL | grep demo-app由于链名里带随机字符串用服务名过滤是最快的。查完之后重点确认几件事这个Service对应的KUBE-SVC-XXXX链是否存在、链里是否包含指向后端Pod的KUBE-SEP-XXXX规则、KUBE-SEP规则里的目标地址是否就是Endpoint列表里那几个Pod IP。我检查下来规则链完整转发逻辑也指向了正确的Pod IP只是目标端口自然也是8080。到这里就可以确认kube-proxy只是忠实地按照Service定义去投递问题不在这一层。顺便说一句如果以后遇到规则的KUBE-SEP链里没有任何规则那就要优先怀疑kube-proxy和API Server之间的同步出了问题可能和节点上的kube-proxy进程、甚至和调度有关。4.2 NetworkPolicy与节点相关的干扰排除再检查是否有NetworkPolicy把流量挡在了门外kubectl get networkpolicy -A输出结果为空说明集群默认没有启用网络策略限制网络策略排除。之后还要考虑一种特殊情况Pod的端口可能被监听在127.0.0.1而不是0.0.0.0。这种“localhost陷阱”在技术社区里非常常见尤其在开发环境里跑本地服务时开发者习惯了绑定回环地址结果镜像打包进K8s后忘了改。如果进程监听在127.0.0.1:8080那你即使在容器内用localhost访问也是通的但Pod IP和Service的流量一律进不来。虽然本案例不是这个问题但我在排查时一直把它放在候选根因清单里也是建议大家养成的习惯——看监听地址时不要只看端口数字还要看Local Address那一列的IP到底是0.0.0.0、::还是127.0.0.1。到这里复核工作全部完成kube-proxy规则正常、无网络策略干扰、安全组也不会影响集群内Pod之间的通信。断点可以百分之百锁定为容器端口声明与实际监听不一致。5. 修复与加固让端口声明真正“有效”根因确认后修复方案其实有两条路但两条路的“长期代价”完全不同。选哪条取决于团队对这套服务的配置管理思路。5.1 两种修复路径与选择逻辑第一种是最快的直接改Service的targetPort把8080改成18080。这样Service会发现Pod在18080端口活着流量马上就能走通。但我不太推荐把它当长期方案因为它会让Deployment里的containerPort8080和Service里的targetPort18080永远处于不一致状态。再过几周你做集群巡检时看到这套配置第一反应必然是“这又是谁改错了”然后又得花时间理清楚历史原因。第二种是改回应用启动参数把环境变量PORT改成8080或者直接删掉让应用回到默认端口。这个方案虽然动的是应用配置但它让“声明端口”和“实际端口”重新对齐所有配置都回到同一个约定上后续不会埋雷。实际操作时我选择了第二个方案。先编辑Deploymentkubectl edit deployment demo-app -n demo-ns把PORT18080改成PORT8080保存退出。接着滚动更新kubectl rollout status deployment/demo-app -n demo-ns等新Pod起来后再次用busybox测试Pod IPwget -O- --timeout5 http://10.244.30.7:8080/这次请求立刻有了响应。随后再测Service ClusterIP同样正常。到这里故障才真正画上句号。5.2 用命名端口根治“Invalid Container Port”类问题这个案例给我最深的启发是只要端口数字在两处以上地方被硬编码就迟早会出现不一致。Deployment里写一次containerPortService里写一次targetPort探针配置里可能还要写一次应用环境变量里也有一次。任何一个地方没跟上就会陷入这种“资源都正常但端口不通”的困境。Kubernetes其实早就给出了一种解法命名端口。在Deployment的端口声明里给端口起个名字然后Service的targetPort直接引用名字而不是数字ports: - name: http containerPort: 8080Service定义spec: ports: - name: http port: 80 targetPort: http这样改完之后如果以后应用端口从8080换到18080只需要改Deployment那一处并且保证containerPort和容器内实际监听一致即可。Service不用动探针如果也引用命名端口就更好从这个角度看命名端口的核心价值就是“少写几处数字少踩几个坑”。另外强烈建议把readinessProbe配置出来探测的路径或端口要和业务实际端口绑定不要让“资源正常”成为一个空壳。这样下次再出现端口错位探针会第一时间把Pod标记为未就绪流量就不会被路由到一个没有进程监听的端口上问题会从“用户超时”提前变成“Pod NotReady”排障难度和影响面都会小很多。6. 排障速查清单与个人体会6.1 同类现象的快速排查清单把这期排障实战的完整过程收拢成一张速查表方便下次遇到“资源正常但端口不通”时快速套用排查对象验证方式正常结果异常时该想什么Servicekubectl get svcClusterIP、端口正常Service没创建或被误删Endpointskubectl get endpoints有Pod IP地址selector不匹配ClusterIP访问集群内wget/curl访问有响应继续下一层Pod IP访问用Endpoint中的IP访问targetPort有响应Service定义有问题容器内监听ss -tlnp或netstat -tlnp声明端口正在监听应用端口被覆盖/绑定地址错误应用配置查看env、启动参数、日志端口和声明一致配置漂移iptables链路iptables -t nat -nL规则指向正确Podkube-proxy同步问题NetworkPolicykubectl get networkpolicy -A无拦截策略策略放行规则这个表建议直接截图保存遇到同类问题照着过一遍十分钟内基本能定位到断点。6.2 一些值得分享的排障习惯最后聊聊我的排障习惯。第一不要急着重启Pod。这类“端口声明与实际端口不一致”的问题重启一百次也是同样的结果而且重启会破坏现场。排障的第一步永远是把现状看清楚包括监听端口、进程参数、环境变量、日志输出这些都比重启有价值得多。第二直接进Pod看监听比看任何监控面板都靠谱。监控面板能告诉你CPU、内存、QPS但不会告诉你一个进程到底在哪个端口上听命。在Kubernetes排障体系里ss -tlnp是所有人都应该熟练掌握的第一条命令。第三发现问题后顺手把“两层一致性”检查一遍。所谓两层第一层是容器内进程的监听状态第二层是Deployment的containerPort、Service的targetPort和探针端口。只要这三处有一处不一致就直接纳入风险清单。用命名端口最多解决第二层的部分问题但第一层的“进程到底在监听哪个端口”必须靠习惯和规范去兜底。比如以后写服务配置时可以默认加上一条团队约定所有应用必须监听0.0.0.0或::数据库、缓存等中间件的端口必须通过ConfigMap统一管理Deployment里的端口声明必须和HealthCheck路径在同一个PR里提交。这些规则听着琐碎却是减少“Invalid Container Port”类故障最有效的办法。说实话这类问题并不难查难的是在“资源全绿”的氛围里保持怀疑。你越觉得一切正常越要回头看一眼那句最朴素的话真正在监听的端口才是流量的终点。
阅读完成 · 觉得有帮助?