☰
K8s集群VIP+NodePort访问失败?从现象到根因的分层排障指南
2026/10/14 14:42:14 网站建设 项目流程

前两天有个人在群里问:k8s集群搭好了,直接访问Service的NodePort能通,但套上Keepalived的VIP之后死活访问不了。这个问题我太熟了。VIP+NodePort的组合在物理机、自建机房这类环境里几乎是标配,前端挂个虚拟IP做高可用,后端让kube-proxy把流量转发给Pod。表面上看链路很简单,但一旦不通,排障链路其实很长:从客户端到VIP,从VIP到NodePort,从NodePort到ClusterIP,再从ClusterIP到Pod,每一层都可能出问题。

我见过太多人一上来就怀疑防火墙,改完安全组、清完iptables还是不通,因为问题根本不在那一层。这篇文章我就把VIP+NodePort访问失败的完整排查思路写清楚,从现象定性到分层验证,再到我实际踩过的几个高频坑,最后给一套可以直接照抄的命令清单。不管你是刚搭集群的运维新人,还是被这个故障折磨过几次的K8s维护者,照着这个流程走一遍,大概率能找到根因。

1. 现象定性:先把“不能访问”拆成几种情况

排查之前,先把现象描述清楚。很多时候群里提问只说“VIP+NodePort不通”,这个信息量太小,因为“不通”至少有三种完全不同的表现,对应的排查方向也完全不同。

  • 第一种:从外网或内网访问VIP:NodePort完全没反应,连接会卡住直到超时。
  • 第二种:能连上但马上被拒绝,提示Connection refused。
  • 第三种:时通时不通,或者只有某些客户端通、某些客户端不通。

我为什么强调先定性?因为这三种现象指向的故障层不一样。连接超时通常是数据包到了VIP所在节点,但没人处理,或者转发链路断掉了,最常见的两个元凶是防火墙丢包和内核转发关闭。Connection refused则说明端口上根本没有进程监听,或者NodePort规则没生成,数据包被RST掉了。时通时不通则更倾向于负载均衡、健康检查、conntrack表满这类“状态性”问题。

把整个访问链路画出来会更直观。客户端发起请求时,数据包先到达VIP,由Keepalived管理的VRRP协议把这个VIP绑定在某台节点上;到达节点后,请求进入NodePort的DNAT规则,被kube-proxy改写到ClusterIP;接着又被转发到某个Pod的IP地址;Pod处理完返回,原路返回给客户端。注意这里有个细节:NodePort默认监听在所有节点上,不管你访问哪台节点的IP:NodePort都能通,但VIP是跟节点绑定的,所以VIP漂移到哪台,流量就从哪台进。这就埋下了第一个经典坑,后面会专门说。

还有一个前提值得注意:你访问的到底是哪个地址、哪个端口。如果客户端和集群在同一个二层网络,VIP直接可路由;如果跨网段,还要确认VIP是否做了路由或者是否被防火墙拦截。端口方面,NodePort默认范围是30000到32767,这个区间是apiserver启动参数--service-node-port-range控制的。我遇到过一个情况,运维自己改了NodePort范围,但安全组只放行了默认端口段,结果怎么调都没用。所以先确认这些“看起来不会错”的基础信息,往往是最高效的。

2. 从外往内逐层定位:每一层都能用一条命令验证

很多人排障喜欢从Pod开始看,但我强烈建议反过来:从离客户端最近的一端开始,逐层向后验证。这样做的好处是每次只用排除一个变量,能快速缩小范围。

2.1 先确认VIP这层是活的

既然是VIP访问不通,第一步一定是确认VIP本身有没有正常绑定在节点上。

查看节点IP地址,最直接的命令是:

ip addr show

关注有没有Keepalived配置的虚拟地址,比如192.168.100.200/32这样的情况。如果VIP根本不在任何节点上,那所有问题都无从谈起。注意多看几台节点,因为Keepalived的主备状态跟VIP的绑定位置是动态的,万一VIP已经在故障节点之间来回漂移,你盯着某一台看会漏掉。

确认VIP存在之后,接着看Keepalived的状态是否健康。常见做法是:

systemctl status keepalived ip addr show <网卡名>

