7AA7 Blog

容器网络入门:CNI、Service 与 Ingress 的流量路径

码同客 UID:88
2026-09-22 22:29
118 0 4

一台机器上跑多个容器,用 veth pair 加网桥就能互通。跨了机器,问题就变成另一个:Pod 的 IP 是 10.244.1.5,另一台宿主机上的 Pod 要访问它,怎么知道把包发到哪里。容器网络的设计,基本都围绕这个”怎么知道”展开。

要解决的问题:跨主机 Pod 互通

Kubernetes 对网络的要求可概括成三条:Pod 之间不经 NAT 直接互通;节点与 Pod 互通;Pod 自己看到的地址与其他 Pod 看到的一致。三条合起来的效果是 Pod 可以像独立主机一样被看待,应用不必感知 NAT。

单主机内实现这三条不难:每创建一个 Pod,创建一对 veth,一端放进 Pod 的 network namespace 命名成 eth0,另一端留在主机上挂到网桥(cni0 一类),配上地址和默认路由,同一网桥上的 Pod 就在二层互通了。

跨主机才是难点。底层网络只知道宿主机的地址,不知道宿主机上还挂着一段 Pod 网段。让”某个 Pod 网段在哪台机器上”这件事被底层获知,就是 CNI 插件的工作。配套的还有 IPAM:谁来分配地址、如何保证不冲突、节点扩缩容时网段如何伸缩。

CNI 插件:两类实现与它们的代价

CNI 本身是一个接口约定:容器创建时调用插件做 ADD,删除时做 DEL,插件负责往 netns 里插网卡、配地址、配路由、必要时配置策略。它并不规定跨主机连通用什么技术实现,所以实现方式分成了几类。

实现方式 原理 主要代价 适合环境
Overlay 封装 VXLAN/IPIP/Geneve 把原始报文封在宿主机之间传输 封装头开销、MTU 需下调、加解封装消耗 CPU、抓包看到外层地址 底层网络不可控、云上通用
路由直连 通过 BGP 或控制器下发路由,让底层知道 Pod 网段位置 要求底层网络接受这些路由,路由条目数有上限 自有机房、可配置路由的网络
Underlay Pod 直接接入底层网络(MACVLAN/IPVLAN 或云网卡) IP 管理与网卡配额耦合,配置复杂 对延迟和网络可观测性要求高的场景

Overlay 的吸引力是对底层几乎零要求:只要宿主机三层可达,封装就能把 Pod 网络”架”在上面。代价要具体到字节——VXLAN 在原始以太帧外加一层封装,头部约 50 字节量级,如果 Pod 网卡 MTU 仍是 1500,封装后的报文会超过物理网络的 1500 而被分片或丢弃。常见做法是把 Pod MTU 配到 1450 或更小,或开启巨帧。分片带来的性能下降往往是隐性的:小包正常、大包突然慢。

路由直连没有封装开销,抓包看到的就是原始 Pod IP,排查直观。它要求底层网络愿意接受这些路由:自有机房可以跑 BGP 与交换机对讲;云上通常要写 VPC 路由表,而条目数量有配额,节点规模一大先撞到配额而不是性能。

Underlay 把 Pod 直接放进底层网络,延迟与可观测性最好,代价是 IP 分配要与底层网络协同,Pod 密度受网卡或辅助 IP 配额限制。

Pod IP 与 ClusterIP:稳定与否的分界

Pod IP 是不稳定的。Pod 重建就换一个,Deployment 滚动更新时整批都换,节点故障重建更是如此。把 Pod IP 写进配置或代码,等于给自己埋一个定时失效的地址。

ClusterIP 是为解决这个而存在的稳定入口。它是一条转发规则里的目标地址——iptables 或 IPVS 规则匹配到这个地址后,做 DNAT 换成某个真实 Pod 的地址。它不配置在任何网卡上,因此 ping 它通常不通,这一点常被误判为网络故障。

对比项 Pod IP ClusterIP
稳定性 Pod 重建即变 Service 存续期间不变
存在形式 真实网卡地址 转发规则中的虚拟地址
可达性 可直接 ping/连接 仅在匹配协议端口时可连接
后端集合 一个 Pod 一组 Endpoint
适用场景 有状态服务的固定标识、调试 常规服务访问入口

Service 与后端 Pod 的映射关系保存在 Endpoint(以及规模较大时使用的 EndpointSlice)里。Service 没有后端时,连接的表现是”立刻拒绝”或超时,而不是解析失败——看到 Connection refused 先查 endpoints 是否就绪,比查网络更快接近答案。

