11 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-07-07 14:47 |
K8s Service 与 kube-proxy 虚拟端口
概述
Kubernetes Service 通过虚拟 IP(ClusterIP)和 kube-proxy 的流量转发机制,为一组动态变化的 Pod 提供稳定的网络访问入口——这是理解集群内服务间通信的关键。
核心概念
ClusterIP 的本质:一个"不存在"的 IP
当创建一个 ClusterIP 类型的 Service 时,K8s 做了两件事:
- 在 etcd 中分配一个虚拟 IP(来自
service-cidr,如10.43.0.0/16),这个 IP 不绑定在任何网卡上——你在任何节点执行ip addr都看不到它 - kube-proxy 在每个节点上写入转发规则,将发往该虚拟 IP + 端口的流量透明地分发到后端 Pod
[!info] 虚拟端口 vs 真实端口 Service 的
port(如80)是虚拟端口——它不存在于任何进程监听列表中。真正监听的是 Pod 内的targetPort(如8080)。kube-proxy 负责在中间做"端口翻译"。
graph LR
C[Client Pod] -->|"TCP to 10.43.0.100:80"| VIP["ClusterIP<br/>10.43.0.100:80"]
VIP -->|"iptables/IPVS DNAT"| P1["Pod-A 10.42.1.5:8080"]
VIP -->|"iptables/IPVS DNAT"| P2["Pod-B 10.42.2.8:8080"]
VIP -->|"iptables/IPVS DNAT"| P3["Pod-C 10.42.3.2:8080"]
kube-proxy 三种模式对比
kube-proxy 是运行在每个节点上的网络代理,它 watch kube-apiserver 上 Service/Endpoints 的变化,然后在本机维护转发规则。
| 维度 | iptables | IPVS | nftables(K8s 1.29+ alpha) |
|---|---|---|---|
| 实现方式 | 内核 iptables 链规则 | 内核 IPVS 模块(LVS) | nftables(iptables 的继任者) |
| 匹配复杂度 | O(n) 链表遍历 | O(1) 哈希查找 | O(n) → 未来可优化 |
| 大规模性能 | Service 多时规则膨胀,延迟线性增长 | 万级 Service 仍保持稳定 | 介于两者之间,持续演进中 |
| 负载均衡算法 | 随机 / 概率(nth) |
rr / wrr / lc / sh 等丰富算法 | 随机 / 概率 |
| 连接跟踪 | conntrack 表 | IPVS 自己的连接表 | conntrack |
| RKE2 默认 | ✅ 是(传统默认) | 需手动启用 | 实验阶段 |
启发问题:为什么 IPVS 用哈希表而非链表?——iptables 对每条规则做线性匹配,当 Service 数量上千时,一个新连接可能要遍历数千条规则才能命中;IPVS 在内核中维护哈希表,一次查找直接定位,这是本质区别。
一条请求的完整路径:Pod → Service → 目标 Pod
sequenceDiagram
participant Client as Client Pod (10.42.1.3)
participant KProxy as 本机 kube-proxy
participant Kernel as 内核网络栈
participant Target as Target Pod (10.42.2.8:8080)
Client->>Kernel: ① connect(10.43.0.100:80)
Note over Kernel: CNI 插件已配置路由<br/>10.43.0.0/16 → kube-proxy
KProxy-->>Kernel: ② iptables/IPVS 规则持续 watch
Note over Kernel: ③ DNAT: dst 10.43.0.100:80 → 10.42.2.8:8080
Note over Kernel: ④ 如果目标 Pod 在其他节点:<br/>通过 CNI overlay 或 host-gw 封装转发
Kernel->>Target: ⑤ 10.42.2.8:8080
Target-->>Client: ⑥ 响应原路返回(reverse NAT)
关键步骤拆解:
- 客户端发出请求:Pod 内的应用调用
connect(10.43.0.100:80),对应用完全透明 - kube-proxy 规则生效:通过 iptables DNAT 或 IPVS 将目标地址改写为真实 Pod IP + 端口
- 跨节点转发:如果目标 Pod 在另一台机器上,由 CNI 插件(Flannel VXLAN / Calico BGP)封装后送达
- 响应返回:conntrack 记录了 NAT 映射关系,反向包自动做 SNAT 回到 ClusterIP
Service 类型扩展
| 类型 | 流量来源 | kube-proxy 行为 | 典型场景 |
|---|---|---|---|
| ClusterIP(默认) | 集群内 Pod | 仅在本机 iptables/IPVS 添加规则 | 服务间调用 |
| NodePort | 外部 → 任意节点 IP:Port | 在 ClusterIP 基础上额外开放 30000-32767 端口 | 开发测试、快速验证 |
| LoadBalancer | 外部 → 云厂商 LB → NodePort → Pod | 依赖云 controller 创建外部 LB | 云上生产环境 |
| ExternalName | 集群内 DNS 查询 | kube-proxy 不介入,CoreDNS 返回 CNAME | 引用集群外服务(如 RDS) |
[!tip] NodePort 的"隐藏代价" NodePort 会在集群所有节点上开放端口(即使是没有运行该 Service Pod 的节点)。这意味着外部流量可能先到达一个"空"节点,再经过一次内部转发才能到达目标 Pod——多了一跳延迟。用
externalTrafficPolicy: Local可避免这个问题(但要注意流量不均)。
iptables vs IPVS 深入
iptables 的规则膨胀问题
每个 Service 会产生 N 条 iptables 规则(N = 后端 Pod 数):
# 查看一个 3-Pod Service 生成的 iptables 规则
iptables -t nat -L KUBE-SERVICES -n | grep my-service
# KUBE-SVC-XXXX tcp -- 0.0.0.0/0 10.43.0.100 tcp dpt:80
iptables -t nat -L KUBE-SVC-XXXX -n
# KUBE-SEP-AAA statistic mode random probability 0.33333 → Pod-A
# KUBE-SEP-BBB statistic mode random probability 0.50000 → Pod-B
# KUBE-SEP-CCC /* default */ → Pod-C
[!warning] 1000 个 Service × 3 Pod = 3000+ 规则 每次新建连接,内核从第一条开始逐条匹配。实测表明:当规则超过 5000 条时,单次连接的 DNAT 延迟会从微秒级劣化到毫秒级,对在线服务影响显著。
IPVS 的优势
# 查看 IPVS 虚拟服务
ipvsadm -Ln
# TCP 10.43.0.100:80 rr
# → 10.42.1.5:8080 Masq 1 0
# → 10.42.2.8:8080 Masq 1 0
# → 10.42.3.2:8080 Masq 1 0
| 场景 | iptables | IPVS |
|---|---|---|
| 100 Service | ~300 条规则,性能无感知差异 | ~100 个 hash entry,无感知差异 |
| 1000 Service | ~3000+ 条规则,连接建立延迟可见劣化 | ~1000 entry,几乎无变化 |
| 10000 Service | 规则更新耗时数秒,iptables-restore 阻塞 |
独立于规则数量,毫秒级更新 |
| 负载均衡策略 | 随机/概率 | 支持 rr / wrr / lc / sh / sed / nq |
[!tip] 如何在 RKE2 启用 IPVS 在
/etc/rancher/rke2/config.yaml中配置:kube-proxy-arg: - "proxy-mode=ipvs" - "ipvs-scheduler=rr" # 轮询调度需确保节点内核已加载
ip_vs模块:modprobe ip_vs && lsmod | grep ip_vs
RKE2 中的实践
xinfra 基于 RKE2 集群,以下是与 Service / kube-proxy 相关的关键配置:
graph TB
subgraph "RKE2 集群(xinfra)"
subgraph "网络栈"
Canal["Canal (Calico + Flannel)"]
KProxy["kube-proxy (iptables 模式)"]
CoreDNS["CoreDNS"]
end
subgraph "Service 层"
SVC1["ClusterIP Service"]
SVC2["NodePort Service"]
end
Canal -->|"Pod-to-Pod 网络"| KProxy
KProxy -->|"Service 转发"| SVC1
KProxy -->|"NodePort 转发"| SVC2
CoreDNS -->|"DNS 解析"| SVC1
end
默认配置要点:
| 配置项 | RKE2 默认值 | 说明 |
|---|---|---|
cluster-cidr |
10.42.0.0/16 |
Pod IP 地址范围 |
service-cidr |
10.43.0.0/16 |
ClusterIP 地址范围 |
| kube-proxy 模式 | iptables |
小规模集群足够 |
| CNI 插件 | Canal(Calico + Flannel) | 提供 Pod 网络 + NetworkPolicy |
| CoreDNS | 内置 | Service DNS 自动解析 |
[!info] xinfra 七机房部署 xinfra 管理七机房的 RKE2 集群。每个集群独立的
service-cidr,跨集群服务访问不通过 ClusterIP,而是通过 Wayne 多集群管理平台统一调度——理解这点很重要:ClusterIP 的作用域是单集群内。
常见陷阱与最佳实践
1. Session Affinity(会话保持)
默认情况下,kube-proxy 对每个连接做随机/轮询分发。如果你的应用有状态(如 Session 存储在内存),需要开启:
spec:
sessionAffinity: ClientIP # 基于客户端 IP 做会话保持
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 小时超时
[!warning] Session Affinity 不是银弹 如果后端 Pod 重启,连接会中断。有状态应用更推荐用 StatefulSet + Headless Service 做稳定的网络标识。
2. Headless Service(无头 Service)
当你需要直接获取 Pod IP 而不经过负载均衡时,使用 Headless Service:
spec:
clusterIP: None # 关键:不分配 ClusterIP
ports:
- port: 80
DNS 查询返回的是所有 Pod 的 IP 列表,而非单个虚拟 IP。适用于:
- StatefulSet(如 MySQL 主从、Redis Cluster)
- 客户端自己实现负载均衡的场景
3. externalTrafficPolicy 的选择
spec:
externalTrafficPolicy: Cluster # 默认:跨节点转发,可能多一跳
# externalTrafficPolicy: Local # 只转发到本节点 Pod,保留源 IP
| 策略 | 优点 | 缺点 |
|---|---|---|
Cluster(默认) |
流量均匀分布到所有 Pod | 多一跳延迟,丢失源 IP |
Local |
保留源 IP,低延迟 | 流量分布不均(取决于 LB 分发) |
4. Service 无法访问排查清单
当 curl <service-name>:<port> 超时或拒绝连接时:
flowchart TD
A["Service 不通"] --> B{"kubectl get endpoints<br/>有 IP 吗?"}
B -->|无| C["检查 selector 是否匹配 Pod label<br/>检查 Pod 是否 Ready"]
B -->|有| D{"Pod 内 curl targetPort<br/>通吗?"}
D -->|不通| E["应用未监听 / 端口错误 / readinessProbe 失败"]
D -->|通| F{"CoreDNS 解析正确吗?<br/>nslookup service-name"}
F -->|解析失败| G["检查 CoreDNS Pod 状态<br/>检查 resolv.conf"]
F -->|解析成功| H["检查 kube-proxy 日志<br/>检查 iptables/IPVS 规则是否写入"]
5. 避免的常见错误
- selector typo:Service 的
spec.selector与 Pod 的metadata.labels不匹配,导致 Endpoints 为空 - 端口不匹配:Service 的
targetPort与容器实际监听端口不一致 - NetworkPolicy 阻断:Calico NetworkPolicy 可能阻止了 Service 到 Pod 的流量
延伸阅读
关联笔记
- technical/xinfra-preview/k8s-rke2-fundamentals — K8s 基础概念,包含 Service 类型概览和 Ingress 示例
- technical/xinfra-preview — xinfra 预习总览,本笔记的父文档