Kubernetes网络模型从Pod到Service全链路解析CNI插件选型与跨节点通信故障排查实战指南
先说句掏心窝子的话,K8s网络这块确实容易让人头大。我刚开始接触的时候,也经常被Pod网络、Service网络、CNI插件这些概念绕晕。但说实话,一旦你搞清楚了底层逻辑,其实没那么可怕。今天就带你把这个链路彻底摸透,从Pod怎么通信到Service怎么转发,再到CNI怎么选、故障怎么查,咱们一步步来。
Pod之间是怎么说上话的
咱们先搞明白一个最基础的问题:Pod到底是怎么在网络里”看见”彼此的。
在Kubernetes里,每个Pod都被分配了一个独立的IP地址,这个IP对整个集群都是唯一的。你可以把Pod想象成一台台独立的小服务器,每台都有自己的”门牌号”——也就是IP。但问题在于,这些Pod可能散落在集群的不同节点上,就像住在城市不同区的人,怎么互相联系呢?
这就需要一个网络插件来帮忙,也就是CNI(Container Network Interface)。CNI插件的工作,就是给每个Pod分配IP,并保证跨节点的Pod能够互相通信。
让我给你看一个真实的网络拓扑。假设你有一个三节点的集群:
Node1 (192.168.1.10) Node2 (192.168.1.11) Node3 (192.168.1.12)
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ Pod-A: 10.244.1.2 │ │ Pod-B: 10.244.2.3 │ │ Pod-C: 10.244.3.4 │
│ (nginx) │ │ (redis) │ │ (postgres) │
│ eth0: 10.244.1.2/24 │ │ eth0: 10.244.2.3/24 │ │ eth0: 10.244.3.4/24 │
└──────────┬───────────┘ └──────────┬───────────┘ └──────────┬───────────┘
│ │ │
veth pair veth pair veth pair
│ │ │
cni0 bridge cni0 bridge cni0 bridge
│ │ │
└──────────────────────────────┴──────────────────────────────┘
物理网络(底层连通)
你看,每个Pod都有个veth pair(虚拟以太网对),一端在Pod容器里(比如eth0),另一端连到节点的网桥上。这样Pod-A和Pod-B虽然不在同一个物理机上,但通过网络插件搭建的隧道或桥接,它们就像在同一个局域网里一样,可以直接用IP通信。
这里有个关键概念我得讲清楚:Pod IP是临时的。Pod一旦重建,IP就会变。所以你不能像用传统服务器那样,记住某个IP就去找它。这就是为什么Kubernetes引入了Service这个概念。
Service:给Pod配个”永不更换的门牌号”
Pod IP会变的这个问题,在Kubernetes里是非常现实的。你想想看,一个部署了5个副本的Deployment,如果其中两个Pod因为某些原因被驱逐重建了,它们的IP就变了。如果你硬编码了这些IP去访问,肯定出问题。
Service就是为了解决这个问题而生的。你可以把Service想象成一个稳定的”接待处”,它有一个不变的IP(ClusterIP),不管后面有多少个Pod在变化,你只需要访问这个Service IP就行。
让我用具体的例子来说明。假设你有一个nginx Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
这个Deployment创建后,会有3个Pod,每个Pod有自己的IP,比如10.244.1.5、10.244.2.6、10.244.3.7。这些Pod是动态的,随时可能变化。
现在你创建一个Service:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
这个Service会有一个ClusterIP,比如10.96.0.50。所有对nginx的访问,只需要访问10.96.0.50:80就可以了。Kubernetes会在每个节点上运行一个kube-proxy,它会监听这个Service的变化,然后在节点上配置iptables或nftables规则,把发往10.96.0.50的流量,转发到后端的某个Pod IP上。
这就是Service的工作原理:它提供了一个稳定的访问入口,背后通过kube-proxy和iptables/nftables规则,把流量负载均衡到各个Pod上。
这里有个细节值得注意:Service IP本身并不会真正路由流量。它只是一个”逻辑地址”。当你的Pod发送数据包到Service IP时,这个数据包在节点上会被iptables规则拦截并转发到后端Pod。这就是为什么Service通信速度很快,因为它是本地转发,不需要跨节点路由。
CNI插件:整个网络的”幕后工程师”
说完了Pod和Service,咱们得聊聊真正干活的——CNI插件。CNI插件决定了你的Pod网络是怎么搭起来的。选对了插件,网络就顺畅;选错了,排查问题能让你怀疑人生。
目前主流的CNI插件有这几个:
Calico:这是我在生产环境用得最多的。它基于BGP路由,性能非常好,支持网络策略(NetworkPolicy),可以精确控制哪些Pod可以通信、哪些不行。Calico的工作原理是给每个Pod分配IP,然后在每个节点上配置BGP路由,让节点之间互相学习路由信息。这样Pod之间的通信就像在同一个局域网里一样自然。
# Calico网络策略示例
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-nginx-from-frontend
namespace: default
spec:
podSelector:
matchLabels:
app: nginx
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
这个策略的意思是:只允许带有app=frontend标签的Pod访问带有app=nginx标签的Pod的80端口。其他所有访问都被拒绝。这就是Calico网络策略的威力,你可以精细化管理Pod之间的通信。
Flannel:这个插件相对简单,适合小型集群或者测试环境。Flannel使用VXLAN隧道来跨节点通信,配置简单,但性能上比Calico差一些。如果你不在意性能,只想快速让集群跑起来,Flannel是个不错的选择。
Cilium:这是近年来崛起的一匹黑马。Cilium基于eBPF技术,性能非常出色,而且在网络策略和可观测性方面有很强的优势。如果你的集群对性能要求很高,或者需要细粒度的网络策略,Cilium值得考虑。
# Cilium网络策略示例
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-nginx-from-frontend
spec:
endpointSelector:
matchLabels:
app: nginx
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "80"
protocol: TCP
Weave Net:这个插件特点是加密通信,每个Pod之间的流量都是加密的,安全性很好。但性能上相对较弱,适合对安全性要求高、对性能要求不高的场景。
选哪个插件,取决于你的具体需求。如果你的集群规模大、性能要求高,Calico或Cilium是不错的选择。如果是小型集群或者开发环境,Flannel就够了。如果需要强加密,可以考虑Weave Net。
跨节点通信的底层原理
搞清楚CNI插件之后,咱们得深入看看跨节点通信到底是怎么实现的。这是很多初学者困惑的地方:Pod-A在Node1上,Pod-B在Node2上,它们怎么互相通信的?
不同的CNI插件有不同的实现方式,但大体可以分为两类:路由模式和隧道模式。
路由模式(比如Calico):每个节点都知道整个集群的Pod网段路由。Node1通过BGP协议学习到10.244.2.0/24这个网段在Node2上,10.244.3.0/24在Node3上。当Pod-A要访问Pod-B时,数据包从Pod-A发出,经过Node1的网桥,然后根据路由表转发到Node2。整个过程就像在路由网络上一样,只是这些路由是CNI插件动态配置的。
# 在Node1上查看路由表
$ ip route
default via 192.168.1.1 dev eth0
10.244.0.0/16 dev cni0 proto kernel scope link src 10.244.1.1
10.244.2.0/24 via 192.168.1.11 dev eth0 # 到Node2的Pod网段
10.244.3.0/24 via 192.168.1.12 dev eth0 # 到Node3的Pod网段
你看,Node1的路由表里已经有了到其他节点Pod网段的路由。这就是Calico通过BGP动态学习到的。当Pod-A发送数据包到Pod-B时,内核根据这个路由表,直接把数据包转发到Node2的IP地址上。
隧道模式(比如Flannel):Node1和Node2之间建一条VXLAN隧道。Pod-A发送的数据包被封装在VXLAN隧道里,从Node1发送到Node2,Node2再解封装,把原始数据包交给Pod-B。这种方式的好处是不需要在每个节点上配置路由,但性能上会稍差一些,因为多了封装和解封装的开销。
# 在Node1上查看VXLAN隧道
$ ip link show flannel.1
3: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN
link/ether 3a:5c:6f:12:34:56 brd ff:ff:ff:ff:ff:ff
kube-proxy:Service流量的”交通警察”
前面提到了kube-proxy,这是Service通信中非常关键的角色。kube-proxy运行在每个节点上,它负责监听Service的变化,然后在节点上配置网络规则,把发往Service IP的流量转发到后端的Pod。
kube-proxy有三种工作模式:iptables、ipvs和userspace。userspace模式比较老旧,现在基本不推荐使用了。iptables模式是默认模式,而ipvs模式性能更好,适合大规模集群。
让我给你展示一下iptables模式下的规则是怎么配置的:
# 查看kube-proxy配置的iptables规则
$ sudo iptables-save | grep nginx-service
-A KUBE-SERVICES -d 10.96.0.50/32 -p tcp -m comment --comment "default/nginx-service cluster IP" -m tcp --dport 80 -j KUBE-SVC-XXXX
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAA
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.50000 -j KUBE-SEP-BBBB
-A KUBE-SVC-XXXX -j KUBE-SEP-CCCC
-A KUBE-SEP-AAAA -p tcp -m comment --comment "default/nginx-service" -j DNAT --to-destination 10.244.1.5:80
-A KUBE-SEP-BBBB -p tcp -m comment --comment "default/nginx-service" -j DNAT --to-destination 10.244.2.6:80
-A KUBE-SEP-CCCC -p tcp -m comment --comment "default/nginx-service" -j DNAT --to-destination 10.244.3.7:80
这段规则的意思是:当有数据包发到10.96.0.50:80时,kube-proxy会随机把它转发到三个Pod中的一个(10.244.1.5:80、10.244.2.6:80或10.244.3.7:80)。这就是Service负载均衡的工作原理。
如果你切换到ipvs模式,这些iptables规则会少很多,性能也会更好:
# 查看ipvs规则
$ sudo ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.50:80 rr
-> 10.244.1.5:80 Route 1 0 15
-> 10.244.2.6:80 Route 1 0 12
-> 10.244.3.7:80 Route 1 0 18
ipvs模式用内核的负载均衡器来处理流量转发,比iptables的多个规则匹配要快得多,特别是在Pod数量很多的时候,性能差异非常明显。
跨节点通信故障排查实战
好了,理论讲得差不多了,咱们来点实战的。在实际工作中,跨节点通信出问题是最常见的,也是最让人头疼的。下面我分享几个典型的排查案例和思路。
案例一:Pod之间无法跨节点通信
假设你的Pod-A在Node1上,Pod-B在Node2上,Pod-A ping不通Pod-B。该怎么排查?
第一步,先确认Pod的网络配置是否正确:
# 进入Pod-A容器
$ kubectl exec -it pod-a -- bash
# 在容器内检查IP地址
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
inet 127.0.0.1/8 scope host lo
2: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
inet 10.244.1.2/32 brd 10.244.1.2 scope global eth0
确认Pod有正确的IP地址。如果没有,可能是CNI插件出了问题。
第二步,在节点上检查CNI网桥和路由:
# 在Node1上检查
$ ip link show cni0
$ ip addr show cni0
$ ip route
确认节点的网桥存在,路由表里有正确的路由条目。
第三步,检查节点的防火墙规则:
# 查看iptables规则
$ sudo iptables -L -n -v
# 查看firewalld状态(如果是CentOS/RHEL)
$ sudo firewall-cmd --list-all
有时候防火墙规则会阻止跨节点的Pod通信,特别是UDP流量(VXLAN隧道用的就是UDP 8285端口)。确保这些端口是开放的。
# 如果是Flannel,确保UDP 8285端口开放
$ sudo iptables -I INPUT -p udp --dport 8285 -j ACCEPT
# 如果是Calico,确保TCP和UDP 179端口开放(BGP)
$ sudo iptables -I INPUT -p tcp --dport 179 -j ACCEPT
$ sudo iptables -I INPUT -p udp --dport 179 -j ACCEPT
第四步,从节点层面测试连通性:
# 在Node1上ping Node2上的Pod-B IP
$ ping 10.244.2.3
# 如果不通,检查Node1到Node2的物理网络
$ ping 192.168.1.11
如果物理网络不通,那就是底层网络的问题了,需要检查交换机、路由器等网络设备。
案例二:Service无法访问
Pod之间能通,但Service访问不了。这通常跟kube-proxy有关。
第一步,检查kube-proxy是否正常运行:
$ kubectl get pods -n kube-system | grep kube-proxy
kube-proxy-abc123 1/1 Running 0 10d
kube-proxy-def456 1/1 Running 0 10d
如果有Pod处于CrashLoopBackOff或Error状态,查看日志:
$ kubectl logs kube-proxy-abc123 -n kube-system
第二步,检查iptables或ipvs规则是否正确配置:
# 检查iptables规则
$ sudo iptables-save | grep nginx-service
# 检查ipvs规则
$ sudo ipvsadm -Ln | grep nginx-service
如果没有规则,可能是kube-proxy没有正确同步Service信息。检查kube-proxy的配置文件:
$ kubectl get configmap -n kube-system kube-proxy -o yaml
确认mode字段设置正确,iptables或ipvs都支持。
第三步,测试Service ClusterIP是否可达:
# 在Pod内测试
$ kubectl exec -it pod-a -- curl http://10.96.0.50
# 如果在Pod内不通,检查节点层面的路由
$ traceroute 10.96.0.50
Service IP通常只在节点上有效,Pod发送的数据包在节点上被iptables/nftables拦截后转发到后端Pod。如果你在Pod内直接访问Service IP,数据包会先到达节点,然后被转发。
案例三:跨节点DNS解析失败
DNS问题在Kubernetes集群里也很常见。如果Pod无法解析Service域名,可能是CoreDNS或kube-dns出了问题。
第一步,检查DNS Pod是否正常运行:
$ kubectl get pods -n kube-system | grep dns
coredns-5644d7b6d9-abc12 1/1 Running 0 10d
coredns-5644d7b6d9-def45 1/1 Running 0 10d
第二步,测试DNS解析:
# 在Pod内测试
$ kubectl exec -it pod-a -- nslookup kubernetes.default.svc.cluster.local
$ kubectl exec -it pod-a -- nslookup nginx-service.default.svc.cluster.local
如果解析失败,检查 resolv.conf:
$ kubectl exec -it pod-a -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
确保nameserver指向集群的DNS服务IP(通常是Service的ClusterIP)。
第三步,检查CoreDNS日志:
$ kubectl logs coredns-5644d7b6d9-abc12 -n kube-system
案例四:网络策略导致通信被阻断
有时候Pod之间无法通信,是因为NetworkPolicy阻止了流量。
# 查看当前命名空间的网络策略
$ kubectl get networkpolicies -n default
# 查看策略详情
$ kubectl describe networkpolicy allow-nginx-from-frontend -n default
如果发现有策略阻止了通信,需要调整策略规则,允许必要的流量。
性能优化与最佳实践
排查完问题,咱们再聊聊怎么让网络跑得更好。
1. 选择合适的CNI插件
对于大型集群(100+节点),我强烈推荐使用Calico或Cilium。它们的性能优于Flannel,特别是在网络策略丰富的场景下。如果你的集群规模小,Flannel完全够用,配置也简单。
2. 启用ipvs模式
如果选择使用kube-proxy,并且集群规模较大,建议切换到ipvs模式:
# kube-proxy配置
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
strictARP: true
scheduler: rr
ipvs模式可以显著减少iptables规则数量,提升转发性能。
3. 优化MTU设置
如果使用了VXLAN等隧道模式,需要注意MTU设置。VXLAN封装会增加40字节的开销,所以需要适当减小MTU:
# 检查当前MTU
$ ip link show cni0
# 调整MTU(以Flannel为例)
$ flanneld --iface=eth0 --mtu=1450
MTU不匹配会导致数据包分片,影响网络性能。确保所有节点和网络设备都使用一致的MTU设置。
4. 监控网络指标
网络问题往往需要数据来定位。建议使用Prometheus + Grafana监控网络指标:
# Prometheus监控CNI插件的指标
scrape_configs:
- job_name: 'calico'
static_configs:
- targets: ['localhost:9091']
- job_name: 'kube-proxy'
static_configs:
- targets: ['localhost:10249']
关键指标包括:数据包转发速率、连接数、错误包数量、延迟等。通过这些指标,可以快速定位网络瓶颈。
总结
Kubernetes网络这个主题,说复杂也复杂,说简单也简单。核心就是三个东西:Pod网络(CNI插件)、Service网络(kube-proxy)、网络策略(NetworkPolicy)。理解了这三者的关系,大部分网络问题都能迎刃而解。
选CNI插件的时候,别盲目追新,要根据集群规模和实际需求来定。小型集群用Flannel,大型集群用Calico或Cilium。排查故障的时候,要有系统性的思路:从Pod配置到节点网络,从iptables规则到物理网络,一层一层往下查。
最后记住一点:Kubernetes网络不是魔法,它就是标准的Linux网络技术(网桥、路由、iptables/nftables、BGP、VXLAN等)的组合。理解了底层原理,网络问题就不可怕了。
如果你在实际工作中遇到具体的网络问题,欢迎带着现象和数据来讨论。网络排查有时候就是这样,多问几个为什么,答案自然就出来了。
