深色模式
Service 原理:ClusterIP / NodePort / LoadBalancer
摘要:本文解释 Kubernetes Service 为什么存在、ClusterIP/NodePort/LoadBalancer 三种类型的内部机制与流量路径,以及 kube-proxy(iptables/ipvs/nftables/eBPF)如何把虚拟 IP 翻译成 Pod IP。覆盖版本:Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+(
EndpointSlice已 GA;nftables模式 v1.33 GA;ipvs在 v1.35 弃用)。 - 工具:
kubectl、ip、iptables/ipvsadm(节点上)。 - 前提:了解 Pod 生命周期与标签选择器。
背景与问题:为什么需要 Service
Pod IP 是易变的:每次重启、调度、滚动更新都会换 IP。如果前端硬写后端 IP,一次重启就全断。Service 解决的问题是:
- 提供稳定的虚拟 IP(ClusterIP) 与 DNS 名称;
- 通过标签选择器动态绑定后端 Pod;
- 在健康后端之间做负载均衡。
后端 Pod 列表由 EndpointSlice(v1.21 起取代单一 Endpoints 对象)维护。大 Service(>1000 端点)必须用 EndpointSlice,因为它把端点分片成约 100 个一组,单 Pod 变化只更新一个分片,避免 etcd 与全节点扩散风暴。
三种 Service 类型
三种类型并非彼此独立,而是层层叠加:
ClusterIP:默认类型,仅集群内部可达。NodePort:在 ClusterIP 基础上,在每个节点开一个固定端口(默认范围 30000–32767)。LoadBalancer:在 NodePort + ClusterIP 基础上,向云厂商(或 MetalLB)申请外部负载均衡器,把流量转发到节点的 NodePort。
选型口诀
内部通信用 ClusterIP;没有云 LB 又要从外部访问(开发/自建)用 NodePort;生产对外暴露 HTTP 服务,优先走 Ingress + 一个 LoadBalancer(而不是为每个服务各建一个 LB,见 north-south.md)。
ClusterIP 原理
ClusterIP 是一个虚拟 IP:没有任何网络接口拥有它,也不响应 ICMP(所以 ping 不通是正常行为)。kube-proxy 监听 Service/EndpointSlice 变化,在节点上写 iptables/ipvs/nftables 规则,把发往 ClusterIP 的包做 DNAT(目的地址改写为某个后端 Pod IP)。
yaml
apiVersion: v1
kind: Service
metadata:
name: web
namespace: default
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
protocol: TCP
port: 80 # Service 暴露的端口
targetPort: 8080 # 后端 Pod 容器监听端口1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
NodePort 原理
NodePort 是 ClusterIP 的扩展:Kubernetes 从 30000–32767 分配一个端口,kube-proxy 在每个节点的该端口上监听。任意节点的 <NodeIP>:<NodePort> 都会被转发到 ClusterIP,再走普通 ClusterIP 负载均衡。
注意
- 默认端口范围有限(仅约 2768 个),且全集群同一端口只能被一个 Service 占用。
- NodePort 会做 SNAT:从节点外进来的流量,源 IP 可能被改写,后端 Pod 看到的是节点 IP 而非真实客户端 IP(除非设置
externalTrafficPolicy: Local,见north-south.md)。
yaml
apiVersion: v1
kind: Service
metadata:
name: web-np
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 可选;不写则由集群自动分配1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
LoadBalancer 原理
LoadBalancer 在云环境(AWS/GCP/Azure)由 cloud-controller-manager 创建外部 LB,把流量送到节点的 NodePort;在裸金属由 MetalLB 接管。kube-proxy 只负责“节点内”这一段,对外部 LB 本身无感知。
yaml
apiVersion: v1
kind: Service
metadata:
name: web-lb
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
# externalTrafficPolicy: Local # 保留客户端源 IP,但分布可能不均1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
生产危险
裸金属集群上直接创建 LoadBalancer 时,若无 MetalLB / 云控制器,Service 会一直处于 Pending(EXTERNAL-IP 为 <pending>)。不要反复 apply,先确认 LB 实现是否已部署。
kube-proxy 模式与性能
kube-proxy 的转发实现决定大规模下的行为:
| 模式 | 机制 | 规模表现 | 状态 |
|---|---|---|---|
iptables | 规则链,O(n) 查找 | 规则多时更新慢(数千 Service 时单条新增可达百毫秒级) | 默认 |
ipvs | 内核哈希表,O(1) | 适合大规模 | v1.35 弃用 [版本相关] |
nftables | nft 表 | 优于 iptables,官方推荐 | v1.33 GA |
eBPF(Cilium 等) | 内核 BPF map | 最快、可观测 | [厂商特定] |
关于“虚拟 IP 不可达”的排查
ClusterIP 不响应 ping 是设计如此。验证连通性请用 TCP(如 curl 或 nc),而不是 ping。若 TCP 也不通,按 troubleshooting.md 检查 kube-proxy 状态与本节点的转发规则。
操作步骤
步骤 1:验证环境与 kube-proxy 模式
bash
kubectl version --short
# 查看 kube-proxy 实际模式(需节点权限)
kubectl -n kube-system get configmap kube-proxy -o yaml | grep mode1
2
3
2
3
步骤 2:创建并验证 Service
bash
kubectl apply -f web.yaml
kubectl get svc web
kubectl get endpointslices -l k8s-app=web # 查看后端分片
# 从集群内测试
kubectl run curl-test --rm -it --image=curlimages/curl -- sh \
-c 'curl -s http://web.default.svc:80/'1
2
3
4
5
6
2
3
4
5
6
回滚与清理
bash
kubectl delete -f web.yaml
# 或按名删除
kubectl delete svc web1
2
3
2
3
注意
删除 Service 不会删除后端 Pod。删除 LoadBalancer 时,云 LB 通常会被 cloud-controller 异步回收;若残留,检查 cloud-controller 日志。
故障排查
- EndpointSlice 为空:说明选择器不匹配或后端 Pod 未 Ready。用
kubectl describe svc web看Endpoints与事件。 - 新连接间歇性失败:可能是某节点 kube-proxy 挂了,该节点上依赖 Service 的访问会失败,但 Pod 直连仍可用(CNI 独立)。
- 端口冲突:
NodePort报provided port is already allocated,换端口或改用自动分配。
安全与合规
- 默认 Service 是“全集群可达”的,配合 NetworkPolicy 才能做隔离(见
networkpolicy.md)。 - 对外的
LoadBalancer可用loadBalancerSourceRanges限制来源 CIDR(云厂商实现为 LB 防火墙规则)。
常见坑
- 混淆
port/targetPort/nodePort:三者分别作用于 Service 端口、Pod 容器端口、节点端口。 - 以为 ClusterIP 能 ping:不能,属正常。
- 在大规模集群仍用默认
iptables:规则膨胀后更新延迟明显,应评估nftables或 eBPF。
替代方案与权衡
- Ingress / Gateway API:HTTP 七层路由,比一堆 NodePort/LB 更省成本、更灵活(见
ingress.md、north-south.md)。 - Headless Service:不需要虚拟 IP、需要直接寻址每个 Pod 时(如 StatefulSet),见
headless-service.md。