文章

K8s Service 模型与转发实现

K8s Service 模型与转发实现

Service 四类型

类型访问入口典型用途
ClusterIP集群内虚拟 IP(默认)集群内部服务间调用
NodePort每个节点 :3xxxx 端口从集群外临时访问(开发/调试)
LoadBalancer云 LB 分配的公网/内网 IP生产对外暴露(依赖云控制器)
ExternalName返回 CNAME 的 DNS 记录把集群内服务映射到集群外域名
# 最小 ClusterIP(自动分配 VIP)
apiVersion: v1
kind: Service
metadata: { name: api }
spec:
  selector: { app: api }
  ports:
    - port: 80
      targetPort: 8080
# NodePort:自动或指定 nodePort
spec:
  type: NodePort
  ports: [ { port: 80, targetPort: 8080, nodePort: 30080 } ]
# LoadBalancer
spec:
  type: LoadBalancer
# ExternalName
spec:
  type: ExternalName
  externalName: api.example.com

Endpoint / EndpointSlices

Service (selector) ──endpoints-controller──▶ Endpoints/EndpointSlices
                                                (匹配 Pod IP:targetPort 列表)
kube-proxy / KPR 监听 Endpoints 变化 → 更新转发规则/eBPF map
  • EndpointSlices:K8s 1.21+ 默认,把 Endpoint 分片(每片最多 100 个),避免大 Service 的 Endpoint 对象过大导致 apiserver 压力
  • kubectl get endpointslices -l kubernetes.io/service-name=api

三种 kube-proxy 转发实现

kube-proxy 负责把 Service VIP 翻译成 backend Pod IP。三种模式:

1. iptables(默认,最老)

Service VIP 10.96.0.10:80
  → iptables nat 表 PREROUTING/KUBE-SERVICES 链
  → KUBE-SVC-XXX(统计 -m statistic 随机选一个)
  → KUBE-SEP-YYY(DNAT 到 Pod IP:targetPort)
  → conntrack 记录
  • O(n) 规则查找:Service/后端越多,链越长
  • 规则全量替换:Endpoint 一变,整条链重写,大集群更新慢
  • 同节点 backend 也走一遍 Netfilter(性能损耗)

2. IPVS(较新,推荐大集群)

IPVS 虚拟服务 10.96.0.10:80
  → 哈希表 O(1) 查找(ip_vs 内核模块)
  → 调度算法:rr / lc / dh / sh(一致性哈希)
  → DNAT + conntrack
  • 哈希查找,性能远好于 iptables 长链
  • 仍需 conntrack,且 NodePort 实现仍依赖 iptables 做入口
  • 开启:--proxy-mode=ipvs

3. eBPF / KPR(Cilium,最现代)

维度iptablesIPVSeBPF (KPR)
查找复杂度O(n) 链遍历O(1) 哈希O(1) map
conntrack必须必须可绕过(DSR)
规则爆炸严重
连接稳定性随机(易跳)可选 shMaglev 一致性哈希
同节点优化Socket LB 免协议栈
内核要求≥5.10

DNAT 与 conntrack 原理

Client Pod (10.1.1.5:34567) → Service VIP (10.96.0.10:80)
  1. kube-proxy 把目的改写成 backend (10.2.2.6:8080)  ← DNAT
  2. conntrack 记录:(10.1.1.5:34567 ↔ 10.96.0.10:80) ⇄ (10.1.1.5:34567 ↔ 10.2.2.6:8080)
  3. backend 回包源 IP 是自己的 10.2.2.6
  4. conntrack 把回包源改回 10.96.0.10(反向 NAT)
  5. Client 以为一直在跟 10.96.0.10 通信

会话亲和(sessionAffinity)

spec:
  sessionAffinity: ClientIP     # 同一客户端 IP 固定到同一 backend
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  • iptables 用 recent 模块、IPVS 用 sh 调度实现
  • KPR 用 Maglev 表的 clientIP 维度保证

externalTrafficPolicy(NodePort/LB)

行为源 IP跨节点
Cluster(默认)backend 可跨节点被 SNAT 成节点 IP允许
Local只用本节点 backend保留真实客户端 IP无本地 backend 的节点直接丢弃

Local 能保留客户端真实 IP(利于审计/限速),但要求 LB 做”只转发到健康后端所在节点”的健康检查,否则会丢包。

CoreDNS 架构

Pod 的 /etc/resolv.conf:
  nameserver 10.96.0.10        # kube-dns Service 的 ClusterIP(即 CoreDNS)
  search default.svc.cluster.local svc.cluster.local cluster.local
  options ndots:5

查询 api → 先试 api.default.svc.cluster.local → api.svc.cluster.local → ...
  → CoreDNS 查 K8s 插件维护的服务记录 → 返回 ClusterIP
  • CoreDNS 以 Deployment 运行,前面是 kube-dns Service(ClusterIP)
  • K8s 插件:CoreDNS 监听 Service/Endpoint,自动生成 svc.cluster.local 区记录
  • NodeLocalDNS(可选):在每个节点跑一个本地 DNS 缓存(169.254.20.10),避免 Pod 直接打 CoreDNS 造成 conntrack 压力与抖动
# 开启 NodeLocalDNS(DaemonSet),Pod resolv.conf 指向本地缓存
# kubectl edit configmap nodelocaldns -n kube-system
  • 自定义记录:用 CoreDNS 的 hosts / file / rewrite 插件

排障速查

现象查法
Service 不通kubectl get endpointslices -l kubernetes.io/service-name=xxx 看 backend 是否空
ClusterIP 解析不出kubectl exec -it pod -- nslookup <svc>、看 CoreDNS Pod 日志
随机丢包conntrack -S 看表满、`dmesg
NodePort 部分节点不通externalTrafficPolicy=Local + 该节点是否有 backend
DNS 抖动/延迟高开 NodeLocalDNS、看 CoreDNS HPA 是否打满

关联知识

学习时间

阶段时间备注
Service/DNS2026-07-16完成:四类型、EndpointSlices、iptables/IPVS/eBPF 对比、DNAT/conntrack、sessionAffinity、CoreDNS/NodeLocalDNS