L4 vs L7 load balancing — transport против application
В первом уроке мы говорили о LB абстрактно — что он распределяет запросы. Пора углубиться: load balancer бывает двух принципиально разных уровней, и это меняет всё — от производительности до возможностей маршрутизации. Это L4 (Layer 4, transport-уровень) и L7 (Layer 7, application-уровень) балансировка.
Разница не в маркетинге, а в том, что LB видит и понимает про трафик. L4 видит только TCP-пакеты и IP-адреса. L7 разбирает HTTP, читает заголовки, понимает URL. Из этого вытекает всё остальное: скорость, гибкость, цена, типичные use cases. Давайте разберёмся, когда какой нужен — и почему в большой инфраструктуре обычно используют оба.
Что значит «уровень» — ставим в контекст OSI
Вернёмся к OSI-модели. Layer 4 — transport (TCP, UDP). Layer 7 — application (HTTP, gRPC, WebSocket). LB на уровне N знает протокол этого уровня и всех ниже, но не знает про уровни выше.
Главный практический вывод: L4 LB можно использовать для любого TCP/UDP-трафика, не только для HTTP. Это и MySQL, и Postgres, и Redis, и SMTP, и WebSocket, и gRPC, и игровые сервера. L7 LB — только для HTTP (и протоколов поверх HTTP — gRPC, WebSocket).
Второй вывод: L4 LB не может смотреть внутрь трафика. Если контент зашифрован TLS — L4 видит только зашифрованный поток байт и может его лишь перенаправить, не более.
L4 LB: тупой и быстрый
L4 LB работает на уровне TCP-соединения. Когда клиент устанавливает TCP-соединение с LB, LB:
- Принимает SYN, выбирает backend по своему алгоритму (round-robin, least-connections и т.п.).
- Устанавливает TCP-соединение с выбранным backend-ом.
- Дальше — пересылает байты в обе стороны без разбора. Что в этих байтах — HTTP, gRPC, MySQL-протокол, кто угодно — LB пофиг.
В терминах OSI это работа на L4 (transport): LB понимает TCP/UDP, но не знает, какой протокол поверх.
Плюсы L4:
- Скорость. Не нужно парсить HTTP, не нужно расшифровывать TLS. Просто перекидываем байты с одного сокета на другой. Производительность измеряется миллионами пакетов в секунду на скромном железе.
- Любой протокол. MySQL, Redis, MQTT, custom binary — работает всё. Если у вас сервис на нестандартном TCP-протоколе — L4 единственный вариант.
- Прозрачность для TLS. Если хотите end-to-end шифрование от клиента до backend без расшифровки на LB — L4 это позволяет. TLS-handshake происходит между клиентом и backend, LB просто пересылает зашифрованный поток.
- Низкая нагрузка на CPU. LB не делает дорогих операций, поэтому держится на дешёвом железе.
Минусы:
- Нет понимания контекста. Не можете роутить по URL, по host header, по user-agent.
- Sticky sessions только по IP клиента. Если у клиента поменялся IP (mobile network) — сессия порвётся.
- Не видит ошибки приложения. Backend может возвращать 500 на каждый запрос — L4 LB не заметит (TCP-соединение-то здоровое).
# Простейший L4 LB на HAProxy для MySQL
frontend mysql_in
bind *:3306
mode tcp
default_backend mysql_pool
backend mysql_pool
mode tcp
balance roundrobin
server db1 10.0.0.1:3306 check
server db2 10.0.0.2:3306 check
mode tcp — ключевая директива. Она говорит HAProxy: не парси протокол, не пытайся понять, что там, просто туннелируй TCP.
L7 LB: умный и универсальный (для HTTP)
L7 LB разбирает HTTP-протокол. Каждый запрос для него — это набор смысловых единиц: метод (GET/POST), путь (/api/users), заголовки (Host: api.example.com), тело. На основе этого можно делать гораздо больше:
- Path-based routing.
/api/*-> один пул,/static/*-> другой пул (с большим кэшем). - Host-based routing.
api.example.comиwww.example.comмогут идти на разные backend-ы, хотя у LB один IP. - Header-based routing. Canary deploy: запросы с
X-Canary: trueидут на новую версию, остальные — на старую. - TLS termination. LB сам расшифровывает HTTPS, backend получает чистый HTTP внутри сети. Меньше нагрузки на backend, проще управлять сертификатами централизованно.
- Кэширование. Если ответ кэшируем (Cache-Control), LB может отдавать его без обращения к backend.
- Application-aware health checks. Не «TCP-соединение открыто», а «GET /healthz вернул 200».
- Compression. LB может сжимать ответы gzip/brotli, backend отдаёт без сжатия.
- WAF (Web Application Firewall). Парсит запросы, блокирует SQL-инъекции и XSS-попытки.
Минусы L7:
- Медленнее, чем L4. Парсинг HTTP и TLS — это работа. Хотя «медленнее» относительно — современный NGINX легко тянет десятки тысяч RPS на одном CPU.
- Только HTTP-семейство. MySQL, Redis и кастомные TCP-протоколы L7 LB не понимает.
- TLS termination требует управления сертификатами. Сертификат должен лежать на LB.
- Сложнее настраивать. Конфиг толще, правил больше, нюансов больше.
Сравнение в цифрах и фичах
| Свойство | L4 LB | L7 LB |
|---|---|---|
| Что видит | TCP/UDP, IP, порты | HTTP-метод, URL, заголовки |
| Скорость | Очень высокая, миллионы PPS | Высокая, десятки тысяч RPS |
| Поддерживаемые протоколы | Любой TCP/UDP | Только HTTP-семейство (включая gRPC, WebSocket) |
| TLS termination | Нет (passthrough) | Да |
| Routing по URL/headers | Нет | Да |
| Sticky sessions | По IP клиента | По cookie, по header, по URL |
| Health checks | TCP connect или TCP+простой ping | HTTP-запрос с проверкой статуса/тела |
| Application logs | Только IP и порт | Полный HTTP request/response |
| Типичные продукты | AWS NLB, HAProxy TCP, IPVS | AWS ALB, NGINX, Envoy, Traefik |
Когда какой использовать
Правило большого пальца:
- HTTP-API/веб-приложение — L7. Вам нужны path-based routing, TLS termination, application-level health checks.
- База данных (MySQL/Postgres) или Redis — L4. Эти протоколы не HTTP, L7 LB их не понимает.
- Long-lived TCP-соединения (game servers, message brokers) — L4. L7 LB не оптимален для соединений на часы.
- Очень высокая нагрузка на edge (миллионы PPS) — L4 быстрее. Часто комбинируют: L4 на самом краю для скорости, L7 внутри для интеллекта.
- Microservices с разными endpoint-ами на одном домене — L7. Без него никак:
/users/*-> users-service,/orders/*-> orders-service.
В реальной production-системе крупного сервиса часто используют и L4, и L7 одновременно. Самый внешний слой — L4 (для скорости и DDoS-фильтрации), за ним — L7 (для умной маршрутизации).
Internet
|
v
+-------------------+
| L4 LB (AWS NLB) | <- очень быстрый, фильтрует SYN-флуд
+-------------------+
|
v
+-------------------+
| L7 LB (NGINX/ALB) | <- TLS termination, routing
+-------------------+
|
v
+-------------------+
| Application |
+-------------------+
TLS passthrough vs TLS termination
Тут важный момент. L4 LB не может расшифровать TLS, поэтому работает в режиме passthrough: клиент устанавливает TLS-соединение напрямую с backend, LB просто пересылает зашифрованные байты. Сертификат должен быть на backend.
L7 LB обычно работает в режиме TLS termination: TLS расшифровывается на LB, дальше до backend идёт plain HTTP (или новый TLS, который дешифруется заново — этот вариант называется TLS bridging).
Какой выбрать?
- Termination — стандарт. Удобно, централизованное управление сертификатами, дешевле для backend-а. Используют 95% веб-сервисов.
- Passthrough — когда нужна end-to-end защита (compliance), или когда LB не должен видеть содержимое (банки, медицина), или когда содержимое не HTTP (MySQL over TLS).
Есть третий вариант — SNI-based routing на L4. SNI (Server Name Indication) — расширение TLS, в котором клиент в открытом виде объявляет, к какому домену он подключается, до самого handshake. L4 LB может прочитать SNI (он в первом TLS-сообщении и не зашифрован) и роутить по нему — хотя при этом сам трафик остаётся зашифрованным. Это компромисс: получаем host-based routing без TLS termination.
Sticky sessions на L4 и L7
Sticky session (или session affinity) — это когда LB старается отправить все запросы одного пользователя на один и тот же backend. Зачем: если у вас приложение хранит state в памяти (sessions in memory, локальный кэш) — запросы должны идти на тот же инстанс. На самом деле это антипаттерн в современной архитектуре (stateless backends + external session store), но иногда быстрее наклеить sticky, чем переписать приложение.
Реализация на L4: по IP клиента. LB ведёт таблицу client_ip -> backend и пока IP тот же, направляет туда же. Минус: если у клиента поменялся IP (mobile network, NAT), он попадёт на другой backend — сессия теряется. Плюс: работает для любого TCP-протокола.
Реализация на L7: по cookie. LB добавляет в ответ свой cookie (например, LB_BACKEND=server2), а на следующих запросах читает его и направляет туда же. Не зависит от IP — работает корректно при смене сети. Минус: только для HTTP.
# L7 sticky на NGINX (требует commercial NGINX Plus или модуль sticky)
upstream app {
sticky cookie srv_id expires=1h domain=.example.com;
server 10.0.0.1;
server 10.0.0.2;
server 10.0.0.3;
}
# L4 sticky на HAProxy (по IP-source)
backend tcp_pool
mode tcp
balance source
server srv1 10.0.0.1:5432 check
server srv2 10.0.0.2:5432 check
Попробуй сам
Давайте попробуем оба режима на одной HAProxy-конфиге. Установите HAProxy (brew install haproxy или apt install haproxy) и создайте конфиг:
global
log stdout local0
defaults
log global
timeout connect 5s
timeout client 30s
timeout server 30s
# L4 (TCP) -- для любого TCP-сервиса, например, для MySQL
frontend mysql_in
bind *:13306
mode tcp
default_backend mysql_pool
backend mysql_pool
mode tcp
balance roundrobin
server db1 127.0.0.1:3306 check
server db2 127.0.0.1:3307 check
# L7 (HTTP) -- понимает HTTP, может роутить
frontend http_in
bind *:18080
mode http
use_backend api_v2 if { path_beg /v2/ }
default_backend api_v1
backend api_v1
mode http
balance roundrobin
server app1 127.0.0.1:8001 check
server app2 127.0.0.1:8002 check
backend api_v2
mode http
balance roundrobin
server app3 127.0.0.1:8003 check
server app4 127.0.0.1:8004 check
Заметьте отличия:
- В L4-секции
mode tcp— HAProxy просто туннелирует. - В L7-секции
mode http, и естьuse_backend ... if { path_beg /v2/ }— это правило path-based routing. Запросы на/v2/*идут на пул v2, остальные — на v1.
Запустите четыре Python-сервера на портах 8001-8004 (python3 -m http.server 8001 и т.д.), запустите HAProxy, и проверьте:
haproxy -f /path/to/haproxy.cfg
# Запрос на v1 (попадёт в api_v1 pool):
curl http://localhost:18080/some/path
# Запрос на v2 (попадёт в api_v2 pool):
curl http://localhost:18080/v2/users
Поднимите stats-страницу HAProxy (она встроена), чтобы посмотреть статистику по backend-ам:
frontend stats
bind *:8404
mode http
stats enable
stats uri /
Откройте http://localhost:8404/ — увидите таблицу со всеми backend-ами, их статусом и метриками.
Kubernetes Service: ClusterIP это L4 LB, Ingress это L7 LB