Представьте охранника на входе в большое здание. Первый вариант охранника смотрит только на пропуск: номер сотрудника и турникет назначения. Ему всё равно, зачем человек идёт, что несёт и куда именно внутри. Он просто пропускает. Второй охранник читает документы, знает, на какое совещание человек идёт, может развернуть его на другой этаж, если нужная переговорная занята, и заодно проверяет, нет ли запрещённых предметов. Первый быстрее. Второй умнее. В мире сетевой инфраструктуры первый называется L4-балансировщиком, второй L7-балансировщиком. И разница между ними определяет архитектуру всей системы.
Что видит каждый уровень и почему это принципиально
Модель OSI делит сетевой стек на семь уровней. Четвёртый, транспортный, работает с TCP и UDP: он знает IP-адреса источника и назначения, порты и состояние соединения. Седьмой, прикладной, работает с содержимым: HTTP-методы, URL-пути, заголовки, cookies, тело запроса.
L4-балансировщик живёт на транспортном уровне и принимает решение, опираясь только на то, что там видно: IP-адрес клиента, порт назначения, протокол TCP или UDP. Он не знает, что за данные идут внутри TCP-потока. Это может быть HTTP-запрос, MySQL-сессия, игровой трафик или зашифрованный HTTPS. Балансировщику всё равно. Он выбирает бэкенд по алгоритму и перенаправляет пакеты. Соединение при этом остаётся единым: клиент устанавливает TCP-соединение с виртуальным IP балансировщика, тот переадресует пакеты на выбранный сервер.
L7-балансировщик делает нечто принципиально иное. Он полностью завершает TCP-соединение на своей стороне, читает и разбирает прикладной протокол, принимает решение о маршрутизации и открывает новое соединение к бэкенду. Клиент видит балансировщик, бэкенд видит балансировщик. Два разных TCP-соединения вместо одного.
Как работает L4 и что происходит с пакетами
L4-балансировщик в режиме NAT перехватывает входящие пакеты и переписывает в них IP-адрес назначения с виртуального IP на реальный адрес бэкенда. Обратные пакеты проходят через балансировщик в обратную сторону и получают обратную замену адресов. Клиент уверен, что общается с одним IP, на деле трафик уходит на конкретный бэкенд.
В режиме DSR (Direct Server Return) балансировщик только переписывает MAC-адрес назначения и отправляет пакет напрямую на бэкенд. Бэкенд отвечает клиенту напрямую, минуя балансировщик. Это снимает с него нагрузку на обратный трафик и даёт максимальную пропускную способность.
Настроить L4-балансировку через Linux IPVS можно так:
# добавить виртуальный сервис на порту 80
ipvsadm -A -t 192.168.1.100:80 -s rr
# добавить два бэкенда
ipvsadm -a -t 192.168.1.100:80 -r 10.0.0.10:80 -m
ipvsadm -a -t 192.168.1.100:80 -r 10.0.0.11:80 -m
# посмотреть текущую таблицу
ipvsadm -L -n
Флаг -m означает режим маскарадинга (NAT), флаг -g означал бы DSR. Алгоритм -s rr задаёт round-robin. Другие варианты: lc (least connections), wlc (weighted least connections), sh (source hashing для session affinity).
В HAProxy L4-режим настраивается через блок stream:
stream {
upstream tcp_backends {
server 10.0.0.10:3306;
server 10.0.0.11:3306;
}
server {
listen 3306;
proxy_pass tcp_backends;
}
}
Производительность L4 измеряется в миллионах пакетов в секунду. Пропускная способность достигает 10-40 Гбит/с на один сервер. Задержка добавляется в диапазоне десятков-сотен микросекунд, поскольку балансировщик не разбирает содержимое и не ждёт полного HTTP-запроса.
Как работает L7 и что происходит на уровне запросов
L7-балансировщик принимает от клиента полное TCP-соединение, дожидается HTTP-заголовков, читает метод, путь, Host-заголовок, cookies, тело. Только после этого он выбирает бэкенд и открывает к нему новое соединение. Это означает, что для каждого клиента поддерживаются два активных соединения: одно входящее, одно исходящее.
Такая архитектура открывает возможности, недоступные L4. Запросы к /api/ можно направить на API-серверы, запросы к /static/ на CDN-серверы, запросы с заголовком X-Version: v2 на новую версию приложения. Sticky sessions реализуются через чтение cookie SESSIONID, а не через хеш IP-адреса.
Конфигурация nginx как L7-балансировщика с path-based routing:
upstream api_servers {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
upstream static_servers {
server 10.0.2.10:80;
server 10.0.2.11:80;
}
server {
listen 443 ssl;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
location /api/ {
proxy_pass http://api_servers;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
proxy_pass http://static_servers;
}
}
HAProxy в режиме HTTP с routing по заголовкам:
frontend http_front
bind *:80
use_backend api_back if { path_beg /api/ }
use_backend static_back if { path_beg /static/ }
default_backend app_back
backend api_back
option httpchk GET /health HTTP/1.1\r\nHost:\ localhost
http-check expect status 200-299
server api1 10.0.1.10:8080 check inter 5s
server api2 10.0.1.11:8080 check inter 5s
SSL-терминация на L7 означает, что балансировщик расшифровывает HTTPS-трафик, проверяет содержимое и может передавать на бэкенды как зашифрованный (mTLS), так и plain HTTP трафик. Это разгружает бэкенды от TLS-хендшейков и позволяет инспектировать запросы для WAF или rate limiting.
Проблема HTTP/2 gRPC и мультиплексирования
Здесь L4 обнаруживает принципиальное ограничение. HTTP/2 мультиплексирует несколько запросов внутри одного TCP-соединения. Клиент открывает одно соединение и гонит через него десятки параллельных стримов. L4-балансировщик видит одно TCP-соединение и направляет его на один бэкенд. Все сотни запросов этого клиента уходят на один сервер, хотя балансировщик честно думает, что балансирует.
gRPC построен поверх HTTP/2, и эта проблема воспроизводится в полный рост в микросервисных архитектурах. Если между двумя сервисами стоит L4-балансировщик, gRPC-клиент с пулом соединений равномерно распределит соединения, но каждое соединение залипнет на один бэкенд. Балансировка происходит на уровне соединений, а не запросов.
L7-балансировщик не имеет этой проблемы. Он видит отдельные HTTP/2-стримы внутри мультиплексированного соединения и распределяет их независимо. Envoy, Nginx и HAProxy умеют это в HTTP/2-режиме. Убедиться, что nginx проксирует HTTP/2 к бэкендам, а не только принимает его от клиентов, можно так:
upstream grpc_backend {
server 10.0.1.10:50051;
server 10.0.1.11:50051;
}
server {
listen 443 ssl http2;
location / {
grpc_pass grpcs://grpc_backend;
}
}
Проверки работоспособности бэкендов на разных уровнях
L4-балансировщик проверяет здоровье бэкенда одним способом: устанавливает TCP-соединение. Если порт отвечает, сервер считается живым. Если процесс принимает TCP-соединения, но падает с ошибкой 500 на каждый запрос из-за недоступной базы данных, L4 этого не знает. Трафик продолжает идти на сломанный бэкенд.
L7-балансировщик проверяет реальный ответ приложения. HAProxy с HTTP health check:
backend app_servers
option httpchk GET /health HTTP/1.1\r\nHost:\ app.example.com
http-check expect string "status":"ok"
server app1 10.0.1.10:8080 check inter 5s fall 3 rise 2
server app2 10.0.1.11:8080 check inter 5s fall 3 rise 2
Параметр fall 3 означает: пометить бэкенд как недоступный после трёх последовательных неудачных проверок. rise 2 означает: вернуть его в ротацию после двух успешных. Это защищает от кратковременных сбоев и флапинга.
По рекомендациям из production-практики L4-проверки должны помечать бэкенд недоступным при таймауте SYN в диапазоне 3-5 секунд. L7-системы выгодно дополнять outlier detection: автоматическим исключением бэкендов с elevated 5xx rate, например при 5 последовательных ошибках или 10% ошибок за 30 секунд, с карантином на 30-60 секунд перед повторным включением в ротацию.
Многоуровневая архитектура и то как Google и AWS строят балансировку в production
Реальные high-traffic системы редко выбирают между L4 и L7. Они используют оба уровня последовательно, и каждый делает то, что умеет лучше всего.
Архитектура Google в production выглядит так: Anycast VIP направляет входящий трафик на Maglev L4-балансировщик с consistent hashing, который поглощает DDoS и обеспечивает географическое распределение. Затем трафик попадает на GFE (Google Front End), L7-уровень, который терминирует TLS и выполняет content-based routing. Failover на L4-уровне сходится за секунды с менее чем 5% реремаппинга соединений.
AWS реализует ту же логику через Network Load Balancer на L4 с Anycast и статическими IP-адресами и Application Load Balancer на L7 с host- и path-based routing и TLS-терминацией.
В Terraform это разворачивается так:
# L4 NLB для TCP-трафика
resource "aws_lb" "tcp_nlb" {
name = "tcp-nlb"
load_balancer_type = "network"
subnets = var.public_subnets
}
# L7 ALB для HTTP-трафика
resource "aws_lb" "http_alb" {
name = "http-alb"
load_balancer_type = "application"
subnets = var.public_subnets
security_groups = [var.alb_sg_id]
}
# Listener rule для path-based routing
resource "aws_lb_listener_rule" "api_rule" {
listener_arn = aws_lb_listener.http.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
condition {
path_pattern { values = ["/api/*"] }
}
}
Общий принцип, который вытекает из этих архитектур: L4 поглощает объём и атаки на сетевом уровне, L7 применяет логику и политики на уровне приложения. Пытаться сделать одним уровнем работу обоих обычно означает либо потерю производительности там, где она нужна, либо отсутствие умной маршрутизации там, где она необходима.
Выбор между L4 и L7 не является вопросом вкуса или моды на технологии. Это вопрос о том, что именно нужно знать балансировщику для принятия правильного решения. Если достаточно IP и порта, L4 справится быстрее и проще. Если нужен URL, заголовок или cookie, без L7 не обойтись. А когда нужно и то и другое, правильный ответ почти всегда строит оба уровня в последовательную цепочку.