跳到主要内容

网络体系

前面几篇讲过,Service 能给一组 Pod 提供固定入口。但「Pod 拿到 IP 之后,跨节点的两个 Pod 究竟是怎么通起来的」一直没展开。这篇把 K8s 的网络体系讲透:网络模型定下的规矩、CNI 插件怎么实现这张网、kube-proxy 怎么做 Service 转发,最后给一套网络排障的标准流程。

Kubernetes 网络模型​

Kubernetes 不规定网络的具体实现,但定下了三条必须满足的硬性要求:

  1. 每个 Pod 都有独立的 IP 地址:Pod 内的所有容器共享这一个 IP 和网络命名空间,对外就像一台独立的主机。
  2. Pod 与 Pod 之间可以直接通信,无需 NAT:不管两个 Pod 在不在同一台节点上,都能用对方的 IP 直接访问。
  3. 节点与 Pod 之间也可以直接通信,无需 NAT:宿主机可以直接访问本机和其他节点上 Pod 的 IP。

三条合起来就一个意思:整个集群在效果上是一张扁平的大网,每个 Pod 都像直接插在这张网里的真实机器,IP 全网唯一、全网可达。

这套要求和 Docker 单机网络的差别非常大:

对比项Docker 单机(默认 bridge)Kubernetes 集群网络
IP 分配宿主机私有网段(默认 172.17.0.0/16),只在本机有意义全集群统一规划的 Pod 网段,每个 Pod IP 全局唯一
跨主机通信默认不通,要靠 -p 端口映射 + 宿主机 IP天然互通,直接用 Pod IP 访问
NAT容器出宿主机全靠 NAT(端口映射、masquerade)集群内 Pod 通信不做 NAT
端口管理宿主端口要自己分配,容易冲突Pod 各有独立 IP,端口随便用
为什么敢定这么「奢侈」的规矩

Docker 靠端口映射把多个容器挤在一个宿主 IP 上,规模一大,「哪台机器的哪个端口是什么服务」根本没法管。K8s 干脆规定每个 Pod 一个全网可达的 IP,从根上消灭端口冲突和服务发现问题——代价是把「怎么实现这张大网」这个难题留给了网络插件,也就是下面要讲的 CNI。

CNI​

CNI(Container Network Interface)是一套接口规范,定义了容器运行时和网络插件之间该怎么对话。K8s 自己不实现网络,只要求插件按规范提供几个操作,最核心的两个是:

  • ADD:创建 Pod 时调用,负责给 Pod 配网——分配 IP、创建 veth 对、接上网桥、写好路由。
  • DEL:删除 Pod 时调用,把 ADD 分配的资源全部回收。

(规范里还有 CHECK、VERSION 等操作,日常打交道最多的还是 ADD 和 DEL。)

节点上和 CNI 相关的两个固定位置:

位置存放内容
/etc/cni/net.d/CNI 配置文件(.conf / .conflist),按文件名排序取第一个生效
/opt/cni/bin/CNI 插件的二进制文件(bridge、host-local、loopback 等)

一份典型的配置文件长这样(以 bridge 插件为例):

{
"cniVersion": "1.0.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16"
}
}

创建 Pod 时,kubelet 与 CNI 的调用链是这样的:

CNI 调用链

记住这层关系

kubelet 不直接配网,它只是「在创建和删除 Pod 时调用 CNI 插件」。所以排查 Pod 拿不到 IP 的问题,先去节点上看 /etc/cni/net.d/ 有没有配置、CNI 插件的 Pod(DaemonSet)健不健康。

Flannel​

Flannel 是最老牌、最简单的 CNI 插件,定位非常明确:把网络打通,别的都不管。

它的默认后端是 VXLAN 覆盖网络,原理一句话:把 Pod 发出的报文整个封装进 UDP 包(4789 端口),借宿主机的三层网络运到对端节点,再拆封还原——只要节点之间三层可达,Pod 网络就能通,对底层网络几乎零要求。

flannel 跨节点通信

部署就是一条命令:

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

代价也很直白:

  • 功能少:不支持 NetworkPolicy,没有任何流量治理能力。
  • 有开销:VXLAN 封包拆包要消耗 CPU,报文还要多出 50 字节头部。
host-gw 后端

Flannel 还有个 host-gw 后端:不封包,直接在宿主机上写「目标 Pod 网段 → 对端节点 IP」的路由。性能接近 Calico,但要求所有节点在同一个二层网络里,云上基本用不了,了解一下即可。

Calico​

Calico 的思路和 Flannel 完全不同:不封包,把集群当成纯三层网络来路由。

每个节点上的 Calico 组件(Felix + BIRD)通过 BGP 协议互相通告「我这台节点上有哪个 Pod 网段」,于是每台宿主机都有完整的 Pod 路由表,Pod 报文不经过任何封装,直接按路由转发,性能接近物理网络。

Calico 的另外两个招牌能力:

  • 完整支持 NetworkPolicy:K8s 的 NetworkPolicy 资源本身只是一份声明,需要 CNI 插件来执行,Calico 是最早、最常用的实现者,还提供自己扩展的 GlobalNetworkPolicy。
  • IPIP / VXLAN 模式可选:BGP 要求节点间三层可达,跨网段(比如云上跨 VPC)时可以打开 IPIP 或 VXLAN 封包模式,还能按子网混合使用,非常灵活。
BGP 听着吓人,用起来无感

Calico 的 BGP 是节点之间自建的私有对等关系,不需要机房交换机支持 BGP,也不用你手工维护邻居配置——装好它自己就跑起来了。

Cilium​

Cilium 是新一代 CNI 的代表,核心卖点是用 eBPF 做数据面:不再依赖 iptables 规则链,而是在内核里挂载 eBPF 程序,转发、负载均衡、策略判断全部在内核中一次完成。

三大特征:

  • eBPF 数据面:绕过 iptables 长链,Service 数量再大也不掉性能(eBPF 的细节后面专门有一节)。
  • 可替代 kube-proxy:Service 的负载均衡也由 eBPF 实现,集群里可以不装 kube-proxy。
  • Hubble 可观测:基于 eBPF 事件天生就能看到每一条流量,提供服务依赖图、流量日志、丢包可视化,开箱即用。

网络策略方面,Cilium 在支持 K8s NetworkPolicy 之外还能做到 L7 层(比如「只允许 GET /api 这个路径」),这是 iptables 系方案做不到的。

三种主流 CNI 的对比:

对比项FlannelCalicoCilium
数据面VXLAN 覆盖网络(另有 host-gw)BGP 纯三层路由(可选 IPIP/VXLAN 封包)eBPF 内核程序
部署难度最简单,一条命令中等中等偏上
NetworkPolicy不支持支持支持,可扩展到 L7
性能一般(封包有开销)好(不封包时接近物理网络)好,大规模下优势明显
可观测性无一般Hubble,开箱即用
能否替代 kube-proxy否否可以
适合场景入门、小规模集群生产主流、需要网络策略大规模、重性能与可观测
怎么选

学习或小规模集群用 Flannel 最省事;要用 NetworkPolicy 就选 Calico,它是生产环境的老牌稳妥方案;集群规模大、看重性能和可观测性,或者干脆想去掉 kube-proxy,选 Cilium——这也是目前社区新建集群的主流风向。

kube-proxy​

先划清边界:kube-proxy 只负责 Service 转发,不管 Pod 网络。

它的职责是:watch API Server 里 Service 和 Endpoint 的变化,然后在每个节点上维护转发规则,让「访问 ClusterIP / NodePort」的流量能被送到后端 Pod 上。至于 Pod 和 Pod 之间通不通,那是 CNI 的事,和 kube-proxy 无关。

kube-proxy 的转发实现经历了三代演进:

模式工作方式优点缺点现状
userspacekube-proxy 自己监听端口,在用户态做反向代理实现简单流量在内核态和用户态之间来回拷贝,性能差早已淘汰
iptableskube-proxy 只写 iptables DNAT 规则,转发在内核态完成无用户态拷贝,够快规则随 Service 数量线性增长,大规模变慢目前默认
ipvs把转发交给内核 IPVS 模块哈希表查找 O(1),调度算法丰富节点需加载 ipvs 内核模块大规模推荐
排障别找错对象

遇到「Pod 互 ping 不通」就去翻 kube-proxy 日志,是新手常走的弯路——Pod 直连流量根本不经过 kube-proxy。只有访问 Service IP 不通时,才轮到 kube-proxy 登场。

iptables​

iptables 模式下,kube-proxy 为每个 Service 生成一组规则链,一次 ClusterIP 访问的 DNAT 流程是:

  1. 报文先进入 KUBE-SERVICES 链,按「目的 IP + 端口」匹配到对应 Service 的 KUBE-SVC-xxx 链。
  2. KUBE-SVC-xxx 链里有若干条 KUBE-SEP-yyy 规则(每个后端 Pod 一条),用 statistic 模块按概率随机挑一条跳转。
  3. KUBE-SEP-yyy 链执行 DNAT,把目的地址改写成后端 Pod 的 IP 和端口。

iptables 模式 Service 转发链

在节点上可以直接查看这些规则:

# 查看所有 Service 的入口规则
iptables -t nat -L KUBE-SERVICES

# 查看某个 Service 的后端分配(链名从上面的输出里找)
iptables -t nat -L KUBE-SVC-JD5MR3NA4I4DYORP
Chain KUBE-SVC-JD5MR3NA4I4DYORP (1 references)
target prot opt source destination
KUBE-SEP-AAAA all -- anywhere anywhere statistic mode random probability 0.33333333349
KUBE-SEP-BBBB all -- anywhere anywhere statistic mode random probability 0.50000000000
KUBE-SEP-CCCC all -- anywhere anywhere /* default/web */

两个要点:

  • 这里的负载均衡是随机 + 概率,不是轮询;规则自上而下顺序匹配,所以概率依次写成 1/3、1/2、1(兜底),数学上才是均匀分配。
  • 规则数量约等于「Service 数 × 后端数」,每个报文都要沿链顺序匹配。Service 上到几千条后,转发变慢、规则更新也慢——这正是 IPVS 模式要解决的问题。
别手动改 KUBE 链

这些链由 kube-proxy 全权维护,手动插入或修改的规则很快会被它刷掉。想调整行为,应该改 kube-proxy 的配置,而不是改 iptables。

IPVS​

IPVS(IP Virtual Server)是内核自带的四层负载均衡模块(也就是 LVS 的内核部分)。kube-proxy 切到 IPVS 模式后,不再铺一串串 iptables 链,而是把「Service → 后端」的映射写进 IPVS 的哈希表——查找复杂度 O(1),和 Service 数量彻底脱钩。

它还提供多种真实的调度算法(通过 --ipvs-scheduler 指定):

算法含义适用场景
rr轮询(默认)大多数场景
lc最少连接,新请求发给连接数最少的后端长连接、后端性能不均
sh源地址哈希,同一客户端固定到同一后端需要会话保持
dh / sed / nq目的哈希 / 最短期望延迟 / 不排队特殊场景,了解即可

切换方法(kubeadm 部署的集群):

kubectl edit configmap kube-proxy -n kube-system

在 data.config.conf 里把模式改掉:

data:
config.conf: |
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs" # 默认是 iptables
ipvs:
scheduler: "rr"

重启生效并验证:

kubectl rollout restart daemonset kube-proxy -n kube-system
ipvsadm -Ln # 能看到 Service IP 与后端的映射,即切换成功

前提是节点已加载 ip_vs、ip_vs_rr 等内核模块,并安装 ipvsadm 方便观察(多数现代发行版开箱即用,也可以 modprobe ip_vs 手动加载)。

什么时候值得切

Service 数量到几百上千、或者感到 Service 访问延迟随规模上涨时,就该切 IPVS。小集群用默认的 iptables 模式完全够,不必为新而新。

eBPF​

eBPF 是 Linux 内核内置的一套可编程沙箱:不用改内核源码、不用加载内核模块,就能把一段经过验证器安全检查的小程序,挂到内核的各种事件点(网卡收发包、系统调用、函数入口……)上执行。

它在云原生圈火起来,主要靠两点:

  • 可编程内核:数据面逻辑直接写在内核事件点上,既有内核级的性能,又有接近用户态的开发效率;内核版本够新就能用,不用换内核。
  • 绕过 iptables 长链:转发、负载均衡、策略判断全部用 eBPF map(内核里的哈希表)完成,O(1) 查找,没有链式遍历——Service 规模再大性能也稳。

顺带的好处是天生可观测:程序就挂在事件现场,流量、丢包、延迟顺手就能统计导出。

在 Cilium 里,eBPF 几乎包办了所有事:

  • Pod 间转发和 NetworkPolicy 执行,由挂在网卡上的 eBPF 程序完成;
  • Service 负载均衡也走 eBPF,所以能整个替代 kube-proxy;
  • Hubble 直接消费 eBPF 事件,输出服务依赖图和流量日志。
eBPF 不只属于网络

eBPF 是内核的通用能力:安全领域有 Falco、Tetragon,性能分析有 bcc、bpftrace。Cilium 只是把它在网络方向的用法做到了极致,顺带带火了整个生态。

Service Mesh(简介)​

Service Mesh 解决的是「服务间通信治理」问题:把负载均衡、重试、熔断、mTLS 加密、灰度发布、调用链观测这些公共能力,从业务代码里剥离出来,统一下沉到基础设施层。

做法是给每个 Pod 注入一个 Sidecar 代理(最常用 Envoy):Pod 的进出流量先经过 sidecar,由它执行各种治理策略,业务进程完全无感知。所有 sidecar 由一个控制平面统一管理和下发配置。

Service Mesh sidecar 结构

两个代表项目:

  • Istio:功能最全,Envoy sidecar + 强大的控制平面,流量治理细到 API 级别,学习和运维成本也最高。
  • Linkerd:sidecar 用 Rust 实现,主打轻量、简单、资源占用低。

什么时候不需要它:

  • 服务数量不多(几个到十几个),Ingress + Service 就能管好,没必要再叠一层。
  • 只需要其中一两项能力(比如只要 mTLS,或只要灰度发布),有更轻量的专项方案。
  • 团队没有余力维护多一层基础设施——每个 Pod 多跑一个代理,CPU、内存、排障复杂度的上涨都是实打实的。
一句话判断

Mesh 治理的是「几十上百个微服务互相调用」的场面;服务规模没到那个量级,它带来的复杂度往往超过收益。

网络排障​

K8s 网络问题看着玄,但按「从近到远、从 Pod 到 Service 再到插件」的顺序排,绝大多数都能定位。下面是一套可以直接照做的流程。

1. 先查 DNS 解析​

进 Pod 里解析服务名和集群内置域名:

kubectl exec -it <pod名> -- nslookup <服务名>
kubectl exec -it <pod名> -- nslookup kubernetes.default

解析失败就去查 CoreDNS:

kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50

2. 测 Pod 到 Pod 连通性​

先看 Pod 都分布在哪些节点、IP 是什么:

kubectl get pods -o wide

分别挑同节点和跨节点的 Pod 互测:

kubectl exec -it <pod名> -- ping -c 3 <对端Pod IP>
  • 同节点通、跨节点不通 → 问题大概率在 CNI 或底层网络:路由缺失、VXLAN 不通、云安全组没放行。
  • 同节点也不通 → 节点本地网络栈或 CNI agent 出问题,看第 4 步。

3. 测 Service 转发​

kubectl get svc <服务名>
kubectl get endpoints <服务名>
curl <ClusterIP>:<端口>
  • endpoints 为空是最常见的情况:Service 的 selector 和 Pod 的 label 对不上,或者 Pod 还没 Ready。用 kubectl describe svc <服务名> 对比两边标签即可。
  • endpoints 正常但 ClusterIP 不通 → 查 kube-proxy:
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=50

4. 看 CNI 组件是否健康​

CNI 插件以 DaemonSet 形式跑在每个节点上,它出问题时症状往往很「玄学」(时通时断、部分节点不通):

kubectl -n kube-system get pods -o wide | grep -E 'calico|flannel|cilium'

有 Pod 处于 CrashLoopBackOff 或频繁重启,就先翻它的日志;也可以对比各节点上插件 Pod 的状态,看问题是不是集中在某台节点。

5. 检查 NetworkPolicy 是否误伤​

kubectl get networkpolicy -A
kubectl describe networkpolicy <名称> -n <命名空间>

NetworkPolicy 是白名单语义:一旦某个 Pod 被任何一条策略选中,没被明确放行的流量就全部被拒。排障时可以先把可疑策略临时删掉验证,定位后再加回来并补齐放行规则。

6. 排查 MTU 不匹配​

这是最隐蔽的一类:小包通、大包丢,表现为 TCP 能握手但传数据就卡死。

原因在封装开销:VXLAN 头部占 50 字节,所以 Flannel 默认把 Pod 网络的 MTU 设为 1450(1500 - 50)。云上环境尤其容易踩坑:

  • 有的云 VPC 宿主机 MTU 是 1450,再叠一层 VXLAN 就只剩 1400;
  • 有的云内网开巨帧(MTU 9000),CNI 没跟着调,报文封装后超出路径 MTU,被中间设备静默丢弃。

验证方法:

# 查看各网卡 MTU(在节点和 Pod 里都可以执行)
ip link

# 用「禁止分片」的 ping 探测路径最大 MTU
ping -M do -s 1472 <对端IP> # 1472 + 28(包头) = 1500
ping -M do -s 8972 <对端IP> # 巨帧环境

哪一档开始不通,说明路径 MTU 就在那之下。修复方式是把 CNI 的 MTU 调成「宿主机 MTU - 封装开销」(VXLAN 减 50,IPIP 减 20)。

云上部署先实测 MTU

各家云、各种网络互联方式(VPC 对等、专线、混合云)的 MTU 都不一样。装 CNI 之前先用上面的 ping 方法实测一遍路径 MTU,比事后抓包定位省事得多。

走完这六步还定位不了的话,再祭出终极大法:在节点上抓包(tcpdump),看报文到底走到了哪一跳、在哪一跳消失——但绝大多数问题,其实都倒在前六步里了。