nodePort端口连不通这个话题,凡是维护过Kubernetes集群的人,大概率都经历过。我维护的生产集群里就撞过好几次:业务方半夜打电话过来说服务访问不了,登录集群一看,Pod全是Running,Service也健在,NodePort端口也分配了,但从集群外访问就是不通。有一次我折腾到天亮才发现只是云安全组少放行了一个端口,那种挫败感到现在都记得。
这类问题的诡异之处在于:表象高度相似,根因却千差万别。可能是Service的selector写错、可能是kube-proxy状态异常、可能是内核转发开关被关掉、也可能是防火墙策略拦路。这篇文章把我这些年处理K8s集群nodePort故障的思路完整整理一遍,从链路原理到每一条排查命令,再到几个实战复盘,希望能帮遇到同样问题的朋友少走弯路。
1. 故障表象:同样是"连不上",背后的原因可能南辕北辙
nodePort连不上的问题,最让人头疼的不是故障本身,而是表象太相似。我接手过好几次这类工单,用户报障就一句话:"服务挂了,端口访问不了。"但实际原因五花八门。先把我见过的高频表象列一下,大家可以对照自己遇到的情况,心里先有个大致方向。
表象一:集群内部访问完全正常,外部就是连不上
这是最常见的一种。从集群里的Pod去访问Service的ClusterIP,响应很快,业务接口返回正常。但到了集群外,用浏览器或客户端去访问节点IP:NodePort,要么超时,要么直接拒绝。这种问题十有八九出在节点到外部这一段:云安全组没放行端口、节点自身的防火墙拦了,个别IDC机房网络对高位端口做了限制也有可能。
表象二:部分节点能连,另一部分节点连不上
有的集群节点很多,用户已知多个节点IP的情况下,发现其中一个节点访问不通,其他节点正常。如果流量是通过负载均衡器转发到各节点,那可能只影响其中一台。这类故障通常指向单节点异常:kube-proxy挂掉了、节点被标记为NotReady、或者这台节点的防火墙配置和别的节点不一样。
表象三:时好时坏,偶尔通偶尔不通
这种最折磨人。端口大部分时间可用,但高峰时段或某次发版后开始间歇性失败。常见原因包括:部分后端Pod的readiness探针不稳定导致Endpoint数量抖动、conntrack表满了丢新连接、或者externalTrafficPolicy: Local模式下流量被转发到了没有Pod的节点。
把表面对齐之后,我通常不会急着上服务器一通乱敲,而是先在心里把整个访问链路过一遍,再决定从哪里开始查。这也正是下一节要做的事。
2. 建立全局视角:一个NodePort请求在集群里要走完这几跳
2.1 从客户端到Pod,流量一共经过五道关口
一个客户端访问http://节点IP:NodePort,流量在Kubernetes集群里大致要经过这么几个环节:
- 客户端到节点IP的TCP连接,这一步由网络路由和节点安全策略决定;
- 节点网卡接收流量,进入内核协议栈;
- kube-proxy维护的负载均衡规则接管流量,在NAT表中做DNAT,把目标端口从NodePort改写成Pod的targetPort;
- 内核根据路由把改写后的包转发给Pod所在的原生IP,这一步依赖CNI插件创建的虚拟网络设备;
- Pod内容器进程监听targetPort,接收连接并返回数据。
很多人以为NodePort是"节点上有个进程在监听这个端口",严格说不完全对,这取决于kube-proxy的工作模式。在iptables模式下,kube-proxy并不会真的去监听NodePort端口,而是往iptables里写入DNAT规则,流量在PREROUTING阶段就被改写了。在ipvs模式下,kube-proxy会创建虚拟服务器(Virtual Server),节点上确实有内核级别的监听,但处理逻辑仍然是转发到Pod。理解这个区别对排查很重要,后面讲到端口占用问题时还会用到。
2.2 排查的本质,就是检查五道关口里哪一道断了
链路一旦在脑子里成形,排查就变成一件很机械的事:从上到下,一关一关验证。
- 第一关:客户端能不能连通节点IP和NodePort(对应安全组、防火墙);
- 第二关:节点上有没有对应的kube-proxy转发规则(对应Service是否存在、kube-proxy是否工作正常);
- 第三关:转发规则后面的Endpoints里有没有健康的Pod(对应Pod数量、readiness状态);
- 第四关:内核允不允许转发(对应
ip_forward参数、FORWARD链策略); - 第五关:Pod本身能不能收到包、容器端口是否在监听(对应targetPort配置、容器启动参数)。
我自己习惯先做一次"内到外"的基准测试:在节点上直接curl PodIP:targetPort。这条通了,说明Pod侧的第四、五关正常,问题基本限定在节点以外的部分;如果这条不通,那就先不用管安全组,问题一定在集群内部。按照这个顺序去查,能避免大量无用功。
3. 第一道闸门:Service、Pod、Endpoint三者对不上号
3.1 只用三条命令,三分钟确认服务定义是否健康
很多nodePort故障,根子根本不在网络,而在Kubernetes对象本身。第一步,我会执行这三条命令:
kubectl get svc -n <namespace> kubectl get pods -n <namespace> -o wide kubectl get endpoints -n <namespace>把三条命令的输出放在一起对照。重点确认三件事:Service的nodePort字段是否存在;后端Pod是否Running且Ready;Endpoints里有没有Pod的IP和端口。
如果kubectl get endpoints的输出为空,说明到Pod这一步已经断了,这是最典型的"对不上号"事故。注意,NodePort映射关系里,真正决定流量能不能到Pod的,不是Service清单上写的那几行文字,而是Endpoints这个对象。Endpoints是Service根据selector自动维护的"后端地址簿",地址簿是空的,门外立再多路牌也白搭。一个正常的NodePort Service大概长这样:
apiVersion: v1 kind: Service metadata: name: web-service namespace: default spec: selector: app: web ports: - port: 80 targetPort: 8080 nodePort: 30012 type: NodePort3.2 Selector写错是最大概率事故,没有之一
Selector不匹配是Endpoints为空的头号原因。我印象很深的一次:A同学部署了一套服务,YAML里Service的selector写的是app: nginx,Pod的labels却写的是app: nginx-web,少了个后缀。Pod正常Running,Service也创建成功,NodePort也分配了,但流量根本找不到任何后端。
查看方式很简单:
kubectl get svc <service-name> -n <namespace> -o yaml | grep -A 5 selector kubectl get pods --show-labels -n <namespace>出错往往是复制粘贴时改漏了,或者从别的项目里拷过来忘了改。所以我的习惯是,任何一次Service下发后,先看一眼Endpoints有没有内容,再去做外部连通性测试。这个习惯帮我省了不知多少时间。
3.3 有Endpoint还是连不上:targetPort和readiness的陷阱
Endpoints有内容,不代表Pod一定能接住连接。这里有两个很容易被忽略的点。
第一,targetPort必须和容器实际监听的端口一致。比如容器里的进程监听8080,YAML里targetPort却写了80,kube-proxy把流量DNAT后指向Pod的80端口,Pod上没有任何进程在监听,连接自然失败。在节点上执行curl http://PodIP:targetPort可以立刻暴露这个矛盾。
第二,readiness探针的问题。Endpoints只收录Ready状态的Pod,如果某个Pod的readiness探针配置有问题,Pod会一直处于NotReady状态,虽然进程活着、管理接口也能返回,但流量不会调度给它。这类问题通常伴随一种现象:Pod日志里没有任何正常业务请求记录,因为请求根本没到它这里。
到这一步如果都是正常的,说明Kubernetes对象层面没问题,可以把目光移向节点和网络设备。
4. 第二道闸门:安全组、防火墙和端口,节点层的隐形拦截
4.1 云安全组是头号隐形杀手
几乎所有云厂商的节点,入方向默认都只放行少数几个端口(比如22、80、443)。Kubernetes默认的NodePort范围是30000到32767,这个范围通常不在放行列表里。如果你创建Service时用了随机分配的NodePort,然后又没去安全组放行,外部访问必然是超时。
这里有个实操建议:生产环境尽量给关键Service分配固定NodePort,而不是让API Server自动分配。比如写死nodePort: 30012,这样安全组只需要针对固定端口维护一条规则,不用开整个30000到32767的范围。从安全角度看,开放一个完整端口段的风险比开放单个端口大得多;从排查角度看,固定的端口也更容易追查日志和规则。
在云控制台的安全组规则里,通常会区分"入方向"和"出方向"。查的是入方向规则:协议选TCP,端口填实际NodePort(或端口段),源地址按需限制。如果集群在不同子网、不同可用区各有一批节点,还要确认每个子网关联的安全组都放行了,漏掉任何一个,都会造成"部分节点能连、部分不能连"的诡异现象。
4.2 节点上的iptables、firewalld和nftables
排除云安全组之后,下一个怀疑对象是节点自身的防火墙。很多常见的Linux发行版默认开着firewalld。检查命令:
systemctl status firewalld firewall-cmd --list-ports --permanent如果开启了又没有放行NodePort范围,即使云安全组全绿,节点也会把包丢掉。有些场景用的是nftables或裸iptables,同样要检查INPUT链里对应端口是否有DROP或REJECT规则:
iptables -L INPUT -n --line-numbers | grep <NodePort>这里有个细节值得注意:DNAT规则在PREROUTING链生效,理论上优先于INPUT链的拦截,但如果节点上有自定义的RAW表规则或NOTRACK规则,会让某些流量绕过NAT,导致行为变得非常奇怪。遇到这种"怎么查都查不出问题"的情况,直接iptables -t raw -L -n看一眼,往往会有意外收获。
4.3 externalTrafficPolicy: Local是典型的"部分节点不通"元凶
这个坑我要单独拎出来说,因为很多人对它的理解不够深。Service可以设置externalTrafficPolicy: Local,含义是只把外部流量转发给"本节点上存活的Pod"。
好处是保留了客户端真实源IP,坏处是:如果某个节点上恰好没有这个服务的Pod,访问这个节点的NodePort就会直接失败。想象一个三节点集群,Pod只调度到了节点A,而负载均衡把请求分发到节点B、C的NodePort上,B、C上面没有任何Pod,连接自然就断了,表现为"时好时坏"或以轮询频率间歇性失败。
遇到这种Service,排查时注意两点:一是看kubectl get svc <name> -o yaml里的externalTrafficPolicy字段;二是用kubectl get pods -o wide对比Pod分布在哪些节点,再逐一访问每个节点的NodePort验证。如果确实需要Local模式,通常要配合nodeSelector把后端Pod固定在某些节点,并让负载均衡只把流量发给这些节点。
4.4 端口占用和NodePort范围的分配冲突
最后提一个相对少见的坑:NodePort范围里某个端口被节点上的其他进程占用。在iptables模式下,kube-proxy不监听端口,所以即使有进程占用了30012,DNAT规则仍然会在PREROUTING阶段改包,NodePort本身可能还能通。但在ipvs模式下,创建虚拟服务器时如果端口已被占用,会失败或出现规则异常。
遇到这种情况,先确认当前集群kube-proxy的模式,再决定排查方向。如果怀疑端口冲突,在节点上执行:
ss -tlnp | grep -E '(30000|30012)' ipvsadm -Ln | grep 30012另外还要注意,如果你手动指定NodePort但端口落在API Server配置的--service-node-port-range之外(默认30000-32767),Service创建会直接报错。这种问题一般不会出现,但改过集群启动参数的话要留个心眼。
5. 第三道闸门:kube-proxy规则、内核参数和CNI的配合
5.1 先确认kube-proxy本身是活的
Kubernetes对象正确、防火墙也放行了,依然不通的情况,大概率出在数据链路。第一步先看kube-proxy的运行状态:
kubectl get pods -n kube-system -o wide | grep kube-proxy kubectl logs -n kube-system kube-proxy-<pod名> --tail=50kube-proxy通常是DaemonSet,每个节点一个。如果某个节点上的Pod处于CrashLoopBackOff或者一直Pending,那个节点的NodePort大概率不通。日志里常见的错误包括:无法连接API Server、iptables命令执行失败、节点标签导致DaemonSet没把Pod调度过来。
5.2 iptables模式和ipvs模式的验证手法不同
确认kube-proxy模式,看ConfigMap:
kubectl get cm kube-proxy -n kube-system -o yaml | grep modeiptables模式下,检查NAT表里有没有KUBE-NODEPORTS链:
iptables -t nat -L KUBE-NODEPORTS -n -v输出里能看到每个NodePort对应的跳转目标链,再顺着目标链往下查具体的DNAT规则即可。也可以用iptables-save | grep <NodePort>快速定位相关规则。如果规则在,但要转发的Endpoints地址不存在,就要回到第三节检查Endpoints。
ipvs模式下,用ipvsadm查看:
ipvsadm -Ln | grep <NodePort>输出里能看到虚拟服务(Virtual Server)和真实服务器(Real Server)列表。如果RealServer列表是空的,同样回到Endpoints去查。ipvs模式还有一个常见问题:kube-proxy依赖ip_vs、nf_conntrack等内核模块,模块没加载会导致ipvs规则创建失败,日志里通常会有明确报错。
5.3 FORWARD链策略和ip_forward参数:内核层的两个"总开关"
流量要从节点转发到Pod,内核必须打开转发开关:
sysctl net.ipv4.ip_forward如果输出是0,说明内核不允许转发,外部进来的包到节点后直接丢弃。开启方式:
sysctl -w net.ipv4.ip_forward=1 echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf再说FORWARD链。TCP连接的数据包经过节点转发时,会经过filter表的FORWARD链。有些做过安全加固的系统把默认策略设成了DROP,这时候即使有DNAT规则,包也会在转发阶段被丢得干干净净。表现为:从节点上curl PodIP能通,因为那是本机发起的流量,走的是OUTPUT和INPUT链;但从外部访问NodePort不通,因为外部流量转发给Pod时走的是FORWARD链。
验证方法:
iptables -L FORWARD -n -v iptables -L KUBE-FORWARD -n -v如果发现FORWARD策略是DROP,又没有显式的放行规则,赶紧补上。这也是我强烈建议生产集群配置网络策略时,不要在Filter层动"一刀切"的原因。
5.4 conntrack、路由和CNI:容易被忽略的最后一公里
数据链路最后一段,是从节点到Pod网络。这里有几个隐藏很深的问题。
conntrack表满是一个经典故障。当节点上建立的连接数超过nf_conntrack_max时,新连接会被直接丢弃,表现为"端口时好时坏、压力一大就断"。排查方法:执行dmesg | grep -i conntrack看有没有报错,或者用conntrack -S看统计。生产环境调大net.netfilter.nf_conntrack_max是常规操作,但根本解法还是减少无效连接。
路由问题也不少见。节点上必须存在去往Pod CIDR的路由,这条路由通常由CNI插件维护。检查:
ip route如果发现Pod网段的路由缺失,重启对应节点的网络组件通常能恢复。有些CNI插件使用隧道模式转发,比如IPIP封装,如果宿主机内核没有加载对应模块或者隧道网卡状态异常,也会造成流量黑洞。这时查看网络组件的Pod日志,能看到比较明确的信息。
6. 一条能直接照抄的排查命令清单与三个实战复盘
6.1 从外到内的十条快速检查
这套命令我经常直接发给刚入行的同事,按顺序执行,基本能把nodePort不通的问题定位到具体环节。
| 检查点 | 命令 | 期望结果 | 异常时关注 |
|---|---|---|---|
| 服务定义 | kubectl get svc <name> -o yaml | nodePort、selector正确 | externalTrafficPolicy、端口 |
| 后端地址 | kubectl get endpoints <name> | 有Pod IP列表 | selector、readiness |
| Pod形态 | kubectl get pods -o wide | 全部Running/Ready | 探针、镜像 |
| 节点内部 | curl --connect-timeout 3 http://<PodIP>:<targetPort> | 业务响应 | 容器端口 |
| 节点规则 | iptables -t nat -L KUBE-NODEPORTS -n | 存在DNAT规则 | kube-proxy |
| 节点转发 | sysctl net.ipv4.ip_forward | 1 | 开启转发 |
| 过滤器链 | iptables -L FORWARD -n | 有放行规则 | 安全策略 |
| 外部连通 | nc -vz -w 3 <节点IP> <NodePort> | Connected | 安全组/防火墙 |
| 连接跟踪 | conntrack -S或dmesg | grep conntrack | 未达上限 | 调大参数 |
| 节点路由 | ip route | 有Pod网段路由 | 重启CNI组件 |
这份清单的顺序是刻意的:先服务定义,再Pod状态,再节点内部连通性,最后才测外部。因为内部不通的问题继续往外部查是浪费时间。
6.2 三个典型场景复盘
场景一:Endpoints空,Selector少个后缀。某次业务发版后,新版本Pod的label从app: order改成了app: order-service,Service没同步改。用户反馈支付接口连不上,我查了kubectl get endpoints是空的,再一看Service的selector,问题一目了然。改掉selector后,Endpoints立即填充,NodePort马上恢复。整个排查不到十分钟。
场景二:externalTrafficPolicy: Local造成的间歇性失败。某个内部管理平台的Service开了Local模式,Pod只调度在三个节点中的两个。当时负载均衡定期做健康检查,每三次里就有一次打到没有Pod的节点,表现就是"90%时间好的,偶尔刷新就白屏"。查到Service配置后,把所有副本固定在同一个节点组,再调整负载均衡的后端,问题彻底消失。
场景三:云安全组漏放行端口。新开了一批节点,没有关联到已有的安全组,而新安全组里没放行30012端口。节点上所有检查都正常,iptables规则完整,Pod能通,外部telnet就是超时。最后在云控制台对比新旧安全组规则,发现就是少了一条入方向TCP 30012的规则。加上之后秒通。
这三个场景有共性:都不是什么高深的技术,全是"链路里某一环对不上"。所以排查的顺序感非常重要,从服务定义开始一层层往下,而不是上来就动iptables、改内核参数。
6.3 把"救火"变成"防火":日常监控三件套
一次又一次解决同样的问题后,我给自己定了三条防护性措施,分享出来供参考。
第一,监控Endpoints数量。服务对应的Endpoints一旦变成0,立即告警。这个指标比Pod状态更接近用户视角,因为它直接反映"有没有可用后端"。
第二,用外部探针定期连接NodePort。可以在集群外放一个轻量探活任务,或者部署带TCP探测的监控组件,每30秒探一次节点IP:NodePort。一旦连续几次失败就告警,比等用户报障快得多。
第三,规范NodePort的使用。建立内部规范:核心服务一律使用固定NodePort,登记成台账;变更云安全组时,把NodePort清单作为固定附件同步核对。这个习惯能显著降低"安全组忘了放行"这类低级事故的发生频率。
7. 我的几点个人体会
nodePort连不上这个问题,我踩过的坑越多,越觉得它考验的不是Kubernetes知识深度,而是排查的秩序感。每次遇到这类问题,先深呼吸,把链路在脑子里过一遍,再按顺序执行检查命令,基本都能在半小时内定位。
最后分享一个我保留多年的小习惯:到节点上验证Pod连通性时,curl命令一定要带超时参数,比如curl --connect-timeout 3 --max-time 5 http://10.244.1.5:8080。这样测试不会因为连接hang住而卡半天,能快速得到一个明确的超时结论,后续判断链路故障点会清晰很多。排查网络问题,最怕的不是找不到原因,而是在错误的层级上浪费时间。希望这篇总结能让大家少走一点弯路。