你在集群里创建一个 Service,拿到一个 ClusterIP,然后
curl 这个 IP,请求神奇地到达了后端
Pod。这个过程看起来平淡无奇,但你有没有想过:ClusterIP
是一个”虚拟 IP”,它不属于任何网卡、不在任何路由表里、不响应
ARP – 它凭什么能被访问?
答案在 kube-proxy。它是每个节点上运行的组件,负责把 Service 的虚拟 IP 翻译成真实的 Pod IP。但 kube-proxy 并不代理流量(至少现在不了)。它的核心工作是编程内核数据面 – 往 iptables、IPVS 或 nftables 里写规则,让内核在不经过用户态的情况下完成 DNAT。
从 Kubernetes 诞生到现在,kube-proxy 经历了四种实现模式:userspace、iptables、IPVS、nftables。每一代都是对前一代性能瓶颈的回应。理解这个演化过程,就是理解 Linux 内核网络子系统在大规模集群场景下如何被逐步榨干性能。
本文要做的事:
- 回顾 K8s Service 的四种类型
- 拆解 kube-proxy 四种模式的内核实现
- 从规则数、延迟、CPU 三个维度做性能对比
- 解释 EndpointSlice 解决了什么问题
- 深入 Headless Service 与 StatefulSet 的 DNS 行为
- 实验:在同一集群中切换 iptables/IPVS,对比实际规则
本文基于 Kubernetes 1.31, kube-proxy v1.31, Linux 6.x 内核。 实验环境:kind v0.24, Ubuntu 22.04, Docker 26.x
一、K8s Service 类型回顾
在深入 kube-proxy 之前,先回顾 Service 的四种类型。它们是上层抽象,kube-proxy 负责把这些抽象翻译成内核数据面规则。
ClusterIP – 集群内虚拟 IP
最基础的类型。创建后分配一个仅集群内可达的虚拟 IP(VIP):
apiVersion: v1
kind: Service
metadata:
name: my-svc
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 8080ClusterIP 的关键特性:
- VIP 不绑定任何网络接口(no ARP, no routing entry)
- 只存在于 iptables/IPVS/nftables 规则中
- kube-proxy 在每个节点上编程规则,把 VIP:port 的流量 DNAT 到后端 Pod IP:targetPort
- 这个 DNAT 完全在内核态完成,不经过用户态
NodePort – 宿主机端口暴露
在 ClusterIP 的基础上,额外在每个节点上开一个端口(默认范围 30000-32767):
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30080外部流量可以通过 <任意节点IP>:30080
访问服务。kube-proxy 为 NodePort 额外添加 DNAT
规则:先匹配节点 IP + 端口,再 DNAT 到后端 Pod。
LoadBalancer – 云厂商负载均衡器
在 NodePort
的基础上,请求云厂商创建一个外部负载均衡器,把流量导到
NodePort 或者直接导到 Pod(取决于
externalTrafficPolicy):
spec:
type: LoadBalancer这是一个三层叠加的类型:LoadBalancer 包含 NodePort,NodePort 包含 ClusterIP。
ExternalName – CNAME 别名
不涉及 kube-proxy,纯 DNS 层面:
spec:
type: ExternalName
externalName: db.legacy.example.comCoreDNS 对 my-svc.default.svc.cluster.local
返回 CNAME 记录指向 db.legacy.example.com。没有
VIP,没有代理,没有 DNAT。
四种类型的关系
ExternalName -- 纯 DNS CNAME,不涉及 kube-proxy
ClusterIP -- VIP + DNAT 规则(kube-proxy 核心工作)
NodePort -- ClusterIP + 节点端口监听
LoadBalancer -- NodePort + 云厂商 LB
下面聚焦 kube-proxy 的核心工作:如何把 ClusterIP 的 DNAT 写进内核。
二、模式一:userspace(已废弃)
这是 Kubernetes 最早期(1.0 - 1.1)的默认模式。理解它有助于理解后续模式的设计动机。
工作原理
Client Pod -> iptables REDIRECT (kernel) -> kube-proxy 进程 (userspace) -> Backend Pod
- kube-proxy 在用户态为每个 Service 监听一个随机端口
- 写一条 iptables 规则,把 ClusterIP:port 的流量 REDIRECT 到 kube-proxy 监听的端口
- kube-proxy 收到连接后,round-robin 选一个后端 Pod,建立新连接转发
致命问题
- 两次内核/用户态拷贝:每个包都要从内核态到用户态再回来
- 连接数瓶颈:kube-proxy 是所有 Service 流量的代理人
- 延迟高:多了两次上下文切换 + 两次内存拷贝
这个模式在 Kubernetes 1.2 之后被 iptables 模式取代,1.26 后被彻底移除。它清楚地展示了一个教训:流量不应该经过用户态代理。
三、模式二:iptables(当前默认)
从 Kubernetes 1.2 开始成为默认模式,至今仍然是大多数集群的选择。核心思想:把所有的负载均衡逻辑都下沉到内核的 netfilter/iptables 中,kube-proxy 只负责编程规则,不再经手实际流量。
工作原理
kube-proxy watch apiserver 的 Service 和 EndpointSlice 资源,当有变更时重新生成完整的 iptables 规则并写入。流量路径完全在内核态:
Client Pod -> iptables DNAT (kernel, pure netfilter) -> Backend Pod
规则链结构
kube-proxy 创建的 iptables 规则遵循一个固定的链结构。以一个有 3 个后端 Pod 的 Service 为例:
# 1. 入口:KUBE-SERVICES 链
# 所有进入 nat 表 PREROUTING / OUTPUT 的包都先跳到这里
-A PREROUTING -j KUBE-SERVICES
-A OUTPUT -j KUBE-SERVICES
# 2. KUBE-SERVICES:匹配 ClusterIP + port,跳转到 Service 专属链
-A KUBE-SERVICES -d 10.96.100.50/32 -p tcp --dport 80 \
-j KUBE-SVC-XXXXXX
# 3. KUBE-SVC-XXXXXX:用概率实现负载均衡
-A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.33333 \
-j KUBE-SEP-AAAAAA
-A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.50000 \
-j KUBE-SEP-BBBBBB
-A KUBE-SVC-XXXXXX \
-j KUBE-SEP-CCCCCC
# 4. KUBE-SEP-XXXXXX:实际的 DNAT
-A KUBE-SEP-AAAAAA -p tcp -j DNAT --to-destination 10.244.1.5:8080
-A KUBE-SEP-BBBBBB -p tcp -j DNAT --to-destination 10.244.2.3:8080
-A KUBE-SEP-CCCCCC -p tcp -j DNAT --to-destination 10.244.3.7:8080概率负载均衡的数学
注意 --probability 的值不是均匀的 0.33:
3 个后端:
第 1 条: probability = 1/3 = 0.33333 --> 选中概率 33.3%
第 2 条: probability = 1/2 = 0.50000 --> 条件概率 50%,实际 = (1-0.333)*0.5 = 33.3%
第 3 条: 无 probability(默认选中) --> 实际 = (1-0.333)*(1-0.5) = 33.3%
n 个后端:
第 k 条: probability = 1/(n-k+1)
这是一种巧妙的链式概率设计,保证每个后端被选中的概率均等。但它的缺点也很明显:只支持等概率随机,不支持加权、最少连接等调度算法。
规则膨胀与全量更新
假设集群有 S 个 Service,每个 Service 平均有 E 个 Endpoint。总规则数约 S(1+2E):5000 个 Service 10 个 Endpoint = 约 105,000 条规则。
更严重的是更新方式。iptables
规则更新是全量替换:kube-proxy 执行
iptables-save dump 全部规则,修改后
iptables-restore 写回。在 10
万条规则的集群里,一次 restore 可能需要几秒钟,期间 iptables
持锁,阻塞其他组件(如 CNI 插件)的操作。
数据包路径:O(n) 线性遍历
iptables 的匹配是线性的。一个包进入
KUBE-SERVICES
链后,从第一条规则开始逐条匹配。5000 个 Service
意味着最坏情况下遍历 5000 条规则。这个 O(n)
在大规模集群中会导致可观测的延迟增长。
四、模式三:IPVS
IPVS(IP Virtual Server)是 Linux 内核中 LVS(Linux Virtual Server)项目的一部分,专门为四层负载均衡设计。kube-proxy 的 IPVS 模式从 Kubernetes 1.11 开始 GA。
为什么需要 IPVS
iptables 模式的两个核心痛点:
- O(n) 数据包路径:包匹配是线性遍历
- 全量规则更新:任何变更都需要 dump + restore 全部规则
IPVS 解决了这两个问题:
- 数据包匹配使用哈希表,O(1) 时间复杂度
- 后端增删是增量操作,不需要重写全部规则
工作原理
kube-proxy 在 IPVS 模式下:
- 为每个 ClusterIP 在
kube-ipvs0dummy 网卡上绑定 IP 地址 - 通过 netlink API 创建 virtual server
- 为每个 Endpoint 添加 real server
# kube-ipvs0 网卡上绑定了所有 Service 的 ClusterIP
ip addr show kube-ipvs0
# inet 10.96.0.1/32 scope global kube-ipvs0
# inet 10.96.0.10/32 scope global kube-ipvs0
# inet 10.96.100.50/32 scope global kube-ipvs0为什么要绑定 IP?IPVS 工作在 INPUT 链上。包要被路由到本机的 INPUT 链,内核需要认为目标 IP 属于本机。绑定在 dummy 网卡上就是为了让内核把包送进 INPUT 链,然后 IPVS hook 接管。数据包匹配使用哈希表,O(1) 时间复杂度;后端增删是增量操作,不需要重写全部规则。
四种调度算法
IPVS 支持的调度算法远比 iptables 的随机概率丰富:
# Round Robin -- 轮询
ipvsadm -A -t 10.96.100.50:80 -s rr
# Weighted Round Robin -- 加权轮询
ipvsadm -A -t 10.96.100.50:80 -s wrr
ipvsadm -a -t 10.96.100.50:80 -r 10.244.1.5:8080 -w 3
ipvsadm -a -t 10.96.100.50:80 -r 10.244.2.3:8080 -w 1
# Least Connections -- 最少连接
ipvsadm -A -t 10.96.100.50:80 -s lc
# Source Hashing -- 源地址哈希(session affinity)
ipvsadm -A -t 10.96.100.50:80 -s sh| 算法 | 标识 | 行为 | 适用场景 |
|---|---|---|---|
| Round Robin | rr | 轮询,逐个分配 | 后端性能均匀 |
| Weighted Round Robin | wrr | 按权重比例分配 | 后端性能不均匀 |
| Least Connections | lc | 分配给当前连接数最少的后端 | 长连接场景 |
| Source Hashing | sh | 按源 IP 哈希固定到同一后端 | 需要会话保持 |
kube-proxy 默认使用 rr。可以通过
--ipvs-scheduler 参数修改。
IPVS 与 iptables 的混用
IPVS 模式并没有完全摆脱 iptables。以下功能仍然需要 iptables 规则:
# 1. SNAT / Masquerade -- 跨节点回包需要 SNAT
-A KUBE-POSTROUTING -j MASQUERADE
# 2. NodePort -- IPVS 需要 iptables 在 PREROUTING 阶段把 NodePort 流量导入 INPUT 链
-A KUBE-NODE-PORT -p tcp --dport 30080 -j KUBE-MARK-MASQ
# 3. externalTrafficPolicy: Local -- 丢弃非本节点后端的流量
# 4. topology-aware routing所以 IPVS 模式下的 iptables-save
不会是空的,只是规则数量少了一个数量级。
查看 IPVS 规则
ipvsadm -Ln
# TCP 10.96.100.50:80 rr
# -> 10.244.1.5:8080 Masq 1 3 0
# -> 10.244.2.3:8080 Masq 1 2 0
# -> 10.244.3.7:8080 Masq 1 4 0
ipvsadm -Ln --stats # 查看统计信息内核模块依赖
IPVS 模式需要
ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack
内核模块。用 lsmod | grep ip_vs
验证。如果缺少这些模块,kube-proxy 会自动回退到 iptables
模式。
五、模式四:nftables(Kubernetes 1.31+)
nftables 是 Linux 内核中 iptables 的继任者。kube-proxy 的 nftables 模式在 1.29 作为 alpha 引入,1.31 升级为 beta。
为什么要从 iptables 迁移
iptables 有一些根本性的架构限制:
- 规则更新非原子:
iptables-restore在规则多的时候有可见的窗口期 - 无内置数据结构:不支持 set、map,只能用线性链
- 内核/用户态接口老旧:iptables 使用
getsockopt/setsockopt接口,每次操作需要 dump 全表 - 与 nftables 的冲突:现代 Linux 发行版(Debian 11+、RHEL 9+)默认使用 nftables 作为防火墙后端,同时使用 iptables 和 nftables 会导致规则冲突
- 上游维护状态:iptables 已进入维护模式,netfilter 团队的开发重心完全在 nftables
nftables 的核心优势
iptables:
匹配方式: 线性遍历每条规则 O(n)
数据结构: 无(只有 chain + rule)
更新方式: dump 全表 -> 修改 -> restore 全表
原子性: restore 期间有窗口期
nftables:
匹配方式: verdict map / set 查找 O(1)
数据结构: set, map, vmap(内核红黑树/哈希表)
更新方式: 原子性 ruleset swap
原子性: 完全原子,无窗口期
规则结构
nftables 模式使用 verdict map 代替 iptables 的线性链:
# 查看 nftables 规则
nft list table ip kube-proxy
# 输出示例(简化):
table ip kube-proxy {
map service-ips {
type ipv4_addr . inet_proto . inet_service : verdict
elements = {
10.96.100.50 . tcp . 80 : goto service-XXXXXX,
10.96.0.10 . tcp . 53 : goto service-YYYYYY,
}
}
map service-XXXXXX-eps {
type inet_service : ipv4_addr . inet_service
elements = {
0 : 10.244.1.5 . 8080,
1 : 10.244.2.3 . 8080,
2 : 10.244.3.7 . 8080,
}
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
ip daddr . meta l4proto . th dport vmap @service-ips
}
}关键区别:
vmap @service-ips是一个 verdict map,O(1) 哈希查找,直接跳转到对应 Service 的处理链- 不需要像 iptables 那样线性遍历 5000 条规则去匹配 ClusterIP
- 新增/删除 Service 只需要往 map 中 add/delete 一个 entry,不需要重写整个 ruleset
原子性规则替换
nftables 支持事务性操作:
# 原子性提交一批规则变更
nft -f - <<EOF
add element ip kube-proxy service-XXXXXX-eps { 3 : 10.244.4.2 . 8080 }
delete element ip kube-proxy service-XXXXXX-eps { 1 : 10.244.2.3 . 8080 }
EOF
# 要么全部成功,要么全部失败,不存在中间状态对比 iptables 的 iptables-restore,nftables
的原子性保证意味着:
- 没有规则更新的窗口期
- 不需要持全局锁
- 不会出现”规则写到一半 crash 导致防火墙规则不完整”的风险
启用 nftables 模式
# 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: "nftables"前提条件:
- 节点内核 >= 5.13(推荐 6.1+)
- 安装
nft用户态工具 - 内核编译了
nf_tables模块
六、性能对比:规则数 / 延迟 / CPU
以下对比基于 10,000 个 Service,每个 Service 10 个 Endpoint 的场景:
规则数量
| 模式 | 内核规则/对象数 | 说明 |
|---|---|---|
| iptables | ~200,000 条 iptables 规则 | S*(1+2E) 线性增长 |
| IPVS | ~10,000 virtual server + ~100,000 real server + 少量 iptables | IPVS 条目 + 辅助 iptables |
| nftables | ~10,000 map entry + ~100,000 set element | 原生 map/set 数据结构 |
规则更新延迟
场景:新增 1 个 Endpoint
| 模式 | 操作 | 耗时 | 特点 |
|---|---|---|---|
| iptables | dump 200K 规则 + 修改 + restore | ~5s | 持锁阻塞其他 iptables 操作 |
| IPVS | ipvsadm -a 增量添加 |
~1ms | 无锁 |
| nftables | nft add element 原子操作 |
~1ms | 无窗口期 |
数据包路径 CPU 开销
| 模式 | 匹配方式 | 复杂度 |
|---|---|---|
| iptables | 线性遍历 KUBE-SERVICES 链 | O(n) |
| IPVS | 哈希表查找 VIP:port | O(1) |
| nftables | verdict map 哈希查找 | O(1) |
什么时候该切换
| 集群规模 | 推荐模式 | 理由 |
|---|---|---|
| < 1,000 Service | iptables | 够用,最成熟,调试工具完善 |
| 1,000 - 10,000 Service | IPVS | iptables 的 O(n) 和全量更新成为瓶颈 |
| > 10,000 Service | IPVS 或 nftables | 原子更新和 O(1) 查找是刚需 |
| 新集群(2025+) | 考虑 nftables | nftables 是内核社区的方向 |
七、EndpointSlice vs Endpoints:为什么要拆分
老问题:Endpoints 对象的体积膨胀
在 EndpointSlice 出现之前,每个 Service 对应一个
Endpoints 对象,包含所有后端
Pod 的 IP。当 Service 有 3000 个后端 Pod
时,这个对象可达几百 KB。每当一个 Pod
变更,整个对象被更新并推送给所有节点的 kube-proxy:
1 个 Pod 变更
-> 更新 1 个 Endpoints 对象(几百 KB)
-> 推送给所有 kube-proxy(1000 节点 * 300KB = 300MB 网络流量)
-> 每个 kube-proxy 解析完整对象,重新生成规则
EndpointSlice 的解法
EndpointSlice 把一个大的 Endpoints 对象拆分成多个小的 slice,每个 slice 默认最多 100 个 endpoint:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-large-svc-abc12
labels:
kubernetes.io/service-name: my-large-svc
addressType: IPv4
endpoints:
- addresses:
- 10.244.1.5
conditions:
ready: true
nodeName: node-01
- addresses:
- 10.244.1.6
conditions:
ready: true
nodeName: node-01
# ... 最多 100 个
ports:
- port: 8080
protocol: TCP拆分的好处:3000 个后端拆成 30 个 EndpointSlice。1 个 Pod 变更只更新 1 个 slice(~5KB),而非全量 300KB。
额外优势
EndpointSlice 还带来了 Endpoints 不具备的能力:每个
endpoint 可携带
nodeName、zone(拓扑感知路由);ready、serving、terminating
条件分离(优雅终止);通过 addressType 区分
IPv4/IPv6(双栈支持)。
kube-proxy 的 EndpointSlice 消费
kube-proxy 从 1.19 开始默认使用 EndpointSlice API。老的 Endpoints API 仍然存在(向后兼容),但 kube-proxy 不再依赖它。
八、Headless Service 与 StatefulSet 的 DNS 行为
Headless Service:clusterIP: None
当你把 clusterIP 设置为
None,就创建了一个 Headless Service:
apiVersion: v1
kind: Service
metadata:
name: my-headless
spec:
clusterIP: None
selector:
app: my-app
ports:
- port: 80
targetPort: 8080Headless Service 的关键区别:
- 没有 VIP:不分配 ClusterIP
- kube-proxy 不参与:不创建任何 iptables/IPVS/nftables 规则
- DNS 直接返回 Pod IP:CoreDNS 对 Service 名的 A 记录查询直接返回所有后端 Pod 的 IP
# 普通 Service 的 DNS 解析
dig my-svc.default.svc.cluster.local
# ANSWER: 10.96.100.50 (ClusterIP, 一条 A 记录)
# Headless Service 的 DNS 解析
dig my-headless.default.svc.cluster.local
# ANSWER: 10.244.1.5 (Pod IP)
# 10.244.2.3 (Pod IP)
# 10.244.3.7 (Pod IP)
# 返回所有就绪 Pod 的 IP,多条 A 记录StatefulSet + Headless Service:稳定的 DNS 名
StatefulSet 的 Pod 有稳定的身份标识(pod-0,
pod-1, pod-2)。配合 Headless
Service,每个 Pod 会获得一个独立的 DNS A 记录:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: redis-headless # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-headless
spec:
clusterIP: None
selector:
app: redis
ports:
- port: 6379DNS 行为:
# Service 级别 DNS -- 返回所有 Pod IP
dig redis-headless.default.svc.cluster.local
# ANSWER:
# 10.244.1.10
# 10.244.2.11
# 10.244.3.12
# 单个 Pod 的 DNS -- 稳定的名字
dig redis-0.redis-headless.default.svc.cluster.local
# ANSWER: 10.244.1.10
dig redis-1.redis-headless.default.svc.cluster.local
# ANSWER: 10.244.2.11
dig redis-2.redis-headless.default.svc.cluster.local
# ANSWER: 10.244.3.12这个稳定的 DNS 名是有状态服务的关键基础设施。Redis Sentinel 配置哨兵地址、Kafka broker 之间互相发现、ZooKeeper 集群成员列表 – 都依赖这个机制。
九、实验:iptables 与 IPVS 模式切换对比
以下实验在 kind 集群中进行,展示两种模式下的实际规则差异。
环境准备
# 创建 kind 集群
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
networking:
kubeProxyMode: "iptables" # 先用 iptables
EOF
# 部署测试 Service
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80 --target-port=80iptables 模式下查看规则
# 进入节点
docker exec -it kind-worker bash
# 查看 KUBE-SERVICES 链
iptables-save -t nat | grep "KUBE-SERVICES"
# -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
# -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
# -A KUBE-SERVICES -d 10.96.0.1/32 -p tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
# -A KUBE-SERVICES -d 10.96.XXX.YYY/32 -p tcp --dport 80 -j KUBE-SVC-XXXXXXXX
# 查看具体的概率负载均衡和 DNAT 规则
iptables-save -t nat | grep "KUBE-SVC-XXXXXXXX\|KUBE-SEP"
# -A KUBE-SVC-XXXXXXXX ... --probability 0.33333 -j KUBE-SEP-AAAAAA
# -A KUBE-SVC-XXXXXXXX ... --probability 0.50000 -j KUBE-SEP-BBBBBB
# -A KUBE-SVC-XXXXXXXX ... -j KUBE-SEP-CCCCCC
# -A KUBE-SEP-AAAAAA -p tcp -j DNAT --to-destination 10.244.1.5:80
# -A KUBE-SEP-BBBBBB -p tcp -j DNAT --to-destination 10.244.2.3:80
# -A KUBE-SEP-CCCCCC -p tcp -j DNAT --to-destination 10.244.3.7:80
# 统计总规则数
iptables-save -t nat | wc -l切换到 IPVS 模式
# 编辑 kube-proxy ConfigMap
kubectl -n kube-system edit configmap kube-proxy
# 将 mode: "" 改为 mode: "ipvs"
# 重启 kube-proxy 并验证
kubectl -n kube-system rollout restart daemonset kube-proxy
kubectl -n kube-system logs -l k8s-app=kube-proxy | grep "Using"
# "Using ipvs Proxier"IPVS 模式下查看规则
# 进入节点
docker exec -it kind-worker bash
# 查看 IPVS virtual server
ipvsadm -Ln
# TCP 10.96.0.1:443 rr
# -> 172.18.0.3:6443 Masq 1 0 0
# TCP 10.96.0.10:53 rr
# -> 10.244.0.2:53 Masq 1 0 0
# -> 10.244.0.3:53 Masq 1 0 0
# TCP 10.96.XXX.YYY:80 rr
# -> 10.244.1.5:80 Masq 1 0 0
# -> 10.244.2.3:80 Masq 1 0 0
# -> 10.244.3.7:80 Masq 1 0 0
# 查看 kube-ipvs0 dummy 网卡(所有 ClusterIP 绑定在这里)
ip addr show kube-ipvs0
# 对比 iptables 规则数量(IPVS 模式下仍有少量辅助规则)
iptables-save -t nat | wc -l
# 通常比 iptables 模式少一个数量级对比总结
iptables 模式 IPVS 模式
nat 表规则数 30-50 条(3 svc) 5-10 条
IPVS VS 数 0 3-5 个
IPVS RS 数 0 6-10 个
kube-ipvs0 IP 不存在 3-5 个 ClusterIP
数据包路径 KUBE-SERVICES 线性匹配 IPVS hash 查找
调度算法 随机概率 rr (可配置)
扩展测试 – 创建 1000 个 Service 后,iptables 模式下
iptables-save -t nat | wc -l 约 6000-8000
行;IPVS 模式下 ipvsadm -Ln 约 1000 个 virtual
server,但 iptables-save -t nat | wc -l 只有
20-30 行。差异一目了然。
十、eBPF:绕过 kube-proxy 的第五条路
虽然 kube-proxy 的四种模式不断演进,但它们都有一个共同点:依赖 netfilter 框架(iptables/nftables)或 IPVS 内核模块。这些都是通用的内核数据面组件,并非为 Kubernetes Service 专门设计。
eBPF 提供了一种完全不同的思路:直接在 TC(Traffic Control)或 XDP hook 点编程,绕过 netfilter/conntrack 的整个链路。
传统 kube-proxy 路径:
NIC -> netfilter PREROUTING -> conntrack -> iptables/IPVS DNAT -> routing -> ...
eBPF 替代路径 (Cilium):
NIC -> TC ingress hook -> eBPF program (DNAT) -> routing -> ...
跳过了 netfilter/conntrack,减少了数据包处理的内核路径长度
Cilium 是当前最成熟的 kube-proxy 替代方案,它的
kube-proxy-replacement 模式可以完全取代
kube-proxy:
# 安装 Cilium 并启用 kube-proxy 替代
helm install cilium cilium/cilium \
--set kubeProxyReplacement=true \
--set k8sServiceHost=<API_SERVER_IP> \
--set k8sServicePort=6443eBPF 替代 kube-proxy 的优势:
- 更短的数据包路径:跳过 netfilter/conntrack
- 更低的延迟:减少了内核处理步骤
- socket-level LB:在
connect()系统调用时就完成 DNAT,减少每个包的开销 - 完整的可观测性:eBPF 程序可以导出 per-service、per-endpoint 的指标
关于 eBPF 数据面的深入分析,参见 Cilium 数据面:eBPF 如何替代 kube-proxy。
十一、conntrack 与 Service 的关系
不管用哪种 kube-proxy 模式(eBPF 除外),DNAT 完成后都会在内核的 conntrack 表中创建一条记录,这是回包能正确返回的关键。
请求包:
Client Pod (10.244.0.5) -> ClusterIP (10.96.100.50:80)
DNAT 后: Client Pod (10.244.0.5) -> Backend Pod (10.244.1.5:8080)
conntrack 记录:
tcp 120 ESTABLISHED src=10.244.0.5 dst=10.96.100.50 sport=45678 dport=80
src=10.244.1.5 dst=10.244.0.5 sport=8080 dport=45678
回复包: Backend Pod -> Client Pod
conntrack 查表,执行反向 NAT (un-DNAT)
Client Pod 始终以为自己在和 ClusterIP 通信
在高并发场景下 conntrack 表可能溢出(内核日志
nf_conntrack: table full, dropping packet):
# 查看 conntrack 表使用情况
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 调优
sysctl -w net.netfilter.nf_conntrack_max=262144
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30十二、实战排查速查
# === kube-proxy 模式确认 ===
kubectl -n kube-system logs -l k8s-app=kube-proxy | grep -i "using\|proxier"
kubectl -n kube-system get configmap kube-proxy -o yaml | grep mode
ipvsadm -Ln 2>/dev/null && echo "IPVS mode" || echo "Not IPVS"
nft list table ip kube-proxy 2>/dev/null && echo "nftables mode" || echo "Not nftables"
# === Service 不通排查 ===
kubectl get svc my-svc # Service 存在?
kubectl get endpointslice -l kubernetes.io/service-name=my-svc # 有后端?
kubectl get pods -l app=my-app -o wide # Pod 就绪?
iptables-save -t nat | grep my-svc # iptables 规则?
ipvsadm -Ln | grep <ClusterIP> # IPVS 规则?
conntrack -L -d <ClusterIP> # conntrack 记录?
tcpdump -i any -nn host <ClusterIP> # 抓包验证 DNAT
curl <PodIP>:<targetPort> # 绕过 Service 直连
# === 常见问题 ===
# ClusterIP 不通 -> kube-proxy 是否在运行?
# 新 Service 延迟可用 -> iptables-restore 全量更新耗时(大集群)
# IPVS 模式 NodePort 不通 -> lsmod | grep ip_vs(模块是否加载)
# dropping packet -> conntrack 表溢出,增大 nf_conntrack_max附录 A:kube-proxy 启动参数速查
--proxy-mode=iptables|ipvs|nftables # 模式选择
--ipvs-scheduler=rr|wrr|lc|sh # IPVS 调度算法
--cluster-cidr=10.244.0.0/16 # Pod CIDR(用于 SNAT 判断)
--conntrack-max-per-core=32768 # 每核 conntrack 上限
--iptables-sync-period=30s # iptables 同步周期
--iptables-min-sync-period=1s # 最小同步间隔(防抖)附录 B:推荐阅读
- Kubernetes 官方文档:Service
- Kubernetes 官方文档:Virtual IPs and Service Proxies
- KEP-3866: nftables kube-proxy backend
- IPVS 内核文档:IP Virtual Server
- 本系列前置:netfilter 与 iptables、K8s 网络模型、CNI 规范
系列导航 - 上一篇:CNI 规范拆解 - 下一篇:Dual-Stack 双栈网络 - 相关:Cilium 数据面:eBPF 如何替代 kube-proxy