土法炼钢兴趣小组的算法知识备份

Service 与 kube-proxy:ClusterIP 背后的四种实现

目录

你在集群里创建一个 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 内核网络子系统在大规模集群场景下如何被逐步榨干性能。

本文要做的事:

  1. 回顾 K8s Service 的四种类型
  2. 拆解 kube-proxy 四种模式的内核实现
  3. 从规则数、延迟、CPU 三个维度做性能对比
  4. 解释 EndpointSlice 解决了什么问题
  5. 深入 Headless Service 与 StatefulSet 的 DNS 行为
  6. 实验:在同一集群中切换 iptables/IPVS,对比实际规则

本文基于 Kubernetes 1.31, kube-proxy v1.31, Linux 6.x 内核。 实验环境:kind v0.24, Ubuntu 22.04, Docker 26.x

kube-proxy 四种模式架构对比

一、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: 8080

ClusterIP 的关键特性:

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.com

CoreDNS 对 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
  1. kube-proxy 在用户态为每个 Service 监听一个随机端口
  2. 写一条 iptables 规则,把 ClusterIP:port 的流量 REDIRECT 到 kube-proxy 监听的端口
  3. kube-proxy 收到连接后,round-robin 选一个后端 Pod,建立新连接转发

致命问题

这个模式在 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 模式的两个核心痛点:

  1. O(n) 数据包路径:包匹配是线性遍历
  2. 全量规则更新:任何变更都需要 dump + restore 全部规则

IPVS 解决了这两个问题:

工作原理

kube-proxy 在 IPVS 模式下:

  1. 为每个 ClusterIP 在 kube-ipvs0 dummy 网卡上绑定 IP 地址
  2. 通过 netlink API 创建 virtual server
  3. 为每个 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_vsip_vs_rrip_vs_wrrip_vs_shnf_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 有一些根本性的架构限制:

  1. 规则更新非原子iptables-restore 在规则多的时候有可见的窗口期
  2. 无内置数据结构:不支持 set、map,只能用线性链
  3. 内核/用户态接口老旧:iptables 使用 getsockopt/setsockopt 接口,每次操作需要 dump 全表
  4. 与 nftables 的冲突:现代 Linux 发行版(Debian 11+、RHEL 9+)默认使用 nftables 作为防火墙后端,同时使用 iptables 和 nftables 会导致规则冲突
  5. 上游维护状态: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
    }
}

关键区别:

原子性规则替换

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 的原子性保证意味着:

启用 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"

前提条件:


六、性能对比:规则数 / 延迟 / 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 可携带 nodeNamezone(拓扑感知路由);readyservingterminating 条件分离(优雅终止);通过 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: 8080

Headless Service 的关键区别:

# 普通 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: 6379

DNS 行为:

# 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=80

iptables 模式下查看规则

# 进入节点
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=6443

eBPF 替代 kube-proxy 的优势:

关于 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:推荐阅读


系列导航 - 上一篇:CNI 规范拆解 - 下一篇:Dual-Stack 双栈网络 - 相关:Cilium 数据面:eBPF 如何替代 kube-proxy


By .