3. Kubernetes Service 与流量分发:ClusterIP / NodePort / LoadBalancer 工作原理、iptables 与 IPVS 模式对比
Kubernetes Service 是集群内部流量分发与外部访问的核心抽象层。它为一组动态变化的 Pod 提供稳定的虚拟 IP(ClusterIP)和 DNS 名称,并承担客户端请求的负载均衡。理解 Service 的四种类型(ClusterIP、NodePort、LoadBalancer、ExternalName)以及后端代理模式(iptables 与 IPVS),是排查蓝绿发布过程中流量异常、连接中断、灰度比例失效等问题的前提。
3.1 Service 类型与流量分发路径
Kubernetes 提供四种 Service 类型,分别对应不同的网络暴露层级和流量入口。下表总结了它们的核心差异与典型使用场景:
| Service 类型 | 虚拟 IP 范围 | 外部可达性 | 典型场景 | 蓝绿发布中的关键作用 |
|---|---|---|---|---|
| ClusterIP | 集群内部虚拟 IP(默认) | 仅集群内部可达 | 微服务间内部 RPC、API 调用 | 蓝绿两组 Pod 通过同一个 ClusterIP 实现内部流量切换 |
| NodePort | 每个节点上的静态端口(30000-32767) | 通过 <NodeIP>:<NodePort> 从集群外部访问 |
开发测试环境、小型集群对外暴露 | 蓝绿发布期间可通过不同 NodePort 临时验证新版本 |
| LoadBalancer | 云厂商负载均衡器分配的公共 IP | 通过负载均衡器公网 IP 访问 | 生产环境对外暴露服务(AWS ELB、GCP LB、阿里云 SLB) | 蓝绿发布时通过负载均衡器权重或后端组切换实现平滑迁移 |
| ExternalName | 无虚拟 IP,返回 CNAME 记录 | 通过 DNS 解析到外部域名 | 将集群外服务映射为集群内 Service 名称 | 蓝绿发布中不直接参与流量切换,但可用于外部依赖的优雅降级 |
selector 标签是否同时匹配了蓝绿两组 Pod。若标签重叠,流量会按比例分发到两组 Pod,导致“部分切换”现象。
3.2 ClusterIP 与 NodePort 工作原理
ClusterIP 是 Kubernetes Service 的默认类型。kube-proxy 组件在集群每个节点上监听 API Server 的 Service 与 Endpoint 变化,并维护一套转发规则。当客户端 Pod 通过 ClusterIP 发起请求时,数据包经过以下路径:
- DNS 解析: 客户端通过 CoreDNS 将 Service 名称解析为 ClusterIP。
- 节点内核转发: 数据包到达节点网络栈,匹配 kube-proxy 写入的 iptables 或 IPVS 规则。
- 负载均衡选择: 规则将请求 DNAT(目标网络地址转换)为选中的后端 Pod IP 与端口。
- Pod 响应: 响应包经过 conntrack 记录,自动还原源地址为 ClusterIP,客户端无感知。
NodePort 在 ClusterIP 基础上,额外在每个节点的指定端口上监听入站流量。数据包到达节点 <NodePort> 后,内核将其 DNAT 到 ClusterIP,后续路径与 ClusterIP 一致。
- ClusterIP 范围: 默认
10.96.0.0/12,可通过--service-cluster-ip-range配置。 - NodePort 范围: 默认
30000-32767,可通过--service-node-port-range配置。 - 端口冲突: 多个 Service 不能共享同一个 NodePort,但不同节点上的相同 NodePort 可以指向不同 Service。
3.3 LoadBalancer 工作原理与云厂商集成
LoadBalancer 类型是 NodePort 的云原生扩展。当 Service 类型设置为 LoadBalancer 时,Kubernetes 会调用云厂商的 API 创建一个外部负载均衡器(如 AWS NLB、GCP TCP/UDP LB、阿里云 SLB),并将所有节点的 NodePort 注册为后端目标。
流量路径如下:
- 外部请求 到达云负载均衡器的公网 IP。
- 负载均衡器 将请求转发到集群任意节点的
<NodePort>。 - 节点内核 通过 iptables/IPVS 将请求 DNAT 到目标 Pod。
3.4 iptables 与 IPVS 模式对比
kube-proxy 支持两种代理模式:iptables(默认)和 IPVS(推荐用于大规模集群)。两者都工作在节点的网络栈中,但实现原理和性能特征差异显著。
下图展示了两种模式下数据包从客户端 Pod 到目标 Pod 的转发路径对比:
下表从多个维度对比两种模式,帮助你在蓝绿发布场景中选择合适的代理模式:
| 对比维度 | iptables 模式 | IPVS 模式 |
|---|---|---|
| 数据面实现 | Linux 内核 Netfilter 框架,规则链线性匹配 | LVS(Linux Virtual Server)内核模块,哈希表查找 |
| 规则数量影响 | 规则数增加时性能线性下降(O(n)) | 规则数增加时性能基本恒定(O(1)) |
| 调度算法 | 仅支持随机(random) | 支持 rr、wrr、lc、wlc、sh、dh 等多种算法 |
| 连接跟踪 | 依赖 conntrack,高并发下可能丢包 | 支持更高效的连接跟踪,减少 conntrack 压力 |
| 大规模集群(>1000 Service) | 规则更新延迟高,CPU 开销大 | 规则更新快,CPU 开销低 |
| 蓝绿发布场景优势 | 简单稳定,适合中小规模集群 | 支持权重调度,可精确控制蓝绿流量比例 |
| 启用方式 | 默认模式,无需额外配置 | kube-proxy 启动参数 --proxy-mode=ipvs |
- 中小规模集群(< 500 Service): 使用 iptables 模式,简单可靠,排查时可直接查看 iptables 规则链。
- 大规模集群(> 500 Service)或需要精细流量控制: 切换为 IPVS 模式,利用加权轮询(wrr)算法实现蓝绿流量按比例分发。
- 切换 IPVS 后验证: 执行
ipvsadm -Ln查看虚拟服务与后端 Pod 的映射关系,确认调度算法生效。
3.5 蓝绿发布中的 Service 流量切换策略
在蓝绿发布过程中,Service 的 selector 标签是流量切换的“开关”。以下两种策略分别对应不同的切换粒度:
策略一:标签全量切换(粗暴切换)
- 蓝组 Pod 标签:
app: myapp, version: blue - 绿组 Pod 标签:
app: myapp, version: green - Service selector:初始为
app: myapp, version: blue,切换时更新为app: myapp, version: green - 特点: 瞬间切换,所有流量立即指向新版本,适合无状态服务。
策略二:标签交集切换(灰度切换)
- 蓝组 Pod 标签:
app: myapp, version: blue - 绿组 Pod 标签:
app: myapp, version: green - Service selector:
app: myapp(同时匹配蓝绿两组) - 通过调整两组 Pod 的副本数比例(如蓝 10 副本、绿 1 副本)实现 10:1 的流量分发。
- 特点: 流量按 Pod 数量比例分发,适合需要逐步验证新版本的场景。
readinessProbe 失败,导致 Endpoint 控制器未将其加入 Service 的后端列表。执行 kubectl get endpoints <service-name> 确认后端 Pod IP 列表,发现缺少新版本 Pod 的 IP。
3.6 故障排查常用命令速查
以下命令在蓝绿发布网络故障排查中高频使用,建议熟记:
| 排查目标 | 命令 | 预期输出 |
|---|---|---|
| 查看 Service 详情 | kubectl describe svc <service-name> |
ClusterIP、Port、Selector、Endpoints 列表 |
| 查看 Endpoint 列表 | kubectl get endpoints <service-name> |
后端 Pod IP 与端口列表 |
| 查看 Pod 标签 | kubectl get pods --show-labels |
Pod 名称、状态、标签键值对 |
| 查看 iptables 规则 | iptables -t nat -L KUBE-SERVICES -n |
Service 对应的 DNAT 规则链 |
| 查看 IPVS 虚拟服务 | ipvsadm -Ln |
虚拟 IP、端口、调度算法、后端 Pod 权重 |
| 查看 kube-proxy 日志 | kubectl logs -n kube-system <kube-proxy-pod> |
规则同步错误、Service 变更事件 |
kubectl get endpoints <service-name> 确认后端 Pod IP 是否包含新版本 Pod。如果新版本 Pod IP 不在列表中,说明 Service 的 selector 未匹配或 Pod 未通过 readinessProbe。