再进一步,看VRRP状态是MASTER还是BACKUP,可以用ipvsadm之外的另一个工具,比如keepalived自身的日志,一般写在/var/log/messages或journalctl -u keepalived里。如果频繁出现主备切换,说明VIP漂移得太频繁,可能是健康检查脚本写得不对,或者VRRP通告间隔设置得太短。这个情况在实践中并不少见,后面会展开。

另外一个小细节:如果VIP网段跟节点所在网段不同,最好确认一下节点上的路由表,看看回包路径是否正常。比如客户端访问的是VIP,NodePort转发给Pod之后,Pod回包的源IP可能是Pod IP,这时候如果Pod节点上没有正确的回程路由,就会出现“请求进去了,但响应回不来”的情况。这条容易被忽略,我记一次踩坑,查了半天防火墙,最后发现是回程路由丢在了别的地方。

2.2 验证VIP所在节点上NodePort到底有没有监听

VIP这层看着正常之后,下一步就要验证“到了VIP所在节点之后,端口到底有没有人接”。直接在VIP所在节点上,用本地回环访问一下:

curl -v http://127.0.0.1:<NodePort>

如果本机能通,说明kube-proxy和Pod链路没问题,问题大概率出在VIP绑定、防火墙或者路由上。如果本机也不通,那就继续向内部看。

再用ss检查监听状态:

ss -lntp | grep <NodePort>

正常情况下,NodePort会监听在0.0.0.0:<NodePort>,对应进程一般是kube-proxy。如果看到的是127.0.0.1:<NodePort>或者根本没有监听,那要怀疑kube-proxy是否正常。

查kube-proxy到底是哪个DaemonSet Pod在跑,最简单的:

kubectl get pods -n kube-system -o wide | grep kube-proxy

如果某个节点的kube-proxy Pod异常,比如CrashLoopBackOff,那这个节点上的所有NodePort都不可能有规则。修复方式通常是删除这个Pod让它重建,或者检查节点上有没有残留的旧kube-proxy进程占用端口。

但这里有个容易踩的坑:ss显示有监听,不代表规则就生效。因为kube-proxy的NodePort映射是动态刷到iptables或IPVS里的,监听只是占用了socket。真正的转发逻辑在NAT规则里。所以下一步必须看规则层。

2.3 核对kube-proxy的转发规则:iptables模式和IPVS模式的差异

kube-proxy有两种常见工作模式:iptables模式和IPVS模式。这两个模式下的排查方法完全不同。

如果集群用的是iptables模式,最核心的检查命令是:

iptables -t nat -L KUBE-NODEPORTS -n

能看到类似这样的一行一条规则:

DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:30080 /* default/test-service */ to:10.96.0.10:80

这说明NodePort已经成功映射到ClusterIP。如果这条规则不存在,大概率是Service定义本身有问题,或者kube-proxy没有同步成功。下一步就要检查Service和Endpoints。

如果用的是IPVS模式,要看的是:

ipvsadm -L -n

大概会看到一条条TCP的虚拟服务记录,对应Service的ClusterIP和NodePort。IPVS模式下,NodePort实际上是通过IPVS虚拟服务器实现的,所以如果ipvsadm里没有对应条目,同样说明kube-proxy没同步。

我个人的建议是:在自建机房或者物理机环境,IPVS模式通常是更好的选择,因为它的转发性能更高、规则更清晰,而且排障时可以直接用ipvsadm查看调度器上挂了哪些真实服务器。但IPVS模式也有自己的麻烦,比如依赖ipset和ipvsadm这些用户态工具,部分内核模块没加载时,kube-proxy会一直报错。检查一下内核模块:

lsmod | grep ip_vs

如果什么都没有,可以临时加载一下,并记得写入开机模块加载配置。这个坑在全新安装的系统上出现频率不低,特别是用了精简版系统的机器。

2.4 末端检查:Pod是否真的健康,Endpoints是否正常

如果NodePort规则存在,但转发还是失败,就要把目光放到Service的后端。先看Service详情:

kubectl describe svc <service名>

重点看Endpoints字段有没有Pod的IP列表。如果显示<none>,说明Service的selector没错配,或者后端Pod没Ready。配合看Pod状态:

kubectl get pods -o wide -l app=<你的应用标签> kubectl get endpoints <service名>

