文章
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,最现代)
| 维度 | iptables | IPVS | eBPF (KPR) |
|---|
| 查找复杂度 | O(n) 链遍历 | O(1) 哈希 | O(1) map |
| conntrack | 必须 | 必须 | 可绕过(DSR) |
| 规则爆炸 | 严重 | 无 | 无 |
| 连接稳定性 | 随机(易跳) | 可选 sh | Maglev 一致性哈希 |
| 同节点优化 | 无 | 无 | 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/DNS | 2026-07-16 | 完成:四类型、EndpointSlices、iptables/IPVS/eBPF 对比、DNAT/conntrack、sessionAffinity、CoreDNS/NodeLocalDNS |