4. K8s Service负载均衡原理:ClusterIP、NodePort、LoadBalancer三种模式对比
聊到K8s Service的负载均衡,我得先说说自己的经历。刚接触K8s那会儿,我总觉得Service就是个“黑盒子”——把流量扔进去,它就能自动分发到Pod上。后来踩了几次坑才明白,这个“黑盒子”背后藏着不少门道。
说白了,Service就是一组Pod的访问入口。它解决了两个核心问题:Pod的IP是动态变化的,你没法写死;多个Pod需要统一入口做负载分发。K8s提供了三种模式,各有各的适用场景。
4.1 ClusterIP:集群内部的“隐形桥梁”
ClusterIP是默认模式,也是最常用的。它只在集群内部暴露一个虚拟IP,外部无法直接访问。
我举个例子:你部署了一个vLLM推理服务,有3个Pod。ClusterIP会给它们分配一个虚拟IP,比如10.96.0.1。集群里的其他服务(比如你的前端应用)通过这个IP就能访问vLLM了。
- 只能在集群内部访问
- 自动实现轮询负载均衡
- Pod扩缩容时自动更新端点
嗯,这里要注意一点:ClusterIP的负载均衡是四层的,基于TCP/UDP。它不关心HTTP请求的内容,只管把连接分发到不同的Pod上。
4.2 NodePort:打通内外网的“门户”
NodePort在ClusterIP基础上,在每个Node节点上开一个端口(范围30000-32767)。外部流量通过“NodeIP:NodePort”就能访问到Service。
我记得有一次给客户部署推理服务,他们需要从外部压测工具直接访问。ClusterIP搞不定,NodePort正好派上用场。
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
type: NodePort
ports:
- port: 8000 # Service内部端口
targetPort: 8000 # Pod端口
nodePort: 30080 # 节点端口(可选,不指定则随机分配)
selector:
app: vllm
我曾经遇到过一个问题:NodePort模式下,如果客户端直接访问某个Node的IP,而这个Node上没有运行对应的Pod,流量会被转发到其他Node上的Pod。这会导致额外的网络跳转,增加延迟。所以生产环境建议配合外部负载均衡器使用。
4.3 LoadBalancer:云原生的“高速公路”
LoadBalancer模式是NodePort的“升级版”。它在NodePort的基础上,自动调用云平台(AWS、GCP、阿里云等)的负载均衡器服务,对外提供一个公网IP。
说白了,你只需要声明type: LoadBalancer,云平台就会自动帮你创建好外部负载均衡器,并把流量导入到集群内部。
| 模式 | 访问范围 | 适用场景 | 缺点 |
|---|---|---|---|
| ClusterIP | 集群内部 | 微服务间调用 | 外部无法访问 |
| NodePort | 集群外部(通过节点IP) | 测试环境、小规模外部访问 | 端口范围受限、节点故障影响 |
| LoadBalancer | 公网 | 生产环境、对外服务 | 依赖云平台、成本较高 |
4.4 Service的会话保持机制
聊完三种模式,咱们再深入看看会话保持。你想想看,vLLM推理服务通常是有状态的——用户发送一个长文本生成请求,可能涉及多次交互。如果每次请求都落到不同的Pod上,上下文就丢了。
K8s Service默认的负载均衡是轮询,不保证会话保持。那怎么办?
K8s提供了两种会话保持机制:
4.4.1 基于客户端IP的会话保持
设置service.spec.sessionAffinity为ClientIP,K8s就会把同一个客户端IP的请求始终转发到同一个Pod上。
apiVersion: v1
kind: Service
metadata:
name: vllm-session
spec:
selector:
app: vllm
ports:
- port: 8000
targetPort: 8000
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3小时超时
我建议超时时间不要设得太长。曾经有个项目设了24小时,结果某个Pod挂了,所有绑定到这个Pod的客户端都受影响。3小时是个比较合理的值,既保证了会话连续性,又能在Pod故障时及时切换。
4.4.2 基于Cookie的会话保持
如果客户端IP会变化(比如移动网络),基于IP的会话保持就不靠谱了。这时候可以用Ingress Controller配合Cookie实现更精细的会话保持。
我个人习惯用Nginx Ingress的sticky session功能:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vllm-ingress
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "vllm-session"
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
spec:
rules:
- host: vllm.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vllm-service
port:
number: 8000
4.5 三种模式的选型建议
说了这么多,到底该怎么选?我总结一下:
- 微服务间调用:用ClusterIP,简单高效
- 测试环境或小规模外部访问:NodePort够用,但注意端口范围
- 生产环境对外服务:LoadBalancer + Ingress,灵活且可靠
- 需要会话保持:优先考虑应用层方案(如Redis Session),实在不行再用K8s的会话保持
最后说一句:没有银弹。每种模式都有它的适用场景,关键是要理解你的业务需求。我在vLLM部署中,通常会用ClusterIP做内部服务发现,配合LoadBalancer对外暴露,再用Ingress做七层路由和会话保持。这套组合拳打下来,基本能覆盖大部分场景。