Endpoints里能看到Pod的真实IP和端口。这里有一个很容易迷惑人的点:即使Pod在Running状态,如果Readiness探针失败,Pod也不会被加入Endpoints列表,这时NodePort同样会“通但不转发”。所以遇到规则都在、Pod也活着但访问失败的情况,优先看探针和Endpoints。

还有一个值得注意的细节:如果集群里有多个Service,名字差不多,很容易看错。比如你本意是暴露nginx-web这个Service,但NodePort规则里映射的目标是default/test-service,这种低级错误在我之前的排障经历里真出现过。所以核对Service名、命名空间、端口号时,宁可多花十秒钟搜一遍,也不要凭印象猜。

3. 实战中踩得最多的四个坑

3.1 坑一:externalTrafficPolicy: Local 跟 VIP 不在同一层

这是VIP+NodePort场景里最经典的坑,没有之一。

先解释一下externalTrafficPolicy这个参数。它的默认值是Cluster,意思是流量进入任意节点的NodePort之后,kube-proxy会把包转发到任意一个Pod上,哪怕Pod在别的节点。这个过程中,源IP会被SNAT改成节点IP,所以后端Pod看不到客户端的真实IP。

如果改成Local,则只会把流量转发给本机上的Pod,不再跨节点转发。这样做的好处是保留了客户端源IP,坏处很明显:如果VIP所在节点上恰好没有对应Pod,那这个节点的NodePort规则虽然存在,但没有任何后端,访问直接失败。

举一个我实际遇到过的例子。某测试集群里有一个Service,配置了externalTrafficPolicy: Local,Pod有两个副本分布在node-1和node-2上。Keepalived的VIP正常绑定在node-3上。node-3上没有任何该Service的Pod,它的NodePort也没有真实后端。结果就是前端通过VIP访问时,请求到了node-3,node-3的kube-proxy一看本机没有Pod,直接返回拒绝,表现为Connection refused或连接重置。

排查方法很简单:

kubectl -n <命名空间> get svc <服务名> -o yaml | grep externalTrafficPolicy

如果确实配置成了Local,要么把Policy改回Cluster,要么把VIP固定绑定在有Pod的节点上。不改的话,只能保证所有节点都有Pod副本,但这在现代集群里不太现实。我当时跟朋友的建议是:除非你明确需要保留客户端源IP,否则NodePort场景用默认的Cluster就够了。有时候保留源IP的需求可以通过后面加一层负载均衡来实现,没必要在NodePort这一层硬凹。

3.2 坑二:节点防火墙/安全组把NodePort端口段拦了

这个坑说大不大,但特别容易反复踩。

自建机房场景,大部分机器上都开着firewalld或者iptables filter规则。云上K8s节点则通过安全组控制。默认的NodePort范围是30000到32767,很多安全组规则只放行了22、80、443这类常规端口,压根不会想到还有30000多一大段要开。表现就是:在本机curl 127.0.0.1:NodePort完全正常,但从别的机器访问节点IP:NodePort超时或拒绝。

排查时先看本机防火墙:

firewall-cmd --list-all iptables -L INPUT -n --line-numbers

如果是云主机,去安全组配置里确认入站规则是否放行了对应端口段。注意节点IP直连和VIP这两种访问路径可能过的是不同节点,所以每一台有可能承载VIP的节点都要检查,不要只查当前MASTER那台。

这里还有一个容易忽略的点:有些环境里,NodePort容器外的流量会先经过节点的INPUT链,但kube-proxy写入的规则在nat表里。如果filter表里有一条REJECT或DROP规则优先级比放行规则高,即便安全组放行了也没用。我自己习惯的做法是,先临时把本机防火墙规则全量放行做验证,确认通之后,再加精准放行规则,避免因为某条规则顺序问题一直绕不出来。但注意,生产环境做这个操作前一定要确认当前会话不会被断掉,最好通过带外方式操作,或者确保放行规则先插入。

3.3 坑三:VIP漂移后,新节点上的规则不完整或ARP信息没更新

Keepalived高可用不是“配好就永远不用管”的。节点宕机、Keepalived进程重启、网络抖动,都会导致VIP从一台机器漂到另一台。如果新节点上的环境跟旧节点不一致,就会出现“之前还通,突然就不通了”的诡异现象。

最典型的情况是:旧节点上手动加过防火墙放行规则、改过内核参数、装过额外的网络组件,但这些修改只停留在旧节点,没有同步到备节点。VIP一旦漂到备节点,所有依赖本地配置的东西全部失效。表现就是VIP访问不通,但直接访问新节点IP的NodePort却是好的,因为新节点本身的服务是正常的,只是VIP不在预期位置而已。

排查思路是确认当前VIP到底在哪台:

ip addr show | grep <VIP>

对比Keepalived配置里的virtual_ipaddress,确认漂移后环境是否一致。同时检查VRRP相关日志,看主备切换的时间点是否跟故障时间吻合。另一个容易被忽视的是ARP缓存问题。VIP漂移后,同一网段内的路由器和交换机如果还缓存着旧的MAC地址,数据包会一直往老节点送。虽然Keepalived切主时会发免费ARP通告,但某些网络设备对免费ARP处理不积极。这时候在网关或客户端设备上清一下ARP缓存,或者用arping主动通告一次,往往能快速恢复。我是这么处理的:

arping -I eth0 -c 5 -U <VIP>

这个命令从当前VIP所在节点发出免费ARP,告诉同网段设备“这个IP的MAC地址已经变了”。在自建机房环境里,这一招经常比改配置还管用。

3.4 坑四:内核转发没开,conntrack表满,SYN包被悄悄丢掉

先说内核转发。NodePort能通的前提,是节点开启了IP转发功能,否则数据包到达节点后,kube-proxy的DNAT规则就算把目标地址改成了Pod IP,内核也不会把它路由到其他节点。检查方法:

sysctl net.ipv4.ip_forward

如果输出是0,直接开启并持久化:

echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf sysctl -p

这个问题在容器场景里其实很少见,因为Docker或K8s安装时一般都会自动开,但如果你是用精简系统或手工方式部署的,就有可能出现没开的情况。表现也很迷惑:本机访问NodePort可能通,其他机器访问不通,因为跨节点的包转发被拒了。

再说conntrack表满的问题。Linux内核为每个连接维护一条conntrack记录,这个表是有限的。当集群流量大、短连接多的时候,conntrack表可能会满。满了之后,新连接的数据包会被直接丢弃,表现就是“时通时不通”,或者流量一大立刻全断。这个故障在NodePort场景尤其常见,因为NodePort本身会把流量从节点打到ClusterIP再打到Pod,一条客户端连接可能对应多条内核连接记录,conntrack的消耗比直连后端要快得多。

查看当前conntrack使用率:

sysctl net.netfilter.nf_conntrack_max cat /proc/sys/net/netfilter/nf_conntrack_count

如果count接近max,就要考虑调大上限,或优化应用的连接复用。比如短连接改成长连接、减少不必要的连接创建,必要时调大参数:

sysctl -w net.netfilter.nf_conntrack_max=1048576

但千万注意,这个值不是越大越好,它跟内存有关,一条conntrack记录大概占几百字节,调太大会占用大量内存。我见过有人一上来就调到几百万,结果系统内存压力上来,反而更不稳定。合理做法是结合机器内存和实际并发量来算。

3.5 常见问题速查表

故障表现最常见原因快速验证命令解决办法
连接超时防火墙丢包 / 内核转发关闭curl 外部访问、sysctl net.ipv4.ip_forward放行端口段、开启ip_forward
Connection refusedexternalTrafficPolicy=Local 但节点无Pod / kube-proxy异常kubectl get endpoints、ss -lntp改Policy为Cluster、修复kube-proxy
时通时不通conntrack表满 / VIP漂移/ARP缓存nf_conntrack_count、arping调大conntrack、清ARP缓存
本机通、外部不通安全组/防火墙 / 回程路由节点IP直连测试检查安全组和路由表
规则存在但访问失败Endpoints为空 / Pod未Readykubectl describe svc排查select和探针

这张表是我总结经验之后整理的,谈不上包治百病,但覆盖了90%以上的NodePort故障场景。你在实际排障时,先按表现匹配这张表,能省下一大半时间。

4. 一套可以直接抄的排障流程与验证方法

4.1 快速自检脚本思路

排查这种问题,最容易乱的点是命令太多了,在手忙脚乱的时候漏掉关键信息。我习惯把排障步骤写成一个脚本思路,每次故障先跑一遍,快速拿到全链路的状态快照。

流程大概是下面几步:

# 1. 找出VIP所在节点 ip addr show | grep <VIP> # 2. 在VIP所在节点确认NodePort监听 ss -lntp | grep <NodePort> # 3. 本机访问验证后端链路 curl -v http://127.0.0.1:<NodePort> # 4. 检查kube-proxy转发规则是否存在 iptables -t nat -L KUBE-NODEPORTS -n | grep <NodePort> # 5. 检查后端Endpoint是否Ready kubectl get endpoints <Service名> -n <命名空间> # 6. 检查内核转发和conntrack状态 sysctl net.ipv4.ip_forward cat /proc/sys/net/netfilter/nf_conntrack_count

把这几步的输出贴在排查记录里,基本能覆盖问题链路。如果某一步的输出明显异常,再针对那一步深入排查。比如第1步VIP不在任何节点,那就往Keepalived方向查;第5步Endpoints为空,就去查Pod和探针,不要继续往下钻。

4.2 用压测验证“通了”是真的通了

很多时候故障修复之后,简单curl一下就算完事,但没过多久又坏了。我建议修复后做一次简单的压测,确认不只是“当前这个连接碰巧通了”,而是整个链路在大流量下也稳定。

最简单的压测工具是ab,比如:

ab -n 5000 -c 100 http://<VIP>:<NodePort>/

压测的同时,在VIP节点上观察ss连接数和conntrack增长情况。如果你发现连接数一上来,conntrack快速飙升,就要警惕之前的“时通时不通”可能再次出现。另外,用watch实时看ipvsadm -L -n(如果是IPVS模式),确认后端Pod的Active/Inactive连接数量在正常动态变化,说明转发在真正发生。

压测的时候也记得关注后端Pod的CPU和请求成功率,如果来自VIP的流量打到所有Pod之后,某些Pod的日志里出现了大量错误,说明问题可能不在网络层,而在应用本身。这个问题我在一次排查中误判了很久,因为NodePort链路一直通,只是后端服务的健康检查逻辑有问题,导致Pod频繁被摘除,最后现象就是“时通时不通”。

4.3 给新集群的落地建议

最后给一个我个人实践下来的建议。不管是新建集群还是已有集群,尽量把这几个点提前固化下来,能省去以后无数排障时间:

  • 统一规划NodePort端口段,固定给重要服务指定nodePort端口,避免动态分配导致端口漂移。
  • 在每台节点上提前固化好防火墙放行规则、ip_forward=1、conntrack参数,并通过配置管理工具统一下发,防止VIP漂移后环境不一致。
  • 使用externalTrafficPolicy: Cluster作为默认,只有明确需要客户端源IP时才针对个别Service改成Local,并同时规划好VIP的同步。
  • 在监控里加上VIP健康状态、kube-proxy Pod状态、conntrack使用率这几个指标,任何一个异常都能提前触发告警。

4.4 一次典型排障的过程记录

这里写一个比较典型的排障过程,你可以当作案例参考。某次模拟项目X的集群中,前端反馈VIP访问不通,但节点IP直连NodePort是通的。

我先确认了VIP所在节点,执行ip addr show | grep 192.168.100.200,发现VIP绑定在node-3上。然后在node-3上执行curl http://127.0.0.1:30080,发现能通,说明后端链路正常。接着从另一台机器访问http://192.168.100.200:30080,超时。到这里基本锁定,问题出在防火墙或路由,而不是kube-proxy或Pod。

我去查node-3的防火墙,发现firewall-cmd --list-all里放行的端口只有22、80、443,30000到32767这一段完全没有。加上一条放行规则后,VIP访问立刻恢复。整个过程不到十分钟,但如果一上来就盯着Pod重启或者查kube-proxy日志,可能白耗半小时。

这个案例想透露的排障逻辑很简单:用“在哪里通的、在哪里不通的”来划分排查边界,每一层都用一个最小化测试来验证,不要靠猜。

我个人在踩过几次坑之后最大的体会是,VIP+NodePort这个组合本身不复杂,复杂的是它跨了多个子系统:Keepalived管VIP、kube-proxy管转发、防火墙管准入、内核管路由和连接跟踪。任何一层状态不对,表现都一样,但定位方法完全不同。建议每个负责K8s集群的人,都把这套分层排查的流程沉淀成自己的排障手册,最好再配上告警和巡检脚本。等哪天凌晨三点被电话叫醒时,你会发现这套流程真的是救命的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询