Linux Nginx 怎么在 Kubernetes 环境下配置 WebSocket 代理

发布时间:2026/10/4 12:35:57
Linux Nginx 怎么在 Kubernetes 环境下配置 WebSocket 代理 前言WebSocket 在 Kubernetes 里翻车的姿势非常固定握手阶段看起来完全正常浏览器 Network 面板里能看到 101 Switching Protocols连接也确实建立了但过 60 秒左右连接会毫无征兆地断开客户端自动重连日志里则什么都看不到。第二类症状是连接压根建立不起来浏览器控制台报WebSocket connection to wss://... failed服务端连握手请求都没收到。第一类问题的答案在proxy_read_timeout的默认值上——60 秒。WebSocket 是一条长期空闲的长连接如果没有业务心跳读超时一到Nginx 就会认为上游已经没有数据可读主动关闭连接。第二类问题则通常落在协议上WebSocket 的升级握手依赖Upgrade与Connection: Upgrade两个请求头以及 HTTP/1.1任何一层把这两个头吃掉或写死握手就失败。在 Kubernetes 里Nginx 可能以两种身份出现在链路上一是自己部署的 NginxConfigMap 挂配置、Service 暴露 Pod二是Nginx Ingress Controller用 Ingress 资源加注解。这两者的配置方式完全不同本文分别给出可运行的完整示例。示例基于 nginx 1.24RHEL 9 与 Debian 12 通用与 ingress-nginx 1.9/1.10 系列注解名随版本演变实际使用时请以你所装版本的注解文档为准。一、WebSocket 为什么对代理配置特别敏感WebSocket 的握手是一次标准 HTTP 请求但带上了三个关键头GET /ws HTTP/1.1 Host: ws.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13服务端接受后返回 101此后这条 TCP 连接就不再是 HTTP 了双方按 WebSocket 帧格式通信。问题就出在这里Nginx 默认用 HTTP/1.0 与上游通信proxy_http_version默认是 1.0。HTTP/1.0 没有Upgrade机制握手必然失败。必须显式设置proxy_http_version 1.1;。Upgrade和Connection是逐跳头hop-by-hop header代理不会自动转发必须用proxy_set_header手动透传。而且Connection的取值不能写死要根据客户端是否发了Upgrade动态决定——这就是要用map的原因。Nginx 认为「没有数据可读」等于「上游挂了」读超时默认 60 秒。长连接必须把超时改大。Nginx 与上游之间的连接默认会被回收。keepalive_timeout与proxy_read_timeout都要覆盖 WebSocket 的最长空闲时间。二、自己部署 Nginx完整可运行的配置先看核心配置。注意map指令只能出现在http上下文写在server或location里会报map directive is not allowed here。# 基于 nginx 1.24RHEL 9 / Debian 12 通用 # 文件路径RHEL 9 为 /etc/nginx/nginx.confDebian 12 为 /etc/nginx/nginx.conf worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 10240; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # 关键点 1动态决定 Connection 头的取值 # 客户端带 Upgrade 时发 upgrade否则发 close map $http_upgrade $connection_upgrade { default upgrade; close; } upstream ws_backend { server 127.0.0.1:9000; keepalive 32; } server { listen 80; server_name ws.example.com; location /ws { proxy_pass http://ws_backend; # 关键点 2必须用 HTTP/1.1 proxy_http_version 1.1; # 关键点 3逐跳头必须手动透传 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # 常规转发头 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键点 4读超时必须大于最长空闲时间 proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_connect_timeout 10s; # WebSocket 是双向实时流关掉缓冲更符合语义 proxy_buffering off; } } }proxy_read_timeout 3600s的含义是「上游 3600 秒没有任何数据才判定超时」。它不能替代心跳中间的任何一层云 LB、CDN、家用路由器 NAT都可能在自己的空闲超时后掐断连接。工程上正确的做法是应用层定期发 ping/pong例如每 30 秒一次服务端把proxy_read_timeout设成心跳间隔的数倍即可不必盲目设成 24 小时。在 Kubernetes 里把上面这份配置放进 ConfigMap再挂进容器apiVersion: v1 kind: ConfigMap metadata: name: nginx-ws-config namespace: default data: nginx.conf: | worker_processes auto; error_log /var/log/nginx/error.log warn; pid /tmp/nginx.pid; events { worker_connections 10240; } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; map $http_upgrade $connection_upgrade { default upgrade; close; } upstream ws_backend { server ws-svc.default.svc.cluster.local:9000; keepalive 32; } server { listen 8080; server_name _; location /ws { proxy_pass http://ws_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_buffering off; } } } --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-ws namespace: default spec: replicas: 2 selector: matchLabels: app: nginx-ws template: metadata: labels: app: nginx-ws spec: containers: - name: nginx image: nginx:1.24-alpine ports: - containerPort: 8080 volumeMounts: - name: conf mountPath: /etc/nginx/nginx.conf subPath: nginx.conf resources: requests: cpu: 100m memory: 128Mi limits: memory: 256Mi volumes: - name: conf configMap: name: nginx-ws-config --- apiVersion: v1 kind: Service metadata: name: nginx-ws namespace: default spec: selector: app: nginx-ws ports: - name: http port: 80 targetPort: 8080三个细节值得单独说明。第一listen 8080;而不是 80——非 root 用户不能监听 1024 以下端口容器里跑 nginx 经常会遇到这个坑。第二pid /tmp/nginx.pid;是为了让官方镜像在只读根文件系统或非 root 场景下也能启动默认路径/var/run/nginx.pid可能不可写。第三server_name _;是「默认服务器」的惯用写法用于接收任意 Host。验证时不要只看 Pod 是否 Running要真的发起一次握手# 1) 直接对后端 Pod 测试绕过 Nginx确认应用本身支持 WebSocket kubectl port-forward svc/ws-svc 9000:9000 # 2) 在另一个终端发起最小握手预期看到 HTTP/1.1 101 Switching Protocols curl -i -N \ -H Connection: Upgrade \ -H Upgrade: websocket \ -H Sec-WebSocket-Version: 13 \ -H Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ \ http://127.0.0.1:9000/ws如果第 1 步得到 101、第 2 步经 Nginx 后得到 400 或 200问题就锁定在 Nginx 的proxy_http_version或Upgrade透传上。用wscat或websocat做长连接测试比curl更直观# wscat 需要 npm 安装npm i -g wscat wscat -c ws://ws.example.com/ws # 静置观察如果 60 秒后断开说明 proxy_read_timeout 没生效三、用 Nginx Ingress Controller优先用注解如果你用的是社区版 Nginx Ingress Controller绝大多数情况下不需要为 WebSocket 做额外配置——它的配置模板里已经包含proxy_http_version 1.1与Upgrade/Connection的透传。真正需要动手的是超时和会话保持apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ws-ingress namespace: default annotations: # 读超时单位秒。默认 60长时间空闲的 WS 会被掐断 nginx.ingress.kubernetes.io/proxy-read-timeout: 3600 nginx.ingress.kubernetes.io/proxy-send-timeout: 3600 # 握手连接建立的超时默认 5 秒 nginx.ingress.kubernetes.io/proxy-connect-timeout: 10 # 实时双向流关掉响应缓冲 nginx.ingress.kubernetes.io/proxy-buffering: off # 多副本时把同一客户端稳定打到同一后端握手与后续帧必须同一台 nginx.ingress.kubernetes.io/affinity: cookie nginx.ingress.kubernetes.io/session-cookie-name: wsroute nginx.ingress.kubernetes.io/session-cookie-max-age: 3600 spec: ingressClassName: nginx rules: - host: ws.example.com http: paths: - path: /ws pathType: Prefix backend: service: name: ws-svc port: number: 9000注解名和默认值随 ingress-nginx 版本变化较大affinity系列在不同大版本里的推荐写法也有差异部分版本更推荐用upstream-hash-by做一致性哈希。改动前请用kubectl describe ingress与官方注解文档核对不要照抄网上的旧例子。会话保持不只是「性能优化」而是正确性要求。WebSocket 是一次 TCP 长连接握手时选中的后端就是之后所有帧的处理者连接期间不存在「重新负载均衡」。所以要么用 cookie/哈希把客户端钉死在同一个 Pod要么让应用把会话状态外置Redis 等让任意副本都能处理。只靠默认的轮询重连时打到另一台 Pod会话状态就会丢。另外两个 K8s 特有的坑滚动更新时terminationGracePeriodSeconds默认 30 秒Pod 收到 SIGTERM 后 WebSocket 连接会立即被打断客户端只能重连。对长连接业务应适当调大该值并让应用在 SIGTERM 后先停止接受新连接、再给已有连接一个收尾窗口。还有Service的sessionAffinity: ClientIP它只对同一客户端 IP 生效在 NAT 后大量用户共享出口 IP 时会退化成集中打一台通常不如 cookie 方案。常见坑点❌ 照抄网上片段时只写了proxy_set_header Connection upgrade;写死了取值。✅ 用map $http_upgrade $connection_upgrade { default upgrade; close; }动态取值。写死upgrade会让非 WebSocket 请求也带上Connection: upgrade与上游连接池的行为冲突可能引发难以复现的 400。❌ 忘了proxy_http_version 1.1;因为「其他 location 也没写一样能访问」。✅ 普通 HTTP 请求在 HTTP/1.0 下也能工作所以这个错误只会在 WebSocket 上暴露。凡是代理 WebSocket 的 location这一行是必需项。❌ 把map写在server或location里。✅map只在http上下文有效。报错原文是map directive is not allowed here。❌ 把proxy_read_timeout设成86400s就以为万事大吉结果客户端还是每小时断一次。✅ Nginx 只是链路中的一层。云 LB、CDN、NAT 网关各有自己的空闲超时超过它们一样断。应在应用层加 ping/pong 心跳让连接保持活跃而不是无限放大单层超时。❌ 用 Ingress 时给proxy-body-size、proxy-buffering都配了唯独忘了超时于是「握手成功但 60 秒断」反复出现。✅ 记住默认proxy-read-timeout是 60 秒这是 WebSocket 场景下最需要主动覆盖的默认值。❌ 用kubectl port-forward svc/nginx-ws 8080:80测试时一切正常上线后用户仍然断连。✅port-forward是直连 Pod 的隧道完全绕过 Ingress Controller、Service 和云 LB测不到这些层的超时与缓冲。验证必须走真实入口域名或 LoadBalancer IP。❌ 后端多副本且没有会话保持测试时「有时能连、有时连不上」。✅ 要么配置affinity注解或upstream-hash-by要么把会话状态放到 Redis 之类的共享存储里。轮询 本地内存会话的组合必然间歇性失败。❌ WebSocket 的 location 上开了proxy_cache以为能缓存握手结果。✅ WebSocket 握手返回 101 后连接就不再是 HTTP缓存无从谈起。而且Upgrade请求不应被缓存可能造成握手串包。总结配置项取值作用proxy_http_version1.1HTTP/1.0 不支持 Upgrade必须显式指定proxy_set_header Upgrade$http_upgrade透传逐跳头代理不会自动转发proxy_set_header Connection$connection_upgrade配合map动态取值不能用固定字符串proxy_read_timeout大于最长空闲时间常配 3600s覆盖默认 60s避免空闲被断开proxy_bufferingoff双向实时流不需要缓冲map指令位置http上下文写在 server/location 会直接报错Ingress 注解proxy-read-timeout、affinityIngress Controller 场景下的对应开关长期方案应用层 ping/pong 心跳单层调超时无法覆盖整条链路Kubernetes 下的 WebSocket 配置可以浓缩成一句话协议头要透传对超时要放大客户端要钉在同一台后端。前者靠proxy_http_version 1.1加map中者靠proxy_read_timeout加上心跳后者靠会话保持或共享状态。排障时务必按「后端直连 → Nginx → Ingress → 入口 LB」的顺序逐层验证不要因为port-forward通了就认为整条链路都通。具体注解名与默认值请以你所用的 ingress-nginx 版本与官方文档为准。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询