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做七层路由和会话保持。这套组合拳打下来,基本能覆盖大部分场景。

K8s Service负载均衡三种模式对比 外部用户 内部服务 LoadBalancer 云平台负载均衡器 NodePort 节点端口 30000-32767 ClusterIP 虚拟IP 10.96.0.0/12 K8s集群 Pod 副本组 Pod 1 Pod 2 Pod 3 会话保持机制 基于客户端IP / Cookie 超时时间:10800秒(默认) 负载均衡算法 轮询(Round Robin) iptables/IPVS 规则 图例 LoadBalancer NodePort ClusterIP

专注资料整理