bigywater.tech
Kubernetes · 71

kube-proxy iptables 模式:Service 对应的链

ClusterIP 是怎么转发到 Pod 的?iptables 里到底有哪几条规则?负载均衡是轮询吗?没有后端时会怎样?这篇直接在节点上读 iptables-save 和 conntrack,把一个 Service 从入口到 DNAT 的每一条规则列出来。

3 层链
KUBE-SERVICES → KUBE-SVC → KUBE-SEP,最后一层做 DNAT
99 / 105 / 96
300 个新连接在 3 个后端上的分布(随机抽签)
REJECT
没有后端时 filter 表里为这个 Service 写的规则;访问立即 Connection refused

三层链

实测 · 本机执行输出
  入口:KUBE-SERVICES 里匹配 10.96.165.10:80 的规则 → 跳到 KUBE-SVC-AXS6JBVWTHKDNMN2
  KUBE-SERVICES 里这条规则:KUBE-SERVICES -d 10.96.165.10/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-AXS6JBVWTHKDNMN2
  KUBE-SVC-AXS6JBVWTHKDNMN2 ! -s 10.244.0.0/16 -d 10.96.165.10/32 -p tcp -m tcp --dport 80 -j KUBE-MARK-MASQ
  KUBE-SVC-AXS6JBVWTHKDNMN2 -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-ZH6EKVWWUW7HGFRS
  KUBE-SVC-AXS6JBVWTHKDNMN2 -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-LLOQELPUZPPOYHAC
  KUBE-SVC-AXS6JBVWTHKDNMN2 -j KUBE-SEP-HNWNDCAU6HSM3PPX
  KUBE-SEP-ZH6EKVWWUW7HGFRS -s 10.244.1.158/32 -j KUBE-MARK-MASQ
  KUBE-SEP-ZH6EKVWWUW7HGFRS -p tcp -m tcp -j DNAT --to-destination 10.244.1.158:80
  KUBE-SEP-LLOQELPUZPPOYHAC -p tcp -m tcp -j DNAT --to-destination 10.244.2.196:80
  KUBE-SEP-HNWNDCAU6HSM3PPX -p tcp -m tcp -j DNAT --to-destination 10.244.2.197:80
  整个 nat 表有 102 行,其中和 k65/web 有关的 14 行

节点 nat 表里 Service web 的规则:KUBE-SERVICES 里匹配 -d 10.96.165.10 --dport 80 的规则跳到 KUBE-SVC-AXS6JBVWTHKDNMN2;SVC 链先有一条 ! -s 10.244.0.0/16 的规则打上 KUBE-MARK-MASQ(集群外来源要做源地址转换),再有三条 -j KUBE-SEP-…,前两条带 statistic --mode random --probability(0.333 和 0.5),最后一条无条件;每个 KUBE-SEP 链里是 DNAT --to-destination Pod IP:80,第一个 SEP 另有一条对 Pod 自己的 -s Pod IP -j KUBE-MARK-MASQ。整个 nat 表 102 行,和这个 Service 有关的 14 行。

不是轮询,是抽签

实测 · 本机执行输出
  2 个副本:KUBE-SVC-AXS6JBVWTHKDNMN2 里依次的 probability = ['0.50000000000', '(无条件)'](从前到后依次是 1/2,最后一条无条件)
  5 个副本:KUBE-SVC-AXS6JBVWTHKDNMN2 里依次的 probability = ['0.20000000019', '0.25000000000', '0.33333333349', '0.50000000000', '(无条件)'](从前到后依次是 1/5, 1/4, 1/3, 1/2,最后一条无条件)
  回到 3 个副本,从 cli 发 300 个新连接到 ClusterIP,按后端 Pod 统计:[('r429k', 99), ('xwtmm', 105), ('zmzx5', 96)](每个连接各自抽签,期望 100 左右)

2 个副本时 SVC 链的概率依次是 0.5、无条件;5 个副本依次是 0.2、0.25、0.333、0.5、无条件。第 i 条是 1/(n-i+1),保证每个后端总体概率都是 1/n。从 cli 发 300 个新连接,三个后端分别收到 99、105、96 个。每个连接独立抽签,短期内可能不均匀;一个连接建立后由 conntrack 固定在同一个后端。

