На одном из проектов мы получили L7 DDoS порядка 80–100k RPS на API платёжного сервиса. CDN отработал штатно, сетевой канал не насытился, а приложение легло за четыре минуты. Пул соединений к PostgreSQL иссяк первым, за ним встал сервис авторизации, а дальше каскадный отказ прошёлся по всем двенадцати микросервисам как домино. Деньги ушли на WAF и Anti-DDoS-провайдера, но внутри приложения не было ни circuit breaker, ни graceful degradation, ни rate limiting на уровне API Gateway. Ничего. Голое приложение за дорогим забором.
Эта статья - о том, как строить защиту приложения от DDoS-атак изнутри, на уровне архитектурных паттернов, а не сетевых фильтров.
Почему DDoS ломает приложение, а не сеть
Когда команда слышит "DDoS", первая ассоциация - забитый канал: SYN-flood, UDP amplification. В терминологии MITRE ATT&CK это Network Denial of Service (T1498, Impact), включая Direct Network Flood (T1498.001) и Reflection Amplification (T1498.002). Эти атаки фильтруются на уровне сети - Cloud Armor, AWS Shield, Cloudflare.Но современные DDoS всё чаще бьют по прикладному уровню - Endpoint Denial of Service (T1499, Impact), конкретно Service Exhaustion Flood (T1499.002) и Application Exhaustion Flood (T1499.003). Атакующий не забивает канал - он шлёт тысячи валидных HTTP-запросов, каждый из которых дёргает базу, вызывает тяжёлый эндпоинт или генерирует PDF. WAF пропускает такие запросы, потому что каждый по отдельности выглядит легитимно. И вот тут начинается самое интересное.
Каскадный отказ развивается предсказуемо:
- Пул соединений к БД исчерпывается (стандартный PostgreSQL держит 100–200 соединений)
- Сервисы, зависящие от БД, копят запросы в thread pool
- Thread pool забивается, новые запросы получают таймаут
- Балансировщик видит 502/503 от всех инстансов - включая здоровые сервисы, которые просто ждут ответа от упавшего downstream
Autoscaling как защита от DDoS: горизонтальное масштабирование под нагрузкой с потолком
Почему autoscaling - не панацея
Горизонтальное масштабирование под нагрузкой - первая реакция SRE на рост трафика. Нагрузка растёт - поднимаем реплики. В Kubernetes это HPA, в AWS - Auto Scaling Groups, в GCP - Managed Instance Groups.Ловушка: autoscaling без верхней границы при DDoS превращается в budget DDoS. Атакующий заставляет вас платить за обработку мусорного трафика. Система не падает, но ваш бюджет - да. Видел кейс, где за ночь атаки набежало $12k на AWS только за auto-scaled инстансы, которые молотили мусор.
Настройка HPA с потолком
Ключевые параметры, которые я выставляю на каждом проекте:maxReplicas - жёсткий потолок, обычно 3-5x от нормы; scaleUp.stabilizationWindowSeconds - окно 60-120 секунд, чтобы HPA не дёргался от кратковременных всплесков; scaleDown с медленной политикой, чтобы не убить сервис преждевременным сжатием после атаки.
YAML:
# HPA с потолком и стабилизацией для DDoS-сценариев
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
minReplicas: 3
maxReplicas: 15 # потолок = 5x от нормы
metrics:
- type: Resource
resource:
name: cpu
target: {type: Utilization, averageUtilization: 70}
behavior:
scaleUp: {stabilizationWindowSeconds: 60}
scaleDown: {stabilizationWindowSeconds: 300}
maxReplicas: 15 вместо 3 - это 150 соединений вместо 30. При max_connections = 100 на PostgreSQL система ляжет не от DDoS, а от собственного autoscaling. Система убьёт себя сама. Решение: PgBouncer перед базой, ограничение POOL_SIZE на pod, и KEDA вместо HPA для масштабирования по глубине очереди, а не по CPU.Decision point: масштабировать или сбрасывать
Правило, которое я фиксирую в runbook:- CPU < 80%, P99 latency < SLO - HPA справляется, масштабируем штатно
- CPU > 80%, P99 > SLO, healthcheck OK - масштабируем + включаем rate limiting на Gateway
- Healthcheck fail на >30% подов - прекращаем масштабирование, включаем circuit breaker и graceful degradation
Circuit breaker паттерн в микросервисах: как остановить каскадный отказ
Три состояния и логика переключения
Circuit breaker - самый недооценённый паттерн устойчивости микросервисов при DDoS. По систематическому обзору паттернов резилентности (arxiv, 2025, 26 исследований, PRISMA-aligned) circuit breaker входит в девять ключевых тем устойчивости и напрямую предотвращает каскадные отказы.Три состояния:
Closed - нормальная работа. Запросы идут к downstream-сервису. Breaker считает ошибки в скользящем окне. Ошибки превышают порог - переход в Open.
Open - запросы немедленно отклоняются, без ожидания таймаута. Downstream получает передышку. Через заданное время - переход в Half-Open.
Half-Open - ограниченное число тестовых запросов. Успешны - Closed. Нет - обратно в Open.
Как пишет Microsoft в документации Azure Architecture Center: "The Circuit Breaker pattern helps prevent an application from repeatedly trying to run an operation that's likely to fail.""Схема автоматического выключателя помогает предотвращать приложение из многократных попыток запустить операцию которая скорее всего завершится как фейл." Ключевой момент - breaker защищает не downstream-сервис, а вызывающий код от бесконечного ожидания. Это тонкая, но принципиальная разница.
Из того же систематического обзора (тема T1): "Pattern effectiveness depends on failure semantics; over-tight circuit-breaker thresholds reduce throughput.""Эффективность схемы защиты зависит от семантики отказов; слишком жёсткие пороговые значения срабатывания автоматических выключателей снижают пропускную способность." Слишком агрессивные пороги приводят к тому, что breaker срабатывает на обычных всплесках, а не на реальной деградации. Я на одном проекте потратил день на дебаг "почему сервис тупит" - оказалось, breaker с порогом в 3 ошибки за 10 секунд срабатывал на каждый деплой.
Настройка порогов: практические значения
По рекомендациям из анализа API Gateway resilience (Zuplo, 2026):- Failure threshold: 5 ошибок в 60-секундном окне как стартовая точка. Ниже - ложные срабатывания. Выше - клиенты видят реальные отказы слишком долго
- Cooldown period: 30-60 секунд - типичное значение для восстановления от временных проблем
- Half-open test count: 1-3 запроса. Если все успешны - замыкаем цепь
YAML:
# Resilience4j - circuit breaker для DDoS-сценария
resilience4j.circuitbreaker:
instances:
paymentService:
slidingWindowSize: 20
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
slowCallDurationThreshold: 2s
slowCallRateThreshold: 80
slowCallRateThreshold. При DDoS сервис может не возвращать ошибки - а просто отвечать медленно. Без мониторинга latency circuit breaker не сработает, и вместо быстрого отказа вы получите pool exhaustion. Это та ситуация, когда всё вроде "зелёное", а пользователи уже ушли.Circuit breaker без fallback хуже, чем его отсутствие
Ошибка, которую я встречал многократно: circuit breaker настроен, но при переходе в Open клиент просто получает 503. Это не решение - это перенос проблемы на другой уровень. Breaker обязан возвращать fallback-ответ: кэшированные данные, упрощённый результат или явное сообщение о деградации с адекватным HTTP-кодом.Graceful degradation при DDoS-атаке: деградация сервиса под нагрузкой
Fallback-стратегии по уровням
Graceful degradation - не отказ, а осознанное снижение качества. По систематическому обзору, Fallback Logic and Graceful Degradation входит в ключевые паттерны резилентности микросервисов.Три уровня, которые я закладываю в архитектуру:
Уровень 1 - кэшированные ответы. Сервис каталога перегружен - API Gateway отдаёт последнюю закэшированную версию через cache-aside паттерн. Пользователь видит данные с задержкой 5-10 минут, но видит. При ошибке downstream - stale cache с продлённым TTL. Лучше вчерашний каталог, чем пустая страница.
Уровень 2 - редуцированный ответ. Сервис рекомендаций не работает - показываем статический топ-10. Elasticsearch перегружен - переключаемся на полнотекстовый поиск по PostgreSQL (да, медленнее, но работает). Функциональность снижена, но не потеряна.
Уровень 3 - read-only режим. Отключаем все write-операции, кроме критических (оплата, авторизация). API возвращает 503 на некритичные POST/PUT/DELETE. Сервис доступен для просмотра.
Переключение между уровнями - через feature flags (LaunchDarkly, Unleash, или KV-хранилище в Consul/etcd). В идеале автоматически: если метрика
http_server_requests_seconds{quantile="0.99"} в Prometheus превышает SLO дольше 2 минут - переход на Уровень 1.Rate limiting как первая линия DDoS-защиты
Rate limiting на API Gateway - граница между autoscaling и graceful degradation. Он определяет, какой объём трафика допускается до приложения.В Kong или Nginx конфигурация rate limiting сводится к
limit_req_zone по IP или по токену авторизации с burst-буфером. Типичные значения: 100 RPS на IP для публичных эндпоинтов, 500–1000 RPS для аутентифицированных клиентов.Ключевая ошибка: единый лимит на все эндпоинты. DDoS на
/api/health (лёгкий) съедает квоту, и легитимные запросы на /api/checkout (критичный) не проходят. Решение - per-route rate limiting с приоритизацией по бизнес-критичности. Здоровье сервиса и оплата - это разные вселенные, и лимиты у них должны быть разные.Очереди сообщений под нагрузкой: backpressure и Dead Letter Queue
Queue-based load leveling
Очереди - естественный буфер между пиковой нагрузкой и возможностями бэкенда. Как пишет AWS в документации по паттернам отказоустойчивости: "Instead of writing directly to the database, the application sends operations to a queue where they wait to be processed at a controlled rate. This acts like a buffer zone.""Вместо записи личного в базу данных, приложение отправляет операции в очередь где они будут обработаны с контролируемой скоростью. Этот акт похож на буферную зону."При DDoS очередь (Kafka, RabbitMQ, SQS) принимает удар: запросы складываются в буфер, consumer обрабатывает с контролируемой скоростью. Producer генерирует 50k msg/s - consumer обрабатывает 5k. Без потери данных, без перегрузки базы. Очередь - это амортизатор, который гасит удар.
По данным систематического обзора (тема T5), adaptive queues повышают эффективность на 15% по сравнению со статическими конфигурациями. Consumer должен динамически регулировать batch size и concurrency.
Backpressure: когда очередь переполняется
Без backpressure очередь растёт бесконечно, жрёт всю доступную память, и система падает - уже не от DDoS, а от OOM. Вы пережили атаку, но умерли от лечения.Реализации backpressure в разных стеках:
- Kafka:
max.poll.recordsна consumer ограничивает batch,max.block.msна producer заставляет ждать при полном буфере - RabbitMQ:
x-max-lengthна очереди +basic.nackсrequeue=falseдля сброса в DLQ - Reactor/WebFlux: встроенный backpressure через
Flux.onBackpressureDrop()
Dead Letter Queue - обязательный компонент
DLQ - место, куда уходят сообщения, не обработанные после N retry. Как отмечает AWS: "Error handling requires Dead Letter Queues implementation with defined retry policies and monitoring for failed message recovery.""Для обработки ошибок требуется реализация очередей недоставленных сообщений с определенными политиками повторных попыток и мониторингом восстановления сообщений, которые не удалось восстановить."При DDoS это критично: вредоносные запросы, вызывающие exception в consumer, не должны запускать бесконечные retry-штормы. Из систематического обзора (тема T3): "Naive backoff without jitter causes retry storms; adding jitter and budgets smooths recovery.""Наивная задержка без учета jitter приводит к шквалу повторных попыток; добавление jitter и ограничений сглаживает процесс восстановления." Каждый retry к DLQ-сообщению - с exponential backoff и jitter. Без jitter десятки consumer'ов синхронно ретраят одновременно и кладут downstream ещё раз.
Bulkhead pattern: изоляция blast radius при DDoS
Bulkhead - аналогия с водонепроницаемыми переборками корабля. Один отсек затоплен - остальные работают. В отказоустойчивой архитектуре микросервисов: перегрузка одного сервиса не убивает остальные.
Как пишет Zuplo: "Without isolation, all routes through your gateway share the same connection pool. A single slow backend can exhaust every available connection, blocking requests to perfectly healthy services.""Без изоляции, все пути через ваш шлюз деляться пулами подключений. Простой медленный бэкенд может исчерпать каждое доступное подключение, блокируя запросы к абсолютно здоровому сервису." Именно это я видел на том проекте с платёжным API - один тормозящий сервис утащил за собой одиннадцать здоровых.
Три уровня изоляции:
- Thread pool / connection pool per service. Resilience4j Bulkhead с
maxConcurrentCalls: 25для сервиса рекомендаций,maxConcurrentCalls: 100для сервиса авторизации. Перегрузка рекомендаций не исчерпывает соединения к авторизации - Kubernetes namespace + ResourceQuota. Критичные сервисы (платежи, авторизация) в namespace с Guaranteed QoS. Некритичные (аналитика, рекомендации) - BestEffort, при нехватке ресурсов kubelet убьёт их первыми. Жёстко? Да. Но платежи важнее рекомендаций
- Отдельные ingress для внешнего и внутреннего трафика. DDoS на публичный API не затрагивает внутренние gRPC-вызовы
Паттерны устойчивости микросервисов: когда что применять
Все описанные паттерны работают в связке. Decision matrix для проектирования:| Симптом | Первый паттерн | Второй паттерн | Чего НЕ делать |
|---|---|---|---|
| Резкий рост RPS на публичном API | Rate limiting на Gateway | HPA autoscaling с потолком | Масштабировать без лимита |
| Таймауты от downstream-сервиса | Circuit breaker (Open) | Fallback на кэш | Увеличивать таймаут |
| Пул соединений к БД исчерпан | Bulkhead (изоляция пулов) | Queue-based load leveling | Поднимать max_connections |
| P99 latency растёт, ошибок нет | Slow call circuit breaker | Graceful degradation Ур. 1 | Игнорировать до появления 5xx |
| Consumer не справляется с очередью | Backpressure + DLQ | Autoscaling consumer pods | Увеличивать batch без лимита |
Связка по слоям:
- Edge - rate limiting + WAF: сбрасываем мусорный трафик до того, как он дойдёт до приложения
- Gateway - circuit breaker + bulkhead: изолируем сервисы и обрываем каскады
- Service - graceful degradation + fallback: сохраняем частичную работоспособность
- Data - queue + backpressure + DLQ: буферизуем и контролируем поток к хранилищам
- Observability - Prometheus + Grafana + alerting: видим, что происходит, и реагируем
Нагрузочное тестирование: проверяем DDoS-устойчивую архитектуру до атаки
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
По систематическому обзору (тема T8): observability - enabler для всех остальных паттернов. Без метрик вы не узнаете, сработал ли breaker и на каком уровне деградации система.
Опыт последних проектов показывает устойчивый паттерн: команды тратят 80% бюджета безопасности на внешнюю Anti-DDoS-защиту и 0% на архитектурную устойчивость. AWS Shield Advanced - от $3000/мес, Cloudflare Enterprise - от $5000. Настройка circuit breaker в Resilience4j, rate limiting в Kong и backpressure в Kafka - zero-cost по инфраструктуре и 2-3 дня работы инженера. Но именно эти паттерны определяют, ляжет приложение за 4 минуты или деградирует управляемо.
Я убеждён, что через год-два L7 DDoS станет настолько рутинным, что application-level resilience войдёт в стандартный checklist при code review - наравне с проверкой авторизации. Пока этого не произошло, большинство команд продолжат узнавать о пробелах в своей архитектуре из ночных инцидентов. Цена такого урока - от потери SLA до потери клиентов.
Проверьте свой staging прямо сейчас: возьмите k6-сценарий из этой статьи, разгоните до 5000 VU и посмотрите, что вернёт ваш API. Если увидите 502 - у вас та же проблема, с которой начиналась эта статья. Если 503 с
Retry-After - вы на правильном пути.
Последнее редактирование модератором: