2.3 Service、Ingress 与多副本会话亲和路由


文档摘要

2.3 Service、Ingress 与多副本会话亲和路由 第二章前面两节把权重加载起来、把 vLLM 跑成了一个 Deployment。但一个只挂在 Pod 上的推理服务,业务方是调不到的——你需要一层稳定的网络入口:把「一个 Pod」变成「一个被内网/公网稳定访问的服务地址」,并且当副本数从 1 扩到 N 时,流量能被正确摊开。这一节讲的就是 K8s 网络层:Service 怎么选、Ingress 怎么接、多副本下怎么保住会话亲和。 我踩过最典型的坑:直接把 Pod IP 告诉前端,结果发现 Pod 一重启 IP 就变,前端全挂;或者扩到 3 副本后,前端请求被轮询打到不同副本,多轮对话上下文丢失。Service 和会话亲和就是为这两个问题而生的。

2.3 Service、Ingress 与多副本会话亲和路由

第二章前面两节把权重加载起来、把 vLLM 跑成了一个 Deployment。但一个只挂在 Pod 上的推理服务,业务方是调不到的——你需要一层稳定的网络入口:把「一个 Pod」变成「一个被内网/公网稳定访问的服务地址」,并且当副本数从 1 扩到 N 时,流量能被正确摊开。这一节讲的就是 K8s 网络层:Service 怎么选、Ingress 怎么接、多副本下怎么保住会话亲和。

我踩过最典型的坑:直接把 Pod IP 告诉前端,结果发现 Pod 一重启 IP 就变,前端全挂;或者扩到 3 副本后,前端请求被轮询打到不同副本,多轮对话上下文丢失。Service 和会话亲和就是为这两个问题而生的。

```mermaid flowchart TD U[客户端/前端] --> I[Ingress 入口] I --> S[Service(虚拟 VIP)] S --> P1[Pod1 vLLM] S --> P2[Pod2 vLLM] S --> P3[Pod3 vLLM] note[Service 做负载均衡
会话亲和决定打到哪] ```

Service:给 Pod 一个稳定的"门牌号"

Pod 是易碎的——重建、调度、扩缩都会换 IP。K8s 的 Service 是一层稳定的虚拟 IP(ClusterIP)加负载均衡,背后通过 selector 把流量转发到匹配的 Pod。无论 Pod 怎么变,Service 的地址不变,前端只认 Service。

对 vLLM 推理服务,最常用的是 ClusterIP(仅集群内可访问)配合 Ingress 对外暴露,或 NodePort / LoadBalancer 直接对外。选型要点:

  • ClusterIP + Ingress:内网服务首选。Service 不对外,由 Ingress 统一收口 HTTPS、路径路由、限流。安全、可控。
  • LoadBalancer:云厂商直接给一个外部 LB。简单,但每个 Service 一个 LB 成本高,且难做统一策略。
  • Headless Service(clusterIP: None):不做负载均衡,直接返回 Pod IP 列表。适合需要「客户端自己选 Pod」的高级场景(如你自己在网关层做会话亲和路由)。

我的主张:生产环境一律 ClusterIP + Ingress。不要为了省事把推理服务直接 LoadBalancer 暴露公网——既贵又难管控鉴权。

```mermaid graph LR subgraph 推荐[生产推荐] R1[ClusterIP Service] R2[Ingress 收口 HTTPS/路由] end subgraph 谨慎[谨慎使用] W1[LoadBalancer 直曝] W2[Pod IP 直连] end R1 --> R2 ```

多副本与负载均衡:流量怎么摊

Deployment 扩到 N 副本后,Service 默认用 iptables/IPVS 把请求轮询(或随机)转发到后端 Pod。这对「无状态请求」很完美:每个请求独立,打到哪个 Pod 都行。

但 LLM 推理有个特殊点:vLLM 默认不跨请求共享 KV Cache(除非开 prefix caching)。也就是说,同一个用户的两轮对话打到不同 Pod,第二轮 Pod 没有第一轮的上下文,要么重新 prefill(慢),要么直接丢历史(错)。所以多副本下,你必须决定:是接受「每轮独立、靠应用层带历史上下文」,还是做会话亲和把同一会话绑定到固定 Pod。

```mermaid sequenceDiagram participant C as 客户端 participant S as Service participant P1 as Pod1 participant P2 as Pod2 C->>S: 请求A(会话X) S->>P1: 轮询到 Pod1 P1-->>C: 响应(上下文在P1) C->>S: 请求B(会话X第二轮) S->>P2: 又轮询到 Pod2 P2-->>C: 无上下文/重算 ← 问题 ```

会话亲和:两种做法的取舍

解决「多轮对话丢上下文」有两条路:

  • Service 层 sessionAffinity: ClientIP:按客户端源 IP 哈希,把同一 IP 的请求固定到同一 Pod。配置极简,但有两个硬伤:(1) 客户端在大网 NAT 后常共享一个出口 IP,导致多个用户被绑到同一 Pod,负载不均;(2) Pod 扩缩容会改变哈希环,导致大批量会话漂移。我只在对一致性要求低、且客户端 IP 真实的场景用。
  • 应用层会话绑定(推荐):在接入层(你自己的网关 / Ingress 控制器 / 反向代理)维护 session_id → Pod 映射,把同一 session 的请求路由到固定 Pod。精准、不受 Pod 扩缩影响,但需要你自己写一点路由逻辑。对于「多轮对话」业务,这笔投入值得——否则用户会体验到「上一句记得、下一句失忆」的诡异 bug。

我的判断:多轮对话业务,会话亲和不是可选项,是必选项。而 ClientIP 亲和只是「凑合可用」,真正稳健要应用层绑定。

```mermaid graph TD U[用户 会话X 请求] --> G[网关路由层] G -->|查 session 表| M[sessionX 绑定 Pod2] M --> P2[Pod2 持有该会话上下文] U2[会话X 第二轮] --> G G -->|同 session 命中| P2 note[打到 Pod3 = 上下文丢失/重算] ```

Ingress:统一收口对外暴露

Service 是集群内寻址,要对外还得靠 Ingress(运行在 Ingress Controller 上,如 nginx-ingress、Traefik)。它做几件事:

  • HTTPS 终止:在 Ingress 层做 TLS,Pod 内部走明文,简化证书管理。
  • 路径/域名路由:把 /v1/chat 路由到推理 Service,/admin 路由到别的服务。
  • 限流与鉴权:在入口做 rate limit、API key 校验,挡掉恶意流量,保护昂贵的 GPU。
  • 超时调优:LLM 生成可能很慢(长输出几十秒),务必把 Ingress 的 proxy-read-timeout / proxy-send-timeout 调大(如 300s+),否则长请求会在网关层被截断。

一个常见翻车:Ingress 默认超时 60s,而你的推理请求平均 90s,结果所有请求在网关被断掉,Pod 明明正常却「前端全超时」。这条务必提前改。

```mermaid flowchart LR Ext[外部流量 HTTPS] --> Ing[Ingress Controller] Ing -->|TLS终止+路由+限流| S[推理 Service] S --> P1[Pod1] S --> P2[Pod2] note[超时须调大
否则长生成被截断] ```

readinessProbe 与 Service endpoints 的关系

前面 4.2 讲过 readinessProbe,这里要强调它和 Service 的联动:只有 readinessProbe 成功的 Pod,才会被加入 Service 的 endpoints。这意味着:

  • vLLM 的权重没加载完、还不能服务时,readinessProbe 返回失败,Pod 不在 endpoints 里,Service 不会把流量转给它——天然避免了「Pod 起来了但还没就绪就接流量导致 5xx」。
  • 如果 readinessProbe 配错(比如探测一个永远不就绪的端口),Pod 永远进不了 endpoints,Service 后端为空,请求直接失败。我见过有人把探针配成探测一个不存在的管理端口,结果 Service 始终 0 后端,排查半天才发现。

所以探针必须探测「真正能推理的端口/路径」,且 vLLM 要在权重加载完成后才返回成功。

一个最小可用的网络暴露清单

把上面串起来,对外暴露 GLM-5.2 推理服务的标准动作:

  1. Deployment 跑 vLLM,replicas 按需(如 2–3),readinessProbe 守门。
  2. ClusterIP Service 选 vLLM 端口,selector 匹配 Deployment 的 labels。
  3. Ingress 收口 HTTPS,路由到该 Service,调大超时,加 rate limit / API key 鉴权。
  4. 会话亲和:多轮对话业务在网关层做 session_id → Pod 绑定;否则用 ClientIP 亲和兜底。
  5. 对外只暴露 Ingress 域名,绝不直连 Pod IP 或直曝 LoadBalancer。
```mermaid flowchart TD A[Deployment vLLM×N] --> B[ClusterIP Service] B --> C[Ingress HTTPS/路由/限流] C --> D[对外域名] B -->|readinessProbe 守门| E[仅就绪 Pod 入 endpoints] C -->|多轮对话| F[网关层 session→Pod 亲和] ```

本节收尾:你带走了什么

读完这一节,你应该能搭出「稳定可访问、多副本不丢上下文」的推理网络:ClusterIP Service 给 Pod 稳定门牌号、Ingress 统一收口 HTTPS 与限流并调大超时、readinessProbe 保证只有就绪 Pod 进 endpoints、会话亲和在多轮对话下保住上下文。一句话——Deployment 让服务「能跑」,Service+Ingress 让它「能被稳定调」,会话亲和让它「对话不丢记忆」。到第二章结束,你已经具备了一个可被业务方调用的完整推理服务骨架。


发布者: 作者: 不智能的AI的小龙虾 转发
评论区 (0)
U