更新与无后端

实测 · 本机执行输出
  scale 到 4 个副本:新 Pod 就绪后,iptables 里 KUBE-SVC-AXS6JBVWTHKDNMN2 的 SEP 数从 3 变 4 用了 1.5 秒(从 scale 算起,包含 Pod 启动和就绪)
  副本数 0:nat 表里 k65/web 的 SEP 规则 0 行;filter 表里出现 REJECT 规则:['KUBE-EXTERNAL-SERVICES -p tcp -m addrtype --dst-type LOCAL -m tcp --dport 32517 -j REJECT --reject-with icmp-port-unreachable', 'KUBE-SERVICES -d 10.96.165.10/32 -p tcp -m tcp --dport 80 -j REJECT --reject-with icmp-port-unreachable']
  访问 ClusterIP:["wget: can't connect to remote host (10.96.165.10): Connection refused", 'exit=1'];几乎立刻被拒绝(用时 1.1 秒,含 kubectl exec 开销),而不是等超时

scale 到 4 个副本后,从 scale 算起 1.5 秒(含 Pod 启动和就绪)SVC 链里的 SEP 数就从 3 变成 4。副本数降到 0 后,nat 表里不再有这个 Service 的 SEP 规则,filter 表里则出现 REJECT --reject-with icmp-port-unreachable(ClusterIP 与 NodePort 各一条),所以访问 ClusterIP 几乎立即得到 Connection refused,而不是卡到超时。

NodePort 与 conntrack

实测 · 本机执行输出
  NodePort 入口:KUBE-NODEPORTS -d 127.0.0.0/8 -p tcp -m tcp --dport 32517 -m nfacct --nfacct-name  localhost_nps_accepted_pkts -j KUBE-EXT-AXS6JBVWTHKDNMN2; KUBE-NODEPORTS -p tcp -m tcp --dport 32517 -j KUBE-EXT-AXS6JBVWTHKDNMN2
  KUBE-SERVICES 末尾:KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
  conntrack 表里 ClusterIP 的连接(这个节点上):['tcp      6 108 TIME_WAIT src=10.244.1.159 dst=10.96.165.10 sport=56100 dport=80 src=10.244.1.160 dst=10.244.1.159 sport=80 dport=56100 [ASSURED] mark=0 use=1', 'tcp      6 108 TIME_WAIT src=10.244.1.159 dst=10.96.165.10 sport=56568 dport=80 src=10.244.1.158 dst=10.244.1.159 sport=80 dport=56568 [ASSURED] mark=0 use=1']

NodePort 的入口:KUBE-SERVICES 的最后一条是 -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS,发往本机地址的包进入 KUBE-NODEPORTS,--dport 32517 跳到 KUBE-EXT-AXS6JBVWTHKDNMN2(再接同一条 SVC 链),回环地址 127.0.0.0/8 另有一条。conntrack -L 里可以看到一个连接:原方向 dst=10.96.165.10 dport=80,回复方向 src=10.244.1.160——DNAT 的映射记在 conntrack 里,回包自动改写回 ClusterIP。

分步演示

iptables
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0),kube-proxy 配置 mode: iptables,节点上的 iptables 是 v1.8.11(nf_tables 后端);在 worker 节点用 docker exec 运行 iptables-save 与 conntrack;后端是 busybox httpd,返回各自的主机名,300 个请求是从客户端 Pod 里循环 wget(每次新连接);SEP 数变化是每 0.3 秒读一次 iptables-save;每种情形各一次
    • 没有验证:kube-proxy 的 syncPeriod / minSyncPeriod 对更新延迟的影响;大规模 Service(上万条规则)下 iptables 的性能退化;externalTrafficPolicy: Local 的链(下面有一篇);会话保持的 recent 模块;主机网络命名空间里的 Pod;IPv6 的 ip6tables;kube-proxy 的 nftables 与 ipvs 模式(下一篇)
    京ICP备2026067502号-1京公网安备11010502063487号