Kubernetes 网络模型:CNI 插件与 Pod 间通信的底层实现

文章目录

    • [Kubernetes 网络模型的核心原则](#Kubernetes 网络模型的核心原则)
    • [CNI 插件体系:K8s 网络的真正实现者](#CNI 插件体系:K8s 网络的真正实现者)
      • [CNI 的工作位置](#CNI 的工作位置)
      • [CNI 插件的生命周期](#CNI 插件的生命周期)
    • [同节点 Pod 通信:Veth Pair 与网桥](#同节点 Pod 通信:Veth Pair 与网桥)
    • [跨节点 Pod 通信:路由与 Overlay 两种路径](#跨节点 Pod 通信:路由与 Overlay 两种路径)
      • 路由模式:直接发送,无需封装
      • [Overlay 模式:VXLAN 封装穿越复杂网络](#Overlay 模式:VXLAN 封装穿越复杂网络)
      • [VXLAN 封装的完整路径](#VXLAN 封装的完整路径)
      • [Flannel vs Calico:架构权衡](#Flannel vs Calico:架构权衡)
    • [HostPort 与 ContainerPort:两个端口的本质区别](#HostPort 与 ContainerPort:两个端口的本质区别)
    • [生产问题:CNI 插件版本不兼容导致的网络异常](#生产问题:CNI 插件版本不兼容导致的网络异常)
    • [DNS 与 Service 发现:网络之上的名字解析](#DNS 与 Service 发现:网络之上的名字解析)
    • 总结

前置知识:本文需要读者对 Linux 网络基础有初步了解,包括 IP 地址、子网掩码、路由表和 VLAN 的基本概念。若对 Docker 网络模式尚不熟悉,建议先阅读本系列前面的文章,或参考"网络与安全系列"的 Cilium 相关文章。


Kubernetes 网络模型的核心原则

Kubernetes 官方文档定义了网络实现的四个必须解决的问题:Pod 之间的通信、Pod 与 Service 之间的通信、Service 对外部的暴露,以及外部对 Service 的访问。前两个问题的答案完全取决于 CNI 插件的实现,后两个问题则由 Kubernetes 的 Service 机制和 Ingress Controller 处理。

理解 K8s 网络模型的关键在于:Kubernetes 本身并不实现网络,它只定义了一套规则。具体怎么让 Pod 能互相通信,取决于部署时选择了哪个 CNI 插件。CNI 插件负责 IP 地址分配、Veth Pair 创建、桥接和路由规则生成------所有这些工作都在 Pod 创建时由 CNI 插件完成。

这套模型遵循三个核心原则:

原则一:Pod 之间通信无需 NAT。同一 Pod 内的多个容器共享同一个网络命名空间,通过 localhost 通信。跨 Pod 通信时,源 Pod 看到的目标 Pod IP,就是目标 Pod 实际的 IP 地址,网络地址转换(NAT)不会介入这个过程。

原则二:Node 与 Pod 之间通信无需 NAT。节点上的进程(包括 kubelet、容器运行时、以及宿主机上的其他进程)访问 Pod IP 时,不需要经过 NAT 转换。这个原则使得节点可以直接通过 Pod IP 与 Pod 通信,是很多网络调试工具的基础。

原则三:Pod 看到的自己的 IP 与外部看到的 IP 一致。Pod 内部的 eth0 接口 IP 地址,同时也是外部网络看到的该 Pod 的地址。这意味着从 Pod 内部获取自己的 IP 地址后直接对外通信,外部看到的源地址就是这个 IP------而不是经过 NAT 转换后的某个随机地址。


CNI 插件体系:K8s 网络的真正实现者

CNI 的工作位置

当 kubelet 需要创建一个 Pod 时,它并不会直接操作网络命名空间,而是调用容器运行时(containerd 或 CRI-O),再由容器运行时调用 CNI 插件。整个调用链如下:
kubelet

(--network-plugin=cni

--cni-conf-dir=/etc/cni/net.d)
容器运行时

(containerd/cri-o)
CNI 插件

(调用可执行文件)
CNI 配置文件

(/etc/cni/net.d/*.conflist)
CNI 二进制文件

(/opt/cni/bin/*)
网络命名空间

(创建 veth0、eth0)
Pod 网络命名空间
根网络命名空间

(vethXXX + 路由规则)

kubelet 通过 CNI_CONF_DIR(默认 /etc/cni/net.d)读取插件配置,按文件名字母顺序找到第一个有效的配置文件,然后调用对应的 CNI 二进制文件完成网络设置。这个顺序很重要:如果部署了两个 CNI 插件,后加载的会覆盖前一个的配置,导致网络行为不可预期。因此一个集群通常只部署一个 CNI 插件。

CNI 插件的生命周期

CNI 插件对每个 Pod 的操作分为四个阶段:ADD(ADD)CHECK(检查)DELETE(删除)VERSION(版本查询)

ADD 阶段最为关键,插件需要完成以下工作:为 Pod 命名空间创建 Veth Pair,将宿主端的 Veth 接入网桥或路由表,从 CNI 插件管理的 IP 地址池中为 Pod 分配 IP 地址,并配置必要的网络策略规则。

DELETE 阶段负责清理:删除 Veth Pair、释放 IP 地址、移除路由规则。理解这两个阶段对于排查 Pod 创建或删除时的网络问题至关重要------很多网络故障的根因就是 DELETE 阶段的清理逻辑没有正确执行。


同节点 Pod 通信:Veth Pair 与网桥

Veth Pair 的工作原理

在讨论跨节点通信之前,需要先理解同节点 Pod 通信的完整路径,因为这是理解更复杂场景的基础。

Veth Pair(Virtual Ethernet Pair)是 Linux 的成对虚拟网络设备。数据从一端进入后,必然从另一端出现------就像一根网线的两端,只是这根"网线"是纯软件实现的。在 Kubernetes 中,每个 Pod 的网络命名空间中都有一个 eth0 接口,这实际上就是 Veth Pair 的一端;另一端在宿主机的根网络命名空间中,通常命名为 vethxxxxxx
宿主机根网络命名空间
Pod A 网络命名空间
Veth Pair
eth0

10.244.0.10
veth0a3c2b

(无 IP)
cni0 网桥

(管理 10.244.0.0/24)
eth0

192.168.1.100

需要特别注意:宿主机端的 Veth 设备本身不配置 IP 地址。它的作用仅仅是将 Pod 命名空间与宿主机根网络命名空间连接起来。真正的 IP 地址分配发生在 Pod 侧的 eth0 上。

网桥在同节点通信中的作用

同节点 Pod 之间的通信依赖 Linux 网桥(Bridge)。当 Pod A(10.244.0.10)向 Pod B(10.244.0.11)发送数据包时,宿主机上的路由决策如下:

  1. Pod A 的 eth0 发送数据包,源 MAC 为 Pod A,目标 MAC 为 Pod B
  2. 数据包通过 Veth Pair 到达宿主机端的 veth0a3c2b
  3. 宿主机检查路由表,发现 10.244.0.0/24 目标网络是直连网络
  4. 网桥 cni0 根据目标 MAC 地址查找转发表(CAM 表),找到 Pod B 对应的 Veth 端口
  5. 数据包从网桥的对应端口发出,进入 Pod B 的命名空间

整个过程发生在数据链路层(Layer 2),不需要经过 iptables 或 IP 路由,所以延迟极低。这是 Kubernetes 同节点 Pod 通信能够保持低延迟的技术基础。


跨节点 Pod 通信:路由与 Overlay 两种路径

跨节点通信是 K8s 网络中最复杂的部分。数据包从源节点 Pod 发出,到达目标节点 Pod,需要跨越节点之间的物理网络。这个过程有两种实现思路:纯路由(Underlay)Overlay(覆盖网络)

路由模式:直接发送,无需封装

在路由模式下,源 Pod 发送的数据包目标 IP 是目标 Pod 的 IP(例如 10.244.1.10),源节点通过路由表将数据包直接发送到目标节点,不需要额外的封装。

这种模式的关键前提是:每个节点的 Pod CIDR 必须对所有节点可见 。也就是说,节点之间的路由器必须知道 10.244.0.0/24 属于节点 A、10.244.1.0/24 属于节点 B,才能正确路由数据包。

Flannel 的 host-gw 后端和 Calico 的 BGP 模式都采用这种方案。以 Calico BGP 模式为例:每个节点上运行一个 Bird(BGP 守护进程),节点之间建立 BGP 对等连接,相互宣告自己负责的 Pod CIDR 路由。节点 A 的路由表中会直接包含节点 B 的 Pod CIDR 条目,下一跳是节点 B 的 IP 地址。

路由模式的优势 是性能最优:数据包直接由物理网络转发,不需要额外的封装/解封装开销。劣势是对网络环境有要求------节点之间必须是二层可达(即可以直接 ARP),或者节点 Pod CIDR 对整个物理网络可见,这在云环境和跨公网的场景中通常无法满足。

Overlay 模式:VXLAN 封装穿越复杂网络

当节点之间无法直接路由时,Overlay 模式就成了唯一的可行方案。Overlay 的核心思想是:在现有物理网络之上,构建一个虚拟的网络层。Pod IP 只在这个虚拟网络层中有意义,跨节点传输时需要将整个数据包封装到物理网络可路由的外层包中。

VXLAN(Virtual Extensible LAN)是当前最主流的 Overlay 协议。理解 VXLAN 的封装结构是理解跨节点 Pod 通信的前提:
内层(Inner,Pod 网络层)
外层(Outer,物理网络层)
外层 IP Header

Src: 192.168.1.10(节点A)

Dst: 192.168.1.20(节点B)
UDP Header

Src Port: 随机

Dst Port: 4789(VXLAN)
VXLAN Header

VNI: 1(24位虚拟网络标识符)

GBP: 可选
内层 IP Header

Src: 10.244.0.10(Pod A)

Dst: 10.244.1.10(Pod B)
TCP/UDP Header

外层 IP Header 使用的是节点的真实 IP(节点 A 和节点 B 的物理网络地址),物理网络路由器根据这个地址将数据包从节点 A 送到节点 B。VXLAN Header 中的 VNI(VXLAN Network Identifier)是 24 位的标识符,最多支持 1600 万个相互隔离的虚拟网络------这解决了 Kubernetes 多租户或大规模集群的网络隔离问题。

VXLAN 封装的完整路径

理解一个具体的 VXLAN 封装场景,有助于彻底掌握这个机制。假设节点 A 上的 Pod A(10.244.0.10)向节点 B 上的 Pod B(10.244.1.10)发送数据包,完整路径如下:
Pod B 10.244.1.10 VTEP(节点B) eth0(节点B) eth0(节点A) VTEP flannel.1 / cali0 cni0 网桥 veth0a3c2b Pod A 10.244.0.10 Pod B 10.244.1.10 VTEP(节点B) eth0(节点B) eth0(节点A) VTEP flannel.1 / cali0 cni0 网桥 veth0a3c2b Pod A 10.244.0.10 VTEP 查询本地 FDB(转发表) 找到 10.244.1.10 对应的 VNI 和节点B IP 发送数据包 Dst: 10.244.1.10 数据包进入网桥 网桥查询路由 10.244.1.10 不在本地直连网络 VXLAN 封装 外层Dst: 192.168.1.20(节点B) VNI: 1 物理网络路由 节点A → 节点B 接收 UDP 4789 数据包 识别为 VXLAN 封包 VXLAN 解封装 提取内层数据包 VNI: 1 校验通过 内层数据包 Dst: 10.244.1.10 网桥查询 MAC 表 找到 Pod B 对应端口 数据包到达 Pod B

这里有一个值得深入的点:VTEP(VXLAN Tunnel Endpoints)的 FDB(转发表)是如何填充的? 在 Flannel 中,flanneld 守护进程负责维护各节点 Pod CIDR 与节点 IP 的映射关系,并将这些信息写入内核的 FDB 表。在 Calico 中,Felix 组件完成同样的工作。如果是 Calico BGP 模式,则完全不需要 FDB------因为数据包直接通过物理网络路由,不需要 VTEP 封装。

Flannel vs Calico:架构权衡

这是选择 CNI 插件时最核心的问题。大多数对比文章只列功能清单,这里从网络工程师的视角分析三种方案的取舍:

维度 Flannel (VXLAN) Calico (BGP) Calico (VXLAN)
跨节点通信方式 VXLAN 封装 纯路由 VXLAN 封装
网络性能 中等(封装开销) 最优(无封装) 中等(封装开销)
网络可达性要求 节点 IP 可达即可 需大二层或 BGP 对等连接 节点 IP 可达即可
Network Policy 不支持 支持 L3/L4/L7 支持 L3/L4/L7
大规模集群扩展性 好(VXLAN VNI 足够多) 需 Route Reflector
运维复杂度 高(BGP 配置)
加密通信 无(需外部方案) WireGuard(可选) WireGuard(可选)

一个关键误解需要澄清 :很多人认为 Calico 比 Flannel"更强大",但在某些场景下 Calico 的 BGP 模式反而是陷阱。如果节点分布在不同的可用区或需要跨公网通信,BGP 路由模式会因为物理网络不支持 BGP 宣告而完全失效,此时选择 Calico BGP 只会带来额外的运维复杂度。正确的思路是:先确认节点之间的网络环境,再决定使用哪种模式


HostPort 与 ContainerPort:两个端口的本质区别

在 Pod 的端口配置中,HostPort 和 ContainerPort 是两个容易混淆的概念。它们出现在同一个 YAML 的 ports 字段中,但含义完全不同:

ContainerPort 是容器内部应用程序监听的端口。这是应用程序绑定到容器内网络命名空间的具体端口,Pod 中的服务通过这个端口接收流量。在 Pod 定义中声明 ContainerPort 是最佳实践,即使应用程序已经通过 EXPOSE 指令声明了端口,显式声明 ContainerPort 能让 Kubernetes 和其他工具正确识别服务端口。

HostPort 是暴露在宿主机网络接口上的端口。使用 HostPort 时,宿主机上会占用一个端口,外部流量通过 http://<NodeIP>:<HostPort> 直接到达 Pod,而不经过 kube-proxy 或 Service 层。

yaml 复制代码
# HostPort 与 ContainerPort 的对比示例
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    # ContainerPort:容器内 nginx 监听 80
    - containerPort: 80
    # HostPort:宿主机上对外暴露 30080
      hostPort: 30080

为什么要区分这两个概念? ContainerPort 是容器运行时和网络命名空间的概念,与 Kubernetes 本身无关。HostPort 则是一种将 Pod 直接暴露到节点网络的方式------它绕过了 Service 层,意味着这个 Pod 不会获得 Service 提供的负载均衡、健康检查和自动故障转移能力。

生产环境中 HostPort 的典型用途是测试场景(快速验证服务)或 DaemonSet 节点级别的指标采集(让 Prometheus 直接从各节点的固定端口抓取数据)。对于正常的生产服务,强烈建议通过 ClusterIP 或 NodePort Service 暴露,而非直接使用 HostPort。


生产问题:CNI 插件版本不兼容导致的网络异常

问题场景

在 Kubernetes 集群的日常运维中,有一种故障特别隐蔽:节点内核升级后,部分 Pod 无法联网,但节点本身和网络连通性都是正常的 。kubectl 可以连接 API Server,kubectl get nodes 显示节点状态正常,但 Pod 内无法访问 Service 或其他 Pod 的 IP。

这种问题的根因几乎总是 CNI 插件与新内核版本之间的兼容性问题。

根因分析

CNI 插件的部分功能依赖于内核的网络特性。以 VXLAN 为例,VXLAN 的封装和解封装操作在内核的网络栈中完成。不同的内核版本在内核 API(如 ndo_neigh_setupndo_fdb_add 等)的行为上有差异。如果 CNI 插件(及其依赖的 iptables/nftables 规则)与新内核的网络 API 不兼容,就会导致封装失败或路由规则不生效。

另一个常见的兼容性问题来源是 iptables vs nftables。Linux 内核 5.0+ 开始逐步支持 nftables,部分 CNI 插件(尤其是较老的版本)在使用 iptables 规则的同时与 nftables 发生冲突,导致规则不生效。

诊断路径













Pod 内无法联网
节点间网络是否正常?
检查物理网络

节点间 ping/SSH
物理网络正常?
修复物理网络
检查节点防火墙

和安全组规则
API Server 连通?
检查 kubeconfig

API Server 健康状态
Pod DNS 是否正常?
检查 CoreDNS

和 DNS Policy
CNI 插件版本是否兼容?
查看 kubelet 日志

是否有 CNI 错误
CNI 插件与内核版本不兼容
重新安装兼容版本的 CNI
检查 iptables vs nftables 冲突
iptables -L -n

规则是否完整?
禁用 nftables

或升级 CNI 插件
检查 Veth Pair 状态
ip link show

veth 是否正常 UP

解决步骤

第一步:确认 CNI 插件版本与当前内核版本的兼容性。最直接的方式是查看 kubelet 日志中的 CNI 相关错误:

bash 复制代码
# 查看 kubelet 日志中的 CNI 错误
journalctl -u kubelet --no-pager | grep -i cni

# 查看当前加载的 CNI 插件版本
ls -la /opt/cni/bin/
cat /etc/cni/net.d/*.conflist | grep -i version

第二步:如果确认是版本不兼容问题,升级 CNI 插件到与当前内核兼容的版本:

bash 复制代码
# 下载 CNI 插件(以 containernetworking/plugins 为例)
CNI_PLUGINS_VERSION="v1.4.1"
ARCH="amd64"
DEST="/opt/cni/bin"

sudo mkdir -p "$DEST"
curl -L "https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${ARCH}-${CNI_PLUGINS_VERSION}.tgz" | sudo tar -C "$DEST" -xz

# 重建节点上所有 Pod(触发 CNI ADD 重新执行)
kubectl delete pod --all -n kube-system

第三步:处理 iptables vs nftables 冲突。如果内核启用了 nftables 但 CNI 插件仍然使用 iptables,可能需要显式切换回 iptables:

bash 复制代码
# 切换回 iptables 模式
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

# 重启 kubelet
sudo systemctl restart kubelet

预防措施 :在内核升级前,先在测试环境中验证 CNI 插件的兼容性;每次升级 Kubernetes 版本时同步升级 CNI 插件;使用 kubeadm 部署时,kubeadm init/join 的 --cri-socket 参数能够确保容器运行时版本与 Kubernetes 版本匹配。


DNS 与 Service 发现:网络之上的名字解析

虽然 Service 是 Kubernetes 的核心概念,但它的实现完全依赖底层的网络基础设施。ClusterIP Service 的工作原理是 kube-proxy 在每个节点上维护的 iptables 或 IPVS 规则表。当 Pod 访问 http://nginx.default.svc.cluster.local 时,CoreDNS 将这个名字解析为 ClusterIP(一个虚拟 IP,不存在于任何网络接口上),iptables/IPVS 规则将这个虚拟 IP 的流量 NAT 转发到实际的后端 Pod IP。
Pod A

访问 nginx 服务
CoreDNS

nginx.default.svc.cluster.local

→ 10.96.45.21
iptables/IPVS 规则

10.96.45.21 → Pod IPs
负载均衡策略
Pod B #1
Pod B #2
Pod B #3

这个三层转发(DNS 解析 → iptables/IPVS 路由 → 实际 Pod)解释了为什么 Service 发现有时候会变慢:当 DNS 缓存过期或 iptables 规则过多时,Service 发现的延迟会显著上升。在高频短连接场景下,这个问题尤为突出,IPVS 模式(相比 iptables 规则数随 Service 数量线性增长)在大规模集群中是更好的选择。


总结

Kubernetes 网络模型的精髓可以用一句话概括:CNI 插件决定了 Pod 如何通信,Service 决定了如何发现通信对象。理解这两者的关系,比记住任何具体的配置参数都重要。

在选择 CNI 插件时,关键是匹配实际的网络环境:节点之间大二层可达时,Calico BGP 路由模式提供最佳性能;节点跨越复杂网络边界时,Flannel 或 Calico 的 VXLAN 模式是更稳妥的选择。两者之间没有绝对的优劣,只有场景的匹配与否。

HostPort 绕过了 Kubernetes 的 Service 层,在测试场景外几乎没有用武之地。生产环境应该始终通过 Service 暴露应用,让 kube-proxy 接管负载均衡和故障转移。

最后,CNI 插件与内核版本的兼容性问题是最容易被忽视的故障来源。内核升级前在测试环境验证 CNI 兼容性,保持 CNI 插件版本与 Kubernetes 版本同步升级------这是避免"节点升级后网络静默崩溃"的关键。


如果这篇文章对你有帮助,欢迎点赞、收藏。如果还想了解更多 Kubernetes 运维相关的实战内容,可以关注作者的后续更新。

相关推荐
尘世壹俗人1 小时前
知识点12---k8s进阶操作方式yaml资源文件
docker·容器·kubernetes
尘世壹俗人1 小时前
知识点13---k8s存储持久化
容器·kubernetes·flask
图导物联1 小时前
智能场馆导览系统核心技术架构解析
架构·智能场馆导览系统·智慧场馆导览系统
techdashen1 小时前
用自家产品构建自家产品:Cloudflare Images 的工程架构解析
开发语言·架构·rust
我也不曾来过12 小时前
传输层协议UDP和TCP
linux·网络·udp
roman_日积跬步-终至千里2 小时前
【案例题-知识点(2)】架构风格上(五大类详解)
数据库·架构·系统架构
SamDeepThinking2 小时前
秒杀系统怎么区分真实用户和黄牛脚本?
java·后端·架构
奇妙之二进制2 小时前
zmq源码分析之消息可读通知机制
服务器·网络
wuxinyan1232 小时前
Java面试题50:Kubernetes 全栈知识体系之一
java·kubernetes·面试题