Real-Time Backend

Infrastructure

WebSocket-сервис задеплоен в K8s. Работает 15 минут, потом все соединения рвутся. Метрики в норме. nginx Ingress с дефолтными настройками убивает idle соединения через 60 секунд - и это только первая из пяти конфигурационных ловушек.

  • Twitch перешёл от sticky sessions к stateless WebSocket-серверам в 2019 - деплой ускорился с 30+ минут до нескольких секунд за счёт выноса state в Redis
  • Discord при переходе с Elixir на Go снизил потребление памяти в 3 раза при тех же 26 млн concurrent соединений - выбор runtime критичен при таком масштабе
  • AWS ALB нативно поддерживает WebSocket но с дефолтным idle timeout 60с - без heartbeat каждые 30с или увеличения timeout до 3600с соединения рвутся у молчащих пользователей

Kubernetes и WebSocket: почему дефолтная конфигурация не работает

Стандартный K8s Service с алгоритмом round-robin ломает WebSocket по-тихому. Клиент делает HTTP Upgrade request - один под его обрабатывает и устанавливает соединение. Следующий HTTP-запрос этого же клиента (health check, REST API) уходит на другой под. Но WebSocket-соединение уже привязано к первому. Это не катастрофа для соединения, но это проблема если нужна привязка клиента к конкретному поду.

Более серьёзная проблема: HTTP/1.1 Upgrade - специфический запрос. Не все Ingress контроллеры правильно проксируют его по умолчанию. nginx нужна директива `proxy_read_timeout` увеличенная с 60с до нескольких минут, иначе он закрывает idle соединения.

AWS ALB и Google Cloud Load Balancer поддерживают WebSocket нативно - не нужны специальные аннотации. Но timeout всё равно нужно увеличить: ALB по умолчанию закрывает idle соединения через 60 секунд. Для чатов где пользователь может молчать часами - нужно 3600с или heartbeat каждые 30с.

Почему nginx Ingress закрывает WebSocket-соединения через 60 секунд по умолчанию?

Sticky Sessions: когда нужна привязка к поду

WebSocket само по себе не требует sticky sessions - соединение устанавливается один раз и живёт. Sticky нужен если клиент делает REST запросы параллельно с WebSocket и эти REST запросы должны идти на тот же под (например, in-memory state на поде).

Правильная архитектура избегает sticky sessions. State переносится из памяти пода в Redis. Тогда любой под может обслужить любой запрос клиента. Это позволяет масштабировать и деплоить без ограничений.

Twitch перешёл от sticky sessions к stateless WebSocket-серверам в 2019. До этого деплой требовал drain 100K соединений с каждого пода - 30+ минут. После: любой под заменяем мгновенно, клиент reconnect'ится к любому новому поду и получает state из Redis.

Когда sticky sessions необходимы для WebSocket-сервиса?

Service Mesh: Istio и Linkerd для WebSocket

Service mesh добавляет sidecar-прокси к каждому поду. Весь трафик идёт через прокси - это даёт mTLS между сервисами, circuit breaker, retry, rate limiting и трейсинг без изменений в коде. Для WebSocket это работает, но с нюансами.

Istio и Linkerd по умолчанию распознают HTTP/1.1 Upgrade и корректно обрабатывают WebSocket. Но retry и circuit breaker работают на уровне соединения, не сообщений. Если соединение упало - sidecar может попробовать reconnect. Если конкретное сообщение не дошло - sidecar об этом не знает.

Linkerd 2.x имеет более простую конфигурацию для WebSocket чем Istio - не требует явного VirtualService для timeout. По умолчанию Linkerd не применяет timeout к соединениям с HTTP Upgrade. Это делает Linkerd привлекательным для WebSocket-heavy систем.

Как service mesh (Istio/Linkerd) обрабатывает retry для WebSocket?

Масштабирование: HPA, resource limits, affinity

Horizontal Pod Autoscaler для WebSocket работает иначе чем для HTTP. CPU метрика не подходит: idle WebSocket потребляет мало CPU но держит соединение. HPA нужно настраивать по числу connections или по memory.

Resource limits для WebSocket: каждое соединение потребляет ~10-50KB памяти (буферы, state). При 10K соединений на под - 100-500MB только на соединения. Плюс бизнес-логика. Типичный request: 512MB-2GB memory, 0.5-2 CPU core.

Discord при пиковой нагрузке держит 26 млн concurrent WebSocket соединений на Go-based сервисах. Переход с Elixir на Go в 2020 снизил потребление памяти в 3 раза - Go runtime эффективнее для большого числа goroutines чем Erlang processes при таком масштабе. Подробности в Engineering Blog.

Service mesh автоматически решает все проблемы надёжности WebSocket без дополнительной настройки

Service mesh требует явной настройки timeout для WebSocket (0s вместо дефолтных 15-30с) иначе обрывает idle соединения

Дефолтные timeout в Istio и других service mesh рассчитаны на short-lived HTTP запросы. WebSocket соединение живёт часами - без явного `timeout: 0s` для WebSocket routes sidecar будет разрывать idle соединения по таймауту.

Почему CPU не подходит как primary metric для HPA WebSocket-сервиса?

Итоги

  • nginx Ingress требует proxy_read_timeout увеличенного до 3600с и явных Upgrade/Connection заголовков для WebSocket
  • Sticky sessions - симптом плохой архитектуры с in-memory state; правильное решение - state в Redis
  • Istio/Linkerd требуют `timeout: 0s` для WebSocket routes - дефолтные таймауты рвут idle соединения
  • HPA масштабируется по числу соединений, не по CPU; scale down должен быть медленным чтобы не рвать соединения

Связанные темы

K8s инфраструктура для WebSocket пересекается с несколькими областями

  • Graceful Deployment — terminationGracePeriodSeconds и preStop hook - часть K8s конфигурации для graceful shutdown
  • Distributed Tracing — Service mesh автоматически добавляет tracing через sidecar без изменений в коде
  • Edge Computing — Cloudflare Workers и Durable Objects предлагают альтернативу K8s для WebSocket на edge
  • Chaos Engineering — Chaos Mesh - K8s-native инструмент для chaos experiments через CRD

Вопросы для размышления

  • Как организовать zero-downtime scale down HPA если на каждом поде тысячи WebSocket-соединений?
  • Что выбрать для WebSocket в K8s: nginx Ingress, HAProxy Ingress или AWS ALB Ingress Controller?
  • Как настроить resource limits если число соединений на поде непредсказуемо меняется от 100 до 10K?

Связанные уроки

  • sd-03-scalability
  • net-47-container-networking
Infrastructure

0

1

Войти