Представьте охранника на входе в большое здание. Первый вариант охранника смотрит только на пропуск: номер сотрудника и турникет назначения. Ему всё равно, зачем человек идёт, что несёт и куда именно внутри. Он просто пропускает. Второй охранник читает документы, знает, на какое совещание человек идёт, может развернуть его на другой этаж, если нужная переговорная занята, и заодно проверяет, нет ли запрещённых предметов. Первый быстрее. Второй умнее. В мире сетевой инфраструктуры первый называется 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 не обойтись. А когда нужно и то и другое, правильный ответ почти всегда строит оба уровня в последовательную цепочку.