跳到正文

Kubernetes 网络图:Pod、Service、CNI 与网络策略

逐层讲解 Kubernetes 网络:Pod IP 与 CNI、Overlay、Service、CoreDNS、NetworkPolicy 和 Gateway API。

· 创始人兼开发者

发布于

两个 worker 节点框架。用户访问 Gateway,Gateway 路由到 worker-1 上的 web Pod。web Pod 在 CoreDNS 中查询 api Service,然后连接它的 ClusterIP,指向 worker-2 上的两个 api Pod。NetworkPolicy 选中它们;VXLAN 隧道连接两个节点。

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 节点框架。用户访问 Gateway,Gateway 路由到 worker-1 上的 web Pod。web Pod 在 CoreDNS 中查询 api Service,然后连接它的 ClusterIP,指向 worker-2 上的两个 api Pod。NetworkPolicy 选中它们;VXLAN 隧道连接两个节点。

15 个组件 · 9 个连接

在 Archboard 中打开
图表内容
  • 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
一个请求穿过双节点集群:经 Gateway 进入,做一次 DNS 查询,连接被 kube-proxy 改写为 Pod IP 的 ClusterIP,经 VXLAN 跳到另一个节点,再由 NetworkPolicy 决定谁可以连接。每一层都有各自的故障方式。

这张图的读法如下:

  • 两个 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 中构建

  1. 用 Google 或 GitHub 登录并创建一个图表。免费的 Kubernetes YAML 转图表工具可以把现有清单变成第一版草稿。
  2. 从基础包添加两个框架节点,分别代表 worker-1 和 worker-2,并标上它们的 Pod 地址段。
  3. 从 Kubernetes 包添加四个带 IP 的 Pod 节点:worker-1 中的 web 和 coredns,worker-2 中的两个 api Pod。
  4. 在两个框架之间添加一个 Service(ClusterIP,端口 80 到 8080),并给来自 web 和通向两个 api Pod 的连线加标签。
  5. 添加一个选中 app=api 的 NetworkPolicy,用点线连到各 api Pod。
  6. 从网络包添加一个互联网节点,再添加一个带 HTTPS 443 监听器的 Gateway,并把它们串联到 web Pod。
  7. 画一条从 web 到 coredns 的虚线 DNS 连线,以及两个框架之间一条粗的双向 VXLAN 连线。
  8. 从基础包添加一个写有默认值的 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 迁移出去。

免费开始 — 1 个席位,无需信用卡。

相关文章

← 返回博客

一分钟打开画布

使用 Google 或 GitHub 登录。单席位免费,团队加入后再升级。