还有一类是 headless Service(clusterIP: None):DNS 查询直接返回后端 Pod 的 IP 列表,由调用方自己选择连哪个。有状态服务(如数据库集群)常依赖它拿到确定的成员地址。

kube-proxy:转发规则怎么落进内核

kube-proxy 的职责是把 Service 的定义翻译成节点内核能执行的转发规则。它有多种实现形态。

iptables 模式为每个 Service 生成若干规则链,报文匹配到 ClusterIP 后按概率或顺序 DNAT 到某个后端。规则条数随 Service 数量与后端数量增长,且规则更新是全量重写:Service 频繁变更时,更新过程本身会成为负担,规模较大时表现明显。

IPVS 模式用内核哈希表做四层负载均衡。查找复杂度不随规则数线性增长,调度算法更多(轮询、最小连接、源地址哈希等),规模扩展性更好。它的行为依赖 conntrack 与 ipset 配合,某些边界场景(如 NodePort 与本地流量的处理)需要注意,表现与内核版本相关。

也有基于 eBPF 的实现,把转发放到更靠前的位置,减少 conntrack 表项与内核路径长度,代价是对内核版本有要求,且与既有 iptables 规则的共存关系需要理清。

无论哪种形态,这里的转发都发生在第四层:只看地址和端口,不解析 HTTP。因此”按域名路由””按路径路由””按 Header 灰度”都不可能在这一层实现。

Ingress 与 Gateway:为什么需要七层

需要按 Host 或 Path 分发、需要终止 TLS、需要按权重灰度,就必须有能读懂 HTTP 的组件。Ingress 就是这样一层资源定义,但真正干活的是 Ingress Controller——它需要单独部署,不同实现的注解、能力边界和行为差异相当大,换实现时注解往往不能通用。

Gateway API 是较新的模型,把职责按角色拆开:GatewayClass 由基础设施方提供,Gateway 由集群运维声明入口,HTTPRoute 由应用方声明路由规则。这种拆分让”谁能改什么”有了边界,也把灰度、超时、重试纳入标准字段。

七层入口带来的能力是明确的:TLS 终止、按域名与路径路由、权重分流、请求改写、限流与认证卸载。代价同样明确:多一跳转发、多一个要规划容量与高可用的组件,以及”入口控制器挂了全站不可达”的集中风险。

集群内 DNS 的作用

Service 名字能变成地址,靠的是集群 DNS。Pod 内 /etc/resolv.conf 指向集群 DNS 服务,查询 my-svc.my-ns.svc.cluster.local 得到 ClusterIP;headless Service 则返回后端 Pod 的 IP 列表。

一个高频坑在 ndots。resolv.conf 里的 search 域配合 ndots,会让不完全限定域名先按 search 域逐个尝试。若 ndots 设为 5,访问一个外部域名可能先产生数次带后缀的无效查询,DNS 请求量被放大数倍;用完全限定域名(结尾带点)可跳过这个过程。

DNS 出问题的表现常常不是”解析失败”,而是”连接超时”或”偶发慢”,容易被误判成网络抖动。DNS 服务本身也需要容量规划,短连接服务每次建连都解析,压力会被放大到 DNS 与 conntrack 两处。

排障:按段验证,而不是全线抓包

流量路径可以拆成五段,逐段验证比”全线抓包找异常”高效得多。

# 1) 容器内:DNS 配置与自身监听
cat /etc/resolv.conf
curl -v http://127.0.0.1:8080/healthz

# 2) 节点上:Pod 网段路由与转发规则
ip route | grep 10.244
iptables -t nat -L KUBE-SERVICES -n | head
ipvsadm -Ln | head                      # IPVS 模式

# 3) 跨节点:底层可达性与封装通道
ping -c 3 <对端节点IP>
tcpdump -ni eth0 udp port 8472          # VXLAN 端口以实际配置为准

# 4) Service:后端是否就绪
kubectl get endpoints my-svc -o wide

# 5) Ingress:控制器状态与 upstream 健康
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=100

第一段确认容器自身网络配置,第二段确认节点是否知道”目标 Pod 网段该从哪个接口出去”,第三段验证宿主机之间是否真的通——Overlay 场景要确认封装用的 UDP 端口没被安全组拦掉,这在云上很常见。第四段看 Service 有没有后端,第五段看七层控制器是否拿到了正确的 upstream。

段与段之间的边界就是责任边界:容器内归应用与 DNS 配置,节点内归 CNI 与 kube-proxy,节点间归底层网络与安全组,Service 归标签选择器与就绪探针,Ingress 归控制器配置。逐段排除后剩下的通常就是真正的原因。

🔁 4
2.1kViews
118Likes
0Comments
0Participants
11 minread time

Comments (0)

No comments yet — be the first.