搞K8s运维,我最常被新手问到的不是YAML怎么写,而是“Pod明明起来了,日志也正常,为什么从外面访问不通”“两个Pod在同一个集群里,为什么互相ping不通”“ClusterIP到底是个什么东西,为什么我在Node上ping不通它”。
其实这些问题全都属于K8s网络通信的范畴。网络是K8s里最容易出问题、也最让人头晕的部分,因为K8s把传统网络拆成了好几层:容器网络、Pod网络、Service网络、DNS服务发现、网络策略,每一层都有一套独立机制。很多初学者花了不少时间学YAML、学Deployment,但一遇到网络问题就抓瞎,原因就在于没有先把网络的整体模型建立起来。
这篇文章打算从运维实战的角度,把K8s网络通信这件事从头到尾捋一遍。我会先讲清楚K8s网络的整体设计思路,再拆解核心组件的工作原理,然后带你在测试环境里把一套集群网络完整搭起来并做验证,最后整理我们在实际运维中踩过的坑和排查套路。文章内容偏向基础,但会尽量讲透“为什么”,适合刚开始接触K8s的运维同学、从传统虚拟化转过来的工程师,以及准备容器化面试的开发者参考。
1. 先把K8s网络的全景图搞清楚
1.1 为什么单机Docker的网络玩法,放到K8s里就不够用了
要理解K8s网络,最好先回顾一下传统单机Docker的网络模型。你在单机上用Docker跑容器时,默认用的是bridge模式。Docker会自动创建一个docker0网桥,每个容器创建时会分配一个veth虚拟网卡对,一端塞进容器的网络命名空间,另一端挂到docker0上。容器通过这个网桥拿到一个独立IP,同宿主机上的容器之间可以直接通过网桥通信。
这看起来挺美好,但一旦涉及多台物理机就麻烦了。主机A上的容器如果想要访问主机B上的容器,docker0解决不了,因为两个网桥是不同的二层网络。传统做法是给容器做端口映射,主机A上的容器端口映射到宿主机某端口,另一台机器通过宿主机IP访问。这在集群里完全不可用,因为Pod的IP是临时分配的,根本没办法做静态端口规划,而且多副本场景下同一服务的容器分布在多台Node上,你也没办法给用户一个固定的“入口”。
我自己接手过的某套旧系统就是这么玩的,容器之间调用写的是映射后的宿主机端口,结果扩容时发现新副本落在别的机器上,配置得跟着改,部署一次要改一批配置,维护成本极高。后来迁移到K8s,核心诉求第一条就是:“Pod之间要用Pod IP直连,不要任何端口映射,也不要我手动改路由”。
这就是K8s网络模型必须存在的根本原因:K8s需要的是一个跨节点的、扁平的虚拟网络,让每个Pod都拥有一个全局唯一的IP,且任意两个Pod之间可以直接通信,底层物理拓扑对Pod完全透明。
1.2 先记住这四个问题,K8s网络就是围绕它们设计的
K8s的网络通信可以拆成四类场景,搞明白这四类,后续看任何网络问题都有主线。
第一类是Pod与Pod之间的通信,这是最基础的。集群内任意两个Pod,不管在不在同一个Node上,都要能直接通过Pod IP访问。K8s通过网络插件(CNI)来实现这一点,插件负责在宿主机上配置路由和隧道,把各个节点的容器网络打通成一个扁平网络。
第二类是Pod与Service之间的通信。由于Pod是随时可能被杀掉、重建的,Pod IP不稳定,所以K8s引入了Service抽象。Service有一个ClusterIP,这个IP是一个虚拟IP,不绑定任何真实设备,它代表一组Pod的“前端入口”。通过iptables或IPVS规则,访问ClusterIP的流量会被负载均衡转发到某个后端Pod上。
第三类是外部流量访问集群内服务。ClusterIP只在集群内部网络里有效,外部访问需要Service类型是NodePort或LoadBalancer。NodePort会在每个Node上开一个固定端口,外部请求打到任意Node的该端口,流量再被转发到对应服务的ClusterIP上。LoadBalancer则是在NodePort基础上,由云平台再挂一层负载均衡器。
第四类是网络策略控制。K8s里的NetworkPolicy类似防火墙规则,可以限制哪些Pod能访问哪些Pod。这部分需要CNI插件配合支持,Flannel默认不支持,Calico和Cilium都支持。
这四个问题之间是有依赖关系的:Pod间通信是地基,Service依赖Pod通信,外部访问依赖Service,网络策略则是在这些通信链路上加访问控制。排查问题时,也要按这个顺序从下往上查。
2. 核心技术点拆解:CNI插件、Service转发与DNS
2.1 CNI是什么,为什么Flannel、Calico、Cilium长得不一样
K8s本身不实现Pod网络,而是把网络实现交给了CNI(Container Network Interface)插件。这是一套标准接口规范,定义了容器运行时如何调用插件来给容器配置网络。kubelet在创建Pod时,会根据配置调用对应的CNI插件二进制,插件负责创建veth网卡、分配IP、配置路由。
CNI插件目前主流有三类选择:Flannel、Calico、Cilium。我给刚接触K8s的人建议是:测试环境用Flannel练手,因为配置最简单,一条命令装完省心;生产环境优先考虑Calico,功能完善,性能好,支持NetworkPolicy;有条件且对性能要求极高的场景可以上Cilium,利用eBPF技术性能非常强,但学习成本和运维复杂度都更高。
| 插件 | 底层方案 | 性能 | 是否支持NetworkPolicy | 部署复杂度 | 适用场景 |
|---|---|---|---|---|---|
| Flannel | VXLAN、host-gw | 中等(VXLAN有封装开销) | 不支持 | 极低 | 测试环境、小规模集群 |
| Calico | BGP三层路由、eBPF | 高 | 支持 | 中等 | 生产环境主流选择 |
| Cilium | eBPF | 极高 | 支持 | 较高 | 高性能场景、大规模集群 |
Flannel最常用的模式是VXLAN。虚拟化或云网络里常听到的一个词就是“隧道”。VXLAN就是把Pod发出的二层数据帧封装在UDP包里,通过底层物理网络把它从一个Node传到另一个Node。两台机器上各自跑一个叫VTEP的虚拟隧道端点,负责做封包和解包,监听UDP 8472端口。对Pod来说,它完全感觉不到底层隧道存在,就像在一个大二层交换机里通信一样。
Calico不一样,它不做隧道封装,而是走三层路由方案。Calico把每个Node当成一个路由器,通过BGP协议把各Node上的Pod网段路由信息广播出去,数据包直接走底层物理网络路由到目标Node。因为不需要额外封装,性能比VXLAN模式好,延迟也更低,而且天然支持NetworkPolicy。
我个人的选型建议是:如果你刚搭好一套集群准备学习和验证功能,Flannel足够;如果你准备让这个集群持续服务线上业务,不要犹豫,直接上Calico。等集群规模到了几百个Node、对PPS(每秒数据包数)有硬指标时,再去研究Cilium。
2.2 为什么ClusterIP“ping不通”,Service到底是怎么工作的
新手最容易问的一个问题:为什么Service的ClusterIP ping不通?网上很多答案只说“ClusterIP是虚拟IP”,但没说透。这个问题理解了,Service原理就理解了一大半。
ClusterIP确实是一个虚拟IP,它没有绑定任何物理网卡或虚拟网卡。它只存在于iptables或IPVS的规则里。当你访问一个ClusterIP时,数据包会在宿主机上被匹配到一条DNAT规则,把目标地址改写为某个后端Pod的IP,然后转发过去。而ICMP(ping)这种协议,iptables的规则根本不会去处理,因为kube-proxy只会为Service的TCP/UDP端口创建规则,不会创建协议号为ICMP的规则。所以ping ClusterIP没有任何响应,这不是故障,这是正常现象。你拿curl访问ClusterIP的端口能通,就说明Service是正常的。
kube-proxy是这一切规则的维护者。它运行在每个Node上,持续watch API Server里Service和Endpoint(或者EndpointSlice)的变化,一旦Service的后端Pod集合有变化,就立即去更新本机的iptables规则或者IPVS虚拟服务器。因此它的名字很有意思:虽然叫proxy,但实际干的是规则同步的活。
iptables模式下,对于一条Service,kube-proxy会在NAT表里创建一条DNAT规则,规则里包含这个Service关联的所有后端Pod IP。每次新连接进来,iptables会按规则里的概率权重(类似随机数处理)选一个Pod作为目标,然后把包目的地址改成这个Pod IP。
IPVS模式则是另一种实现。它利用Linux内核的IPVS模块,给每个Service创建一个虚拟服务器,后端Pod作为真实服务器注册进去,由内核来做负载均衡。相比iptables,它在大规模服务数量下性能更优,还支持更多负载均衡算法。
这里需要提醒一下:iptables模式下,如果Service某个后端Pod的IP变了,kube-proxy需要重建整条规则链。服务数量大了之后,规则更新会产生明显延迟。我们在一个二百多个Service的集群里就遇到过新增Service后,部分机器上规则长达几秒才生效的情况,这就是iptables模式的痛点。IPVS模式明显要好很多。
2.3 Service发现、DNS解析与kube-proxy之间的协作关系
有了Service之后,Pod之间怎么找到对方的Service地址呢?K8s的做法是提供内建DNS服务(CoreDNS)。每个Service创建后,K8s会自动给它记录一条DNS条目:服务名.命名空间.svc.cluster.local。Pod里只要配置了DNS,就可以通过服务名直接访问,不需要写死ClusterIP。
这个机制在一个典型调用链里是这样的:PodA要访问PodB,但它只知道PodB的Service名,于是先向CoreDNS发起DNS查询,获得Service的ClusterIP,然后再访问这个ClusterIP。kube-proxy维护的iptables规则会把请求转发给某个PodB的Pod实例。
实际运维中,这里有一个非常隐蔽的坑。有些应用自带DNS缓存,或者容器镜像里的DNS配置不合理,Pod启动后会长时间缓存第一次DNS解析结果。如果Service后端Pod发生滚动更新,ClusterIP没有变,一般没影响。但如果Service被删除重建,ClusterIP变了,缓存了旧IP的Pod就会一直访问到不存在的地址上。我们遇到过好几次“莫名其妙访问失败、重启Pod就好”的问题,排查下来都是应用层DNS缓存导致的。
从整个链路来看,请求从Pod内发起,先经过容器内eth0网卡,到宿主机veth设备,匹配宿主机上的iptables规则,被DNAT到后端Pod IP。如果是跨Node的Pod,这个包还会继续匹配路由表,走到CNI插件创建的隧道或路由通道,到达目标Node,最后进入目标Pod的网络命名空间。
理解这条链路非常重要,因为排查网络问题,本质上就是在追一个数据包的完整旅程。
3. 实操:把一套集群网络从零搭起来并验证
3.1 前置准备:一套最小规模的测试集群
这里推荐用kubeadm在虚拟机或云主机上搭建。最少需要两台机器,一台作为控制平面,一台作为工作节点。操作系统以Ubuntu 22.04或CentOS 7.9为主,配置至少2核4G。安装kubeadm、kubelet、kubectl时要注意版本选择,建议统一使用K8s当前稳定版,避免版本不匹配带来的兼容性问题。
节点初始化时,如果之前机器上装过Docker或其他容器运行时,需要先停掉并清理。集群初始化命令大致如下:
kubeadm init --pod-network-cidr=10.244.0.0/16注意这个参数:--pod-network-cidr,它指定了Pod网络的CIDR网段。这个网段必须和CNI插件默认配置的网段一致。如果Flannel默认用10.244.0.0/16,那你init时也要写10.244.0.0/16,否则Flannel启动后会因为网段冲突导致通信异常。这是新手最容易踩的第一个坑。
工作节点初始化完成后,用kubeadm join命令把节点加入集群,然后拉一个测试镜像:
kubectl run test-pod --image=busybox sleep 3600这个Pod后面会用来做网络连通性验证。
3.2 安装Flannel,验证Pod间通信
集群init完成后,此时Pod网段的网络还没有任何实现,所以即使有Pod,它们之间也无法通信。接下来需要安装Flannel CNI插件。Flannel官方清单文件里包含了必要的RBAC权限、DaemonSet和ConfigMap配置,直接应用即可。
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml应用后,会看到每个Node上都启动一个名为kube-flannel-ds的Pod。这些DaemonSet Pod做两件事:一件是把CNI配置和二进制文件写到宿主机目录,供kubelet调用;另一件是作为VTEP端点,建立VXLAN隧道。全部变成Running状态后,Pod网络就通了。
现在的验证方式是创建两个Pod,最好跑到不同的Node上,交叉验证连通性。先在Node A上跑一个Pod,记下它的IP,然后在Node B上跑另一个Pod,从这个Pod里去访问Node A上的Pod:
kubectl exec -it <nodeB上的Pod名> -- ping <nodeA上的Pod IP>能ping通说明VXLAN隧道建立成功,跨Node通信正常。如果ping不通,先检查宿主机上是否有Flannel网卡(一般叫flannel.1),再用ip route查看是否有10.244.0.0/16网段的路由记录。
3.3 验证Service访问、NodePort和流量走向
Pod间通信通了,接下来验证Service层。先部署一个工作负载并暴露Service:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test spec: replicas: 2 selector: matchLabels: app: nginx-test template: metadata: labels: app: nginx-test spec: containers: - name: nginx image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx-test ports: - port: 80 targetPort: 80 nodePort: 30080 type: NodePortService创建后,先查看它的ClusterIP:
kubectl get svc nginx-svc然后在任意一台宿主机上用curl测试:
curl http://<ClusterIP>:80有了响应,验证ClusterIP工作正常。接着在所有Node上用curl测试NodePort:
curl http://<任意Node的IP>:30080访问NodePort的原理是:数据包到达Node的30080端口后,被iptables规则接收,经过一次DNAT把目标地址改成ClusterIP,再经过第二次DNAT改成某个Pod IP,转发给Pod。Pod收到请求后,源地址会被SNAT改成宿主机地址,这是为了确保回包能原路返回。
在这个验证过程中,可以用tcpdump在Node上抓包观察:
tcpdump -i any port 30080 -nn你会看到流量先是目标端口30080,然后目标地址变成ClusterIP的80端口,最后变成某个Pod IP的80端口。眼见为实,抓一次包后你对Service的转发机制就彻底理解了。
3.4 验证跨Node通信在VXLAN下的具体封包过程
为了更直观地看VXLAN隧道的数据包形态,我们可以在源Node上用tcpdump监听UDP 8472端口,同时在宿主机上监听Pod网段流量:
tcpdump -i eth0 udp port 8472 -n -XX跨Node访问时,你会看到内层数据包的源目IP都是Pod网段的IP,而外层是物理机IP,这就是VXLAN的封包。注意:如果集群里Flannel配置成了host-gw模式,则不会封装VXLAN,数据包直接以Pod IP为源目地址通过宿主机路由转发。host-gw模式的性能更好,但要求所有Node处于同一个二层网络内,跨网段就无法工作。
4. 常见故障排查与网络调试技巧
4.1 新手必踩的七个典型网络故障
这些年不管是带新人还是处理线上问题,我总结出几个高频故障场景,整理成一个速查表,遇到问题可以直接对号入座。
| 故障现象 | 可能原因 | 排查命令 / 手段 |
|---|---|---|
| Pod一直ContainerCreating | CNI插件Pod没起来 | kubectl logs -n kube-flannel 组件Pod,检查CNI配置文件是否写入宿主机 |
| 同Node的Pod互通,跨Node不通 | VXLAN隧道异常或路由缺失 | kubectl logs看Flannel日志,ip route检查路由,确认Node间8472端口通 |
| 所有Pod都正常,但curl Service不通 | kube-proxy规则未同步或Endpoints为空 | kubectl get endpoints,检查kube-proxy Pod日志,iptables -t nat -L查看规则 |
| Pod里无法解析服务名 | CoreDNS异常或Pod DNS配置不对 | kubectl get pod -n kube-system -l k8s-app=kube-dns,检查kubelet DNS参数 |
| NodePort不通 | 端口被防火墙拦截或服务端异常 | ss -tnlp查端口,测试防火墙策略,从Node本机curl区分是规则问题还是网络问题 |
| 跨Node的Pod能通,但外部域名访问超时 | 节点上游网络MTU过大导致VXLAN包被丢弃 | ip link查看MTU,把CNI MTU调整为与底层网络一致 |
| Pod创建后网络时好时坏 | 容器网段和物理网络冲突 | 检查Pod网段是否与真实网段重叠,正确设置--pod-network-cidr |
4.2 排查网络问题的方法论:从底层往上层查
网络问题排查最忌讳上来就一顿乱试。我的习惯是严格按“物理链路、宿主机网络、CNI层、Service层、应用层”这个顺序排查。
首先是物理链路,确认两台Node之间能ping通、没有防火墙丢包。其次是宿主机网络,在Node上用ip route查看Pod网段路由是否正确。如果路由没问题,再检查CNI层,看Flannel的日志,确认VTEP隧道状态正常。接下来是Service层,确认Endpoint有没有就绪、kube-proxy规则有没有同步。最后才到应用层,确认目标容器的进程是否真的在监听端口。
这套方法放之四海而皆准。很多新手一上来就看应用日志,发现应用没报错就无从下手,其实问题往往在最底层的路由或隧道上。
4.3 抓包分析实例:一次跨Node通信的完整数据包路径
我举一个实际排障案例。某个新环境的集群里,两个不同Node的Pod之间ping不通。我们在源Pod的宿主机上抓包,先看Pod网段数据包是否到达宿主机,看eth0上是否有目标Pod IP的包。结果发现,eth0上有包,但目标地址的MAC不是预期Node的MAC。
进一步排查发现,底层交换机上启用了某种安全特性,把VXLAN的UDP广播流量隔离了。后来调整了交换机端口配置,网络恢复正常。
这个案例想说明的是:K8s网络问题不总是K8s自身的问题。云网络、物理网络、安全组、隧道协议互相纠缠,排查时一定要会分层,也要会用抓包工具分辨“协议不通”和“策略不通”。
4.4 网络策略NetworkPolicy:给集群加一道防火墙
如果集群里部署了Calico或Cilium,建议开启NetworkPolicy来控制访问。一个常见的策略场景:只允许前端Pod访问后端Pod,禁止其他Pod访问。配置示例:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-backend-except-frontend spec: podSelector: matchLabels: app: backend ingress: - from: - podSelector: matchLabels: app: frontend应用后,不符合label条件的Pod尝试访问backend的Pod,会被直接拒绝。在排障时,如果开启了NetworkPolicy,一定要把它加入排查范围,因为策略导致的不通与网络本身导致的不通,症状完全一样,但排查方向差很远。
这里有个经验:在新环境加策略时,先在测试环境用“默认拒绝全部,然后逐步放行”的方式验证相互影响,不要直接上生产。DNS解析也需要额外放行,否则一旦默认拒绝,所有Pod都无法访问CoreDNS,整个集群的服务发现都会瘫痪。我见过不止一次因为NetworkPolicy规则写错导致大规模DNS故障的情况。
4.5 核心排查工具清单
我日常用得最多的工具是这几个:
- ping:验证L3连通性,但注意对ClusterIP无效
- curl / wget:验证TCP端口是否可访问
- nslookup / dig:验证DNS解析是否正常
- ip route / ip addr:检查路由表与网卡状态
- tcpdump:抓包,看数据包走向
- kubectl get endpoints / describe svc:查看Service后端健康状态
- ps aux | grep kube-proxy:确认kube-proxy是否存活
把这些工具用熟,90%的基础网络问题都能定位。剩下10%需要深入内核网络栈,那就要去查conntrack表、看overlay网络方案本身的文档了。
5. 从维护经验看:网络方案选型与长期运维建议
5.1 业务规模不同,网络方案如何取舍
很多团队刚开始搭K8s时,直接选了个网络插件,用到后面才发现不合适。判断标准其实很简单,集群规模、是否需要NetworkPolicy、对网络性能的要求这三项基本决定了选择。
小规模集群(几十个Node以内)选Flannel完全够用,配置少、排障快。规模中等且需要做多租户隔离、访问控制的,选Calico。追求极高性能的在线业务、对数据面延迟和吞吐率极度敏感的,可以用Cilium,但团队里至少要有人能看懂eBPF日志和内核状态。
我自己维护过Flannel和Calico两种方案,结论比较明确:网络方案切换成本很高,不建议在业务稳定后频繁更换。一个集群建立之初,就要考虑未来半年到一年的容量规划,再决定网络方案。
5.2 长期维护中的几个细节习惯
第一,给Pod网段、Service网段、Node网段规划独立的地址段,避免重叠。一旦重叠,路由混乱的问题会排查到怀疑人生。
第二,监控CNI组件的资源占用。Flannel每个Node都有DaemonSet Pod,虽然占用不大,但异常时需要关注。Calico的Felix组件会定期计算策略规则,在大规模集群里CPU占用可能上涨,需要提前设置监控告警。
第三,定期检查kube-proxy的异常重启。iptables模式下,如果kube-proxy频繁启动,意味着规则反复重建,会影响新Service的生效时间。
第四,做好网络相关事件的审计。通过kubectl get events可以看到很多资源的变动时间,但对网络插件相关的日志,建议集中采集到日志平台,方便事后回看。网络问题是典型的偶发性问题,没有日志复盘就会非常被动。
第五,升级CNI插件前一定要阅读升级文档。Flannel和Calico的版本升级通常要求在指定版本范围内进行,跨大版本升级有可能导致节点网络中断。先在一个测试节点上验证,再滚动升级,这是比较稳妥的做法。
我个人在实际操作中的体会是,K8s网络通信其实就是一个大型的“数据包路由游戏”:从Pod里的veth,到宿主机上的路由和iptables规则,再到跨节点的隧道或BGP路由,最后又是对端的veth。只要把这趟旅程的每个站都理解透了,遇到故障就能靠“数据包到了哪里、下一跳是谁”来快速定位。
如果这篇文章帮你把K8s网络的大致轮廓建立起来了,可以点个关注,后面我会继续更新企业运维里更深入的网络排障案例、存储对接、集群升级这些实战话题。有问题也欢迎在评论区把具体现象和日志贴出来,我看到会逐一回复。