Kubernetes网络模型深度解析:CNI、Service与Ingress的协同工作原理
排查过 Pod 间网络不通的人都知道,Kubernetes 网络问题排查的难度不在于"不理解某一部分",而在于 CNI、Service、Ingress 三者各管一段,出问题时需要沿着数据包的路径从源头追到终点。本文把这条路径拆开来讲透。
一、Kubernetes 网络模型的基本约束
Kubernetes 的网络模型建立在三个基本前提之上,理解这三个前提是理解一切网络行为的基础:
- 每个 Pod 拥有独立 IP:Pod 内的容器共享同一个网络命名空间,通过 localhost 通信
- Pod 间可直接通信(无 NAT):同一集群中任意两个 Pod 可以直接通过对方的 IP 通信,无论它们是否在同一节点上
- 节点可与所有 Pod 直接通信:节点本身可以路由到集群内的任意 Pod IP
这三个前提将网络实现的复杂性下放到了 CNI 插件层。Kubernetes 自身只定义了接口和约束,不规定具体的数据面实现。
graph TB subgraph 控制面 API[API Server] --> SVC[Service Controller] SVC --> EP[Endpoints Controller] API --> ING[Ingress Controller] end subgraph 节点1 KPL1[Kube-Proxy] --> IPT1[iptables/IPVS规则] POD1A[Pod A<br/>10.244.1.10] --> ETH0_1A[eth0<br/>veth pair] POD1B[Pod B<br/>10.244.1.11] --> ETH0_1B[eth0<br/>veth pair] ETH0_1A --> BR1[cni0 网桥] ETH0_1B --> BR1 BR1 --> NODE1_ETH[节点网卡 eth0] end subgraph 节点2 KPL2[Kube-Proxy] --> IPT2[iptables/IPVS规则] POD2A[Pod C<br/>10.244.2.10] --> ETH0_2A[eth0<br/>veth pair] ETH0_2A --> BR2[cni0 网桥] BR2 --> NODE2_ETH[节点网卡 eth0] end subgraph Service层 SVC_OBJ[Service: my-svc<br/>ClusterIP: 10.96.0.1:8080] end subgraph 外部流量 LB[外部负载均衡] --> ING_CTRL[Ingress Controller Pod] ING_CTRL --> SVC_OBJ end NODE1_ETH <-->|VXLAN/BGP路由| NODE2_ETH API -.->|watch| KPL1 API -.->|watch| KPL2 style 控制面 fill:#e3f2fd style 节点1 fill:#fff3e0 style 节点2 fill:#fff3e0 style Service层 fill:#e8f5e9 style 外部流量 fill:#f3e5f5二、Pod 网络通信路径拆解
2.1 同节点 Pod 间通信
同节点 Pod 间通信是最简单的场景。以 Flannel VXLAN 为例:
Pod A (10.244.1.10) → Pod B (10.244.1.11) 1. Pod A 的路由表将 10.244.1.0/24 指向 cni0 网桥 2. 数据包经过 veth pair 到达宿主机的 cni0 网桥 3. cni0 是二层网桥,直接查询 CAM 表,将帧转发到 Pod B 的 veth 接口 4. 数据包进入 Pod B 的网络命名空间关键点在于 cni0 网桥充当了二层交换机,同节点流量无需经过任何隧道封装。
2.2 跨节点 Pod 间通信
跨节点通信依赖 CNI 插件的跨主机方案:
| CNI 插件 | 跨主机方案 | 封装开销 | 适用场景 |
|---|---|---|---|
| Flannel (VXLAN) | VXLAN 隧道封装 | ~50 bytes 额外头部 | 通用场景 |
| Flannel (host-gw) | 主机路由表 | 无额外开销 | 二层互通的集群 |
| Calico (IPIP) | IP-in-IP 隧道 | ~20 bytes | 跨子网部署 |
| Calico (BGP) | BGP 路由协议 | 无额外开销 | 同子网/支持 BGP 的网络 |
| Cilium (eBPF) | eBPF 直通 | 极低 | 高性能场景 |
以 Flannel VXLAN 为例,跨节点通信路径:
Node1 Pod A (10.244.1.10) → Node2 Pod C (10.244.2.10) 1. Pod A 查路由表:10.244.2.0/24 通过 flannel.1 接口 2. flannel.1 是 VXLAN 隧道端点(VTEP),它需要知道: - 目标 VTEP 的 MAC 地址(通过 ARP 或 flannel 守护进程学习) - 目标 VTEP 所在节点的 IP(通过 etcd 存储的节点子网映射) 3. 原始以太网帧被封装在 VXLAN UDP 包中 4. Node1 物理网卡通过 underlay 网络将 VXLAN 包发送到 Node2 5. Node2 flannel.1 解封装,取出原始帧,通过 cni0 转发给 Pod C2.3 网络策略与流量隔离
NetworkPolicy 提供了 Pod 级别的防火墙规则。关键是理解它是通过 CNI 插件实现的(Kubernetes 只是定义了 API 对象):
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-network-policy namespace: production spec: podSelector: matchLabels: app: payment-service # 目标:支付服务 Pod policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: api-gateway # 仅允许 API 网关访问 - namespaceSelector: matchLabels: name: production # 仅允许同命名空间的流量 ports: - protocol: TCP port: 8080 egress: - to: - podSelector: matchLabels: app: database-proxy # 仅允许访问数据库代理 ports: - protocol: TCP port: 5432 - to: - namespaceSelector: {} podSelector: matchLabels: app: dns-server # 允许 DNS 查询 ports: - protocol: UDP port: 53NetworkPolicy 的"默认拒绝"策略需要在每个命名空间显式创建:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: production spec: podSelector: {} # 选中所有 Pod policyTypes: - Ingress - Egress # 不定义任何 ingress/egress 规则 = 拒绝所有三、Service 的 iptables 与 IPVS 模式对比
Service 是 Kubernetes 中最容易被"用对但不理解"的对象。它的底层实现有两种模式:
3.1 iptables 模式
graph LR subgraph 请求流转 CLIENT[客户端 Pod] -->|dip: 10.96.0.1:8080| PREROUTING[iptables PREROUTING] PREROUTING --> KUBE_SVC[KUBE-SVC Chain] KUBE_SVC -->|random概率选择| SEP1[KUBE-SEP-1<br/>DNAT → 10.244.1.10:8080] KUBE_SVC -->|random概率选择| SEP2[KUBE-SEP-2<br/>DNAT → 10.244.2.10:8080] KUBE_SVC -->|random概率选择| SEP3[KUBE-SEP-3<br/>DNAT → 10.244.1.11:8080] SEP1 --> BACKEND1[Pod Backend A] SEP2 --> BACKEND2[Pod Backend B] SEP3 --> BACKEND3[Pod Backend C] endiptables 模式的核心特征是:kube-proxy 将每个 Service 和 Endpoint 翻译为一条条 iptables 规则。其行为特征是:
- 随机负载均衡:通过
statistic --mode random --probability实现 - 规则数量膨胀:每个 Endpoint 对应一条 KUBE-SEP 链,大规模集群中 iptables 规则可能达到数万条
- 规则匹配是 O(n) 的:数据包需要逐条匹配规则,规则越多,延迟越大
3.2 IPVS 模式
IPVS(IP Virtual Server)是 Linux 内核的传输层负载均衡器。kube-proxy 在 IPVS 模式下,将 Service 直接映射为 IPVS 虚拟服务器:
# 查看 IPVS 规则 ipvsadm -Ln # 输出示例: # TCP 10.96.0.1:8080 rr # -> 10.244.1.10:8080 Masq 1 0 0 # -> 10.244.2.10:8080 Masq 1 0 0 # -> 10.244.1.11:8080 Masq 1 0 0IPVS 相较于 iptables 的关键优势:
| 维度 | iptables | IPVS |
|---|---|---|
| 负载均衡算法 | 随机(round-robin 近似) | rr/wrr/lc/wlc/lblc/dh/sh 等 8 种 |
| 规则匹配复杂度 | O(n) 逐条匹配 | O(1) 哈希查找 |
| 大规模 Service 性能 | 5000+ Service 时明显下降 | 10000+ Service 性能稳定 |
| 连接追踪 | 依赖 conntrack | 自身维护连接表 |
| 健康检查 | 依赖 Endpoint 更新 | 支持主動健康检查 |
生产环境建议:当集群 Service 数超过 1000 时,优先使用 IPVS 模式。配置方式:
# kube-proxy ConfigMap apiVersion: v1 kind: ConfigMap metadata: name: kube-proxy namespace: kube-system data: config.conf: | apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs ipvs: scheduler: "wrr" # 加权轮询 strictARP: true tcpTimeout: 0 tcpFinTimeout: 0 udpTimeout: 03.3 ClusterIP 不存在的数据面
一个常见的误区是认为 ClusterIP 绑定在某个网络接口上。实际上,ClusterIP 仅存在于 iptables/IPVS 规则中——它就是一条 DNAT 规则的匹配条件。当你 ping 一个 Service 的 ClusterIP 时,ping 包会被 iptables 规则匹配并做 DNAT,但 ICMP 协议不在规则匹配范围内,所以 ping 不通是正常的。
四、Ingress Controller 选型对比
Ingress Controller 承担着"从外部世界到集群内部服务"的最后一跳。主流选型有两类:
/** * Ingress Controller 健康检查端点 * 用于外部负载均衡器的后端健康探测 */ @RestController public class IngressHealthController { private final HealthChecker healthChecker; private final MeterRegistry meterRegistry; public IngressHealthController(HealthChecker healthChecker, MeterRegistry meterRegistry) { this.healthChecker = healthChecker; this.meterRegistry = meterRegistry; } /** * 深度健康检查:验证所有关键依赖是否可用 * 用于外部 LB 的健康探测,返回 200/503 */ @GetMapping("/healthz") public ResponseEntity<Map<String, Object>> healthCheck() { Map<String, HealthChecker.ComponentStatus> statuses = healthChecker.checkAll(List.of("database", "redis", "message-queue")); boolean allHealthy = statuses.values().stream() .allMatch(s -> s == HealthChecker.ComponentStatus.HEALTHY); // 记录健康检查指标 statuses.forEach((component, status) -> { meterRegistry.counter("health.check", "component", component, "status", status.name().toLowerCase() ).increment(); }); Map<String, Object> body = new LinkedHashMap<>(); body.put("status", allHealthy ? "UP" : "DOWN"); body.put("timestamp", Instant.now().toString()); body.put("components", statuses); return allHealthy ? ResponseEntity.ok(body) : ResponseEntity.status(503).body(body); } }选型对比矩阵:
| 特性 | Nginx Ingress | Traefik | Istio Gateway | Kong |
|---|---|---|---|---|
| 成熟度 | ★★★★★ | ★★★★☆ | ★★★★☆ | ★★★★☆ |
| 性能(QPS) | 最高 | 较高 | 中等(有 Sidecar 开销) | 较高 |
| 动态配置 | 需 reload | 原生支持 | 原生支持 | 原生支持 |
| 插件生态 | Lua 脚本 | 中间件链 | EnvoyFilter | 丰富插件 |
| 服务网格集成 | 需额外配置 | 可集成 | 原生一体 | 需额外配置 |
| 学习曲线 | 低 | 中 | 高 | 中 |
| 适用规模 | 中小型 | 中小型 | 大型/微服务 | 中型/API管理 |
选型建议:如果你的主要需求是"把外部 HTTP 流量引进来做路由分发",Nginx Ingress 是最稳妥的选择。如果需要 API 网关级别的能力(限流、认证、日志),Kong 更合适。如果已经或计划部署服务网格,Istio Gateway 是自然的选择。
五、总结
Kubernetes 网络模型的核心设计思路是"关注点分离":CNI 管 Pod 网络(L2-L3),Service 管服务发现(L3-L4),Ingress 管外部流量(L7)。三层之间通过标准化的接口解耦,每层可以独立选型。
在实际运维中,有三个容易踩的坑值得特别注意:
一是不要把 ClusterIP 当成真实 IP。它只是 iptables/IPVS 规则中的匹配条件,ping 不通不是故障。
二是大规模集群优先考虑 IPVS。iptables 的 O(n) 规则匹配在服务数量超过 1000 时性能退化明显,IPVS 通过内核级的哈希表将查找降到 O(1)。
三是NetworkPolicy 需要 CNI 支持。不是所有 CNI 都实现了 NetworkPolicy(Flannel 默认不支持,需配合 Calico 做 policy-only 模式),选型时务必确认。
排查网络问题时,建议按照"Pod 内 → 同节点 Pod → 跨节点 Pod → Service → 外部访问"的顺序逐层验证。每一层都有对应的调试命令:ip netns exec进入 Pod 网络命名空间、conntrack -L查看连接追踪表、iptables-save | grep <service-name>查看 Service 规则、tcpdump -i flannel.1抓取跨节点封装包。