shop 命名空间中的一个 Pod 调用 http://api,请求超时。Pod 在运行,Service 存在,它的 EndpointSlice 列出了两个就绪的后端。故障可能出在 DNS、kube-proxy 写在节点上的规则、节点之间的 Overlay,或者上周有人应用的 NetworkPolicy。每一项都是独立的一层,由独立的组件负责。
本文借助一张图逐层讲解:网络模型、Pod 到 Pod 的流量、CNI 插件、Service、DNS、NetworkPolicy,以及进出集群的流量。
Kubernetes 网络模型
Kubernetes 网络模型有三条规则。每个 Pod 都有自己的集群范围 IP 地址,由其中的容器共享。每个 Pod 都能访问任何节点上的其他所有 Pod,无需代理或 NAT。而 kubelet 这类节点代理能访问其所在节点上的所有 Pod。
Kubernetes 定义 API,实际工作由你安装的组件完成:
| 层 | 提供什么 | 由谁实现 |
|---|---|---|
| Pod 网络 | 每个 Pod 一个 IP,Pod 之间可达 | CNI 插件 |
| Service | 一组 Pod 的稳定虚拟 IP 和名称 | kube-proxy 或 eBPF 替代方案 |
| DNS | 如 api.shop.svc.cluster.local 的名称 |
CoreDNS |
| NetworkPolicy | Pod 流量的允许规则 | CNI 插件 |
| Ingress、Gateway API | 来自集群外部的路由 | 你自己安装的控制器 |
这种分工能解释很多意外情况,比如 API 服务器接受了某个 NetworkPolicy,而你的插件却悄悄忽略了它。
同一节点上的 Pod 到 Pod
容器运行时启动 Pod 时,会用 ADD 调用 CNI 插件。大多数插件会创建一对 veth,也就是一根虚拟网线,一端是 Pod 内的 eth0,另一端在主机上,并分配 IP。Flannel 把主机一端接到 Linux 网桥 cni0 上;Calico 不用网桥:每个 Pod 得到一条 /32 路由,主机充当它的路由器。无论哪种方式,同一节点内的流量都不需要封装,也不需要 NAT。
跨节点的 Pod 到 Pod:Overlay 或原生路由
每个节点通常拥有 Pod 地址段的一小块。kube-controller-manager 的 --node-cidr-mask-size 默认为 24,此时 10.244.0.0/16 的地址段会给 256 个节点各分一个 /24,也就是 256 个地址,对应 kubelet 默认的 110 个 Pod。跨节点时,插件有两种选择:
| Overlay(VXLAN、Geneve、IP-in-IP) | 原生路由(BGP、云路由、VPC IP) | |
|---|---|---|
| 节点之间的网络看到的是 | 只有节点 IP | Pod IP |
| 前提条件 | 节点之间 IP 可达 | 能路由 Pod 地址段的网络,或给 Pod 用的 VPC 地址 |
| 每个数据包的开销 | IPv4 VXLAN 为 50 字节,IP-in-IP 为 20 字节 | 无 |
| 示例 | Flannel 和 Cilium 的默认模式,Calico VXLAN | 使用 BGP 的 Calico、AWS VPC CNI、Azure CNI |
这部分开销是 MTU 问题:根据 Calico 的 MTU 指南,在 1,500 字节的网络上,IPv4 VXLAN 之后的 Pod 需要 1,450 的 MTU。设错的话,大数据包会消失,表现为随机卡住。
CNI 插件对比
| 插件 | 跨节点流量 | NetworkPolicy | 说明 |
|---|---|---|---|
| Flannel | 默认 VXLAN(UDP 8472);在共享的二层网络上用 host-gw |
Flannel 本身不执行;需添加 kube-network-policies 或 Calico | 小巧简单 |
| Calico | BGP,或 VXLAN(UDP 4789)或 IP-in-IP Overlay | 完整支持,另有 Calico 的命名空间级和全局策略 | iptables、nftables、eBPF、VPP 和 Windows 数据平面 |
| Cilium | 默认 VXLAN(UDP 8472),也可用 Geneve 或原生路由 | 完整支持,另有七层和基于 DNS 的规则 | eBPF 数据路径 |
| AWS VPC CNI | 无 Overlay;Pod 获得 VPC 地址 | 自 v1.14 起,需设置 enableNetworkPolicy |
每个节点的 Pod 数受实例类型限制 |
AWS 的限制很早就会碰到:默认上限是 ENI 数 × (每个 ENI 的 IP 数 − 1) + 2,所以一台有 3 个接口、每个接口 10 个地址的 m5.large 只能运行 3 × 9 + 2 = 29 个 Pod。前缀委派可以提高这个上限。
Service:在不断变化的 Pod 前面放一个稳定 IP
每当 Pod 被替换,它的 IP 就会变。Service 给一组 Pod 一个稳定的虚拟 IP,也就是 ClusterIP,以及一个 DNS 名称;EndpointSlice 跟踪它背后的 Pod。
| 类型 | 你得到什么 |
|---|---|
| ClusterIP(默认) | 集群内的一个虚拟 IP |
| NodePort | 另外在每个节点上开一个端口,默认范围 30000–32767 |
| LoadBalancer | 另外得到云或附加组件提供的负载均衡器 |
| ExternalName | 一个 DNS 别名(CNAME),不做代理 |
Headless(clusterIP: None) |
没有虚拟 IP;DNS 返回 Pod IP |
没有哪台机器拥有 ClusterIP。在每个节点上,kube-proxy 会编程内核,使发往 10.96.48.20:80 的数据包在离开客户端所在节点之前,被改写(目标 NAT)为 10.244.2.22:8080 这样的 Pod 地址。Service 是一个逻辑步骤,不是服务器。规则取决于代理模式:
| 模式 | 在 Kubernetes 1.37 中的状态 | 工作方式 |
|---|---|---|
| iptables | Linux 上的默认模式 | 每个 Service 和 endpoint 一组规则;随机选后端 |
| nftables | 自 1.33 起 GA;计划成为未来的默认模式 | 大规模下更新更快;需要内核 5.13 或更新版本 |
| ipvs | 自 1.35 起弃用;1.40 默认关闭,1.43 移除 | 内核 IPVS 表,更多负载均衡算法 |
| eBPF(Cilium、Calico) | 取代 kube-proxy | 在 eBPF 程序中做负载均衡 |
请显式设置模式,避免升级时被改掉,并规划从 IPVS 迁移。在 nftables 模式下,NodePort 默认只监听节点的主地址。
用 CoreDNS 做 DNS
CoreDNS 是默认的集群 DNS,kubeadm 把它运行在一个仍然叫 kube-dns 的 Service 后面。每个 Service 都有一个名称 <service>.<namespace>.svc.cluster.local,它会解析为 ClusterIP,Headless Service 则解析为各 Pod 的 IP。kubelet 会写入每个 Pod 的 /etc/resolv.conf;在 kubeadm 集群的 shop 中,它通常是这样:
nameserver 10.96.0.10
search shop.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
问题出在 ndots:5:点数少于五个的名称,比如 api.stripe.com,会先带上每个搜索域去尝试,所以一次外部查询可能多出三次失败的查询。可以给高频名称加一个结尾的点,在 Pod 的 dnsConfig 中调低 ndots,或者运行 NodeLocal DNSCache。之后外部名称就走 DNS 的工作原理中展示的路径。
NetworkPolicy:从默认允许到最小权限
默认情况下,每个 Pod 接受来自任何地方的流量。一旦有任何 NetworkPolicy 为 ingress(或 egress)选中了某个 Pod,就只有某条策略允许的流量能通过;回包会被自动允许。策略只会增加允许规则,所以顺序无关紧要。每个命名空间都先从默认拒绝开始:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
然后再放开你需要的部分,这里是从 web 到 api 的 TCP 8080:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-web
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
有四个细节常让人踩坑:
- 端口是 Pod 的端口,即 8080,而不是 Service 的端口 80。
- 默认拒绝也会隔离 web Pod,所以 Gateway 需要自己的规则才能访问它们。
- 一个短横线会改变含义。 同一个
from条目里的namespaceSelector和podSelector必须同时匹配;写成两个条目,则满足其一即可。 - 默认拒绝 egress 会挡住 DNS,所以先放行到
kube-dns的 53 端口。
NetworkPolicy 没有拒绝规则,没有集群范围的作用域,也不涉及 TLS;工作负载之间的双向 TLS 请参阅服务网格架构图。
进出流量:Ingress、Gateway API 和 egress
Ingress 稳定但已冻结,Kubernetes 项目建议改用 Gateway API。社区的 Ingress NGINX 控制器已于 2026 年 3 月退役,不再获得安全修复,但现有部署仍在悄悄运行。
Gateway API 按角色拆分工作:平台负责人运行带有监听器和证书的 Gateway,应用团队从自己的命名空间挂接 HTTPRoute。它在 2023 年 10 月 GA,2026 年的版本又让 TLSRoute、TCPRoute 和 UDPRoute 成为标准。Kubernetes Ingress 图逐跳跟随这条路径。
egress 是被遗忘的方向。离开集群的流量通常会被转换为节点的 IP,AWS VPC CNI 对 VPC 之外的目的地就是这样做的。可以用 egress 规则来限制它,这类规则只能把外部目的地看作 IP 段;也可以用 Cilium 的 toFQDNs 这类支持 DNS 的规则,或者在合作方需要固定源 IP 时使用 egress gateway。
Kubernetes 网络图
图表内容
- worker-1 · pods 10.244.1.0/24
- worker-2 · pods 10.244.2.0/24
- Users
- public-gw
- web · 10.244.1.12
- coredns · 10.244.1.5
- api · 10.244.2.21
- api · 10.244.2.22
- api · 10.96.48.20
- api-allow-web
- HTTPS 443
- HTTPRoute
- DNS: api.shop.svc.cluster.local via kube-dns 10.96.0.10
- TCP 10.96.48.20:80
- DNAT 10.244.2.21:8080
- DNAT 10.244.2.22:8080
- VXLAN overlay over UDP · node IP to node IP
这张图的读法如下:
- 两个 worker 节点,各自标有 /24 的 Pod 地址段。
- 蓝色是南北向: 用户通过 HTTPS 443 访问
public-gw,HTTPRoute 把他们送到 web Pod。 - 琥珀色是 DNS: web 通过 10.96.0.10 的
kube-dns向 CoreDNS 查询api.shop.svc.cluster.local,得到 10.96.48.20。 - 青色是 Service 路径: web 连接 10.96.48.20:80,worker-1 上的 kube-proxy 规则把它改写为 8080 端口上的一个 api Pod。
- 灰色是 Overlay: 数据包在节点 IP 之间,封装在 VXLAN 里跨到 worker-2。
- 红色是策略:
api-allow-web只允许app=web的 Pod 访问 api Pod。 - Markdown 块列出了这些数字背后的默认值。
把 Kubernetes 网络图画清楚
- 给节点标上 Pod 地址段,给 Pod 标上 IP,这些是你在日志里要搜的细节。
- 把 Service 画成一个逻辑步骤,并注明改写发生在哪里。
- 在连线上标注地址、端口和协议,经过 Service 之后改用 Pod 端口。
- 南北向和东西向用不同颜色,Overlay 只在节点之间画一次。
- 把每条策略连到它选中的 Pod 上,并标出允许的来源。
- 省略 kubelet、API 服务器和多余的副本;控制平面由 Kubernetes 架构图负责说明。
- 常见错误: 把 kube-proxy 画在数据包路径上(它只写规则,转发的是内核)、漏掉 DNS,以及把策略画在会忽略它的插件上。
在 Archboard 中构建
- 用 Google 或 GitHub 登录并创建一个图表。免费的 Kubernetes YAML 转图表工具可以把现有清单变成第一版草稿。
- 从基础包添加两个框架节点,分别代表 worker-1 和 worker-2,并标上它们的 Pod 地址段。
- 从 Kubernetes 包添加四个带 IP 的 Pod 节点:worker-1 中的 web 和 coredns,worker-2 中的两个 api Pod。
- 在两个框架之间添加一个 Service(ClusterIP,端口 80 到 8080),并给来自 web 和通向两个 api Pod 的连线加标签。
- 添加一个选中
app=api的 NetworkPolicy,用点线连到各 api Pod。 - 从网络包添加一个互联网节点,再添加一个带 HTTPS 443 监听器的 Gateway,并把它们串联到 web Pod。
- 画一条从 web 到 coredns 的虚线 DNS 连线,以及两个框架之间一条粗的双向 VXLAN 连线。
- 从基础包添加一个写有默认值的 Markdown 块,以及简短的文本注释。
Kubernetes 用例页面列出了更多对象。这个示例共用 11 个图表节点,在 Free 套餐的限制之内。
常见问题
Kubernetes 中的 Pod 如何跨节点通信? 通过 CNI 插件:Overlay 在节点 IP 之间封装 Pod 的数据包,或者原生路由直接承载 Pod 地址。无论哪种方式,Pod 看到的都是彼此真实的 IP。
Pod IP 和 ClusterIP 有什么区别? Pod IP 属于单个 Pod,Pod 被替换时会变。ClusterIP 是 Service 的稳定虚拟 IP,由 kube-proxy 或 eBPF 替代方案改写为当前的 Pod IP。
应该用哪种 kube-proxy 模式? 在 Kubernetes 1.37 中,iptables 是默认模式,nftables 自 1.33 起 GA 并将成为默认,IPVS 已弃用。内核为 5.13 或更新版本时,nftables 是更好的长期选择。
NetworkPolicy 能在每种 CNI 插件上生效吗? 不能。API 在任何地方都会接受它,但只有实现了 NetworkPolicy 的插件才会执行:Calico、Cilium,以及开启了网络策略的 AWS VPC CNI 会执行;单独的 Flannel 不会。
应该用 Ingress 还是 Gateway API? 新项目用 Gateway API:Kubernetes 推荐它,而 Ingress 已冻结。现有的 Ingress 资源会继续工作,但要规划从不再获得安全修复的 Ingress NGINX 迁移出去。