使用Nginx Ingress代理Websocket流量

206 阅读5分钟

本文完成Spring Websocket Chatroom遗留的问题,即使用网关做负载均衡。

使用Ingress Nginx完成基本的负载均衡

1ea3a5a8-4e78-4564-9c9a-854f0b33a13c.png

Nginx Ingress Controller 在较新版本(本文用的v1.12.1)中,默认已经内置了对 WebSocket 的基本支持:

 # Pass the extracted client certificate to the backend                           
 # Allow websocket connections                                                    
 proxy_set_header                        Upgrade           $http_upgrade;         
 proxy_set_header                        Connection        $connection_upgrade;

因此之前的一些烦人的注解不再需要了:

nginx.ingress.kubernetes.io/configuration-snippet: |
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";

以下注解是推荐配置的:

Annotation作用为什么推荐设置?示例
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "3600"控制 Nginx 与后端保持长连接的超时时间避免过早断开空闲连接"3600"(s)
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "100"控制 Nginx 与后端保持的最大空闲连接数避免频繁重建连接"100"
nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"Nginx 等待后端数据的超时(长连接必备)避免 WebSocket 因长时间无数据被断开"1200"(s)
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"Nginx 等待转发客户端数据的超时避免客户端发数据后后端未及时接收导致断开"1200"

最终的Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernets.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "1200"
    nginx.ingress.kubernetes.io/upstream-keepalive-connections: "100"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "1200"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "1200"
  name: chat
  namespace: chat
spec:
  ingressClassName: nginx
  rules:
  - host: "homedev"
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: chat
            port:
              number: 8000

Nginx Ingress Controller 是怎么工作的

Kubernetes 用Ingress来定义从集群外部进来的流量(主要是HTTP 和 HTTPS 流量)该怎么转发给后端的服务。它允许通过声明式的方式,配置域名(Host)、路径(Path)、后端服务(Service)、TLS 证书重定向、重写等规则。但Ingress 本身并不提供任何网络功能,它仅仅是一个规则定义。Ingress Controller负责监听、解析Ingress,并将规则应用到实际的代理软件(如 Nginx、Envoy 等)。

Nginx Ingress Controller 是对 nginx 的一次包装。它的底层能力是在以 nginx 为基础的。它的工作过程:

  1. 通过 Kubernetes 的 Informer 机制实时监听集群中 Ingress、Service、Endpoints 的创建 / 更新 / 删除事件(如用户新增 Ingress 规则、后端 Pod 扩容 / 缩容)
  2. Controller 读取 Ingress 资源中的核心配置,包括:Host 域名:如 example.com(匹配外部请求的 Host 头),Path 路径:如 /api(匹配请求的 URL 路径);Backend 后端:关联的 Service 名称和端口(如 service-user:8080)
  3. Controller 根据 Ingress 关联的 Service,查询对应的 Endpoints 资源,拿到健康的后端 Pod IP:Port 列表
  4. Controller 将上述规则转化为nginx.conf配置。比如将Pod IP:Port列表写入upstream(最新版本的不会直接写在nginx.conf文件中,而是用了lua)
  5. Nginx 对 upstream 集群中的 Pod 执行预设的负载均衡策略,默认策略为 轮询(round-robin)

WebSocket 负载均衡问题

WebSocket 的长连接特性可能导致 nginx 负载均衡失效:

  • 连接绑定:一旦 WebSocket 握手完成,客户端会与特定后端服务器保持长连接,所有后续数据都通过该连接传输,Nginx 无法将同一客户端的 WebSocket 流量分配到其他服务器。

  • 负载倾斜:如果部分客户端的 WebSocket 连接持续时间很长(如在线用户),而新客户端不断连接,会导致早期服务器累积大量长连接,新服务器连接数较少,出现 “老服务器负载高、新服务器空闲” 的不均衡状态。

  • 会话粘性的副作用:若为了解决 WebSocket 连接稳定性启用ip_hash(强制同一 IP 的请求到同一服务器),可能加剧不均衡(如某一 IP 段用户集中)。

如何避免 WebSocket 的负载不均衡?

解决核心是让长连接在后端服务器间合理分布,同时保证连接稳定性,可选的方案如下:

  1. 合理设置连接超时,自动释放闲置连接
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
  1. 采用 “最少连接数” 负载均衡策略
    nginx.ingress.kubernetes.io/load-balance: "least_conn"
  1. 动态扩缩容
  • 动态扩缩容:自动扩容新节点分担负载,新客户端的连接会分配到新节点。可以采取的方式(1)根据CPU动态扩缩,配置HPA (2)提高响应速度,通过KEDA采集QPS、连接数进行扩缩。需要接入Prometheus或OT的客户端暴露Metrics。
  1. 拆分 HTTP 服务器和 WebSocket 服务器

HTTP 服务器处理普通的 HTTP 请求,提供短连接api。WebSocket 服务器独立出来处理长连接实时会话,方便进一步监控、优化。二者暴露不同的Metrics、应用不同的扩缩容策略:

  • 对于 HTTP:暴露QPS,根据QPS扩缩容,对于Spring应用有Spring Boot Actuator开箱即用。
  • 对于 WebSocket:Spring Boot Actuator不区分 HTTP 请求和 WebSocket 连接,不直接告诉你“当前有多少个 WebSocket 活跃连接”,更重要的是它没法解码、不会统计消息速率、错误、心跳等应用层指标。最好的方式是自己接Prometheus或OT的客户端自己暴露。
  1. 拆分长连接与短连接的负载均衡

将 WebSocket 长连接和普通 HTTP 短连接的代理分开,针对不同类型的流量配置独立的负载均衡策略:

  • 普通 HTTP 请求:使用round_robinleast_conn,保持灵活均衡
  • WebSocket 连接:使用least_conn+ 超时释放,专门优化长连接分布

接下来

nginx ingress controller的使用不便性和架构局限性