Лучшая защита от DDoS — это архитектура, которая изначально спроектирована для работы под нагрузкой. Когда система масштабируется, распределяет нагрузку и умеет деградировать gracefully — атака становится дорогим, но решаемым вызовом, а не катастрофой.
Философия отказоустойчивости
Прежде чем погружаться в технические детали, важно понять базовые принципы:
1. Всё ломается. Сервера падают, сеть разрывается, дата-центры затапливает. Вопрос не «если», а «когда». Архитектура должна предполагать отказы и уметь с ними работать.
2. Нет единой точки отказа (Single Point of Failure). Если отказ любого одного компонента ломает всю систему — это проблема. Каждый критичный компонент должен быть продублирован или заменяем.
3. Graceful degradation. Когда ресурсов не хватает — система должна работать хуже, но работать. Показать статическую версию лучше, чем показать ошибку 503. Отключить второстепенные функции лучше, чем потерять основные.
4. Design for 10x. Проектируйте систему с запасом. Если сейчас 100 RPS — архитектура должна держать 1000 RPS. Это даст время среагировать на атаку.
Компоненты отказоустойчивой архитектуры
Рассмотрим ключевые слои и компоненты, которые обеспечивают устойчивость к атакам и нагрузке.
flowchart LR
Users[Пользователи] --> CDN[CDN/Edge]
subgraph Protection["Слой защиты"]
CDN --> WAF[WAF]
WAF --> LB[Load Balancer]
end
subgraph Backend["Backend"]
LB --> S1[Server 1]
LB --> S2[Server 2]
LB --> S3[Server N]
end
subgraph Storage["Данные"]
S1 & S2 & S3 --> Cache[(Redis)]
S1 & S2 & S3 --> DB[(Database)]
end
CDN и Edge Protection
Content Delivery Network — это не только ускорение загрузки для пользователей. CDN — первый и самый важный рубеж защиты от DDoS.
Как CDN защищает от атак
- Распределение нагрузки — трафик распределяется между сотнями точек присутствия (PoP) по всему миру. Даже терабитная атака «размазывается» и становится управляемой.
- Кэширование — статический контент отдаётся из кэша, не доходя до origin-сервера. При L7-атаке на статику сервер вообще не нагружается.
- Anycast — один IP-адрес, но запросы маршрутизируются к ближайшему PoP. Атакующий трафик остаётся локальным.
- Фильтрация — CDN-провайдеры имеют WAF, rate limiting, bot protection на edge-уровне.
- Скрытие origin IP — атакующий не знает реальный IP вашего сервера, атакует только CDN. Подробнее: как найти реальный IP за CDN.
Выбор CDN
Для защиты от DDoS важны не столько скорость CDN, сколько размер сети и capabilities по безопасности. Подробнее о выборе: как выбрать защиту от DDoS.
Балансировка нагрузки
Load Balancer распределяет входящие запросы между несколькими backend-серверами. Это даёт горизонтальное масштабирование и отказоустойчивость.
Уровни балансировки
L4 (Transport Layer) — балансировка на уровне TCP/UDP. Быстрая, но «тупая» — не понимает HTTP-протокол. Подходит для простых случаев и терминации SSL.
L7 (Application Layer) — балансировка на уровне HTTP. Понимает URL, заголовки, cookies. Позволяет умную маршрутизацию: статику на одни серверы, API на другие, тяжёлые запросы на мощные машины.
Алгоритмы балансировки
- Round Robin — запросы по очереди на каждый сервер. Просто, но не учитывает загрузку серверов.
- Least Connections — запрос идёт на сервер с минимумом активных соединений. Лучше распределяет нагрузку.
- IP Hash — запросы с одного IP всегда идут на один сервер. Важно для sticky sessions.
- Weighted — у серверов разные веса. Мощный сервер получает больше запросов.
- Least Response Time — запрос идёт на самый быстрый сервер. Оптимально для производительности.
Health Checks
Балансировщик должен проверять здоровье backend-серверов и исключать неработающие из ротации:
upstream backend {
# Основные серверы
server 10.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=5 max_fails=3 fail_timeout=30s;
# Резервный сервер — используется когда основные недоступны
server 10.0.0.3:8080 backup;
# Keepalive connections для производительности
keepalive 32;
# Алгоритм балансировки
least_conn;
}
server {
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
# Health check headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Auto-scaling
Auto-scaling — способность системы автоматически добавлять ресурсы при росте нагрузки и убирать при снижении. Это ключевая capability для противодействия DDoS. Настройте мониторинг для отслеживания метрик масштабирования.
Типы масштабирования
Вертикальное (Scale Up) — увеличение мощности одного сервера (больше CPU, RAM). Быстро упирается в лимит и требует даунтайма для изменения.
Горизонтальное (Scale Out) — добавление новых серверов. Теоретически безлимитное, но требует stateless-архитектуры приложения.
Где реализовать
- Cloud providers — AWS Auto Scaling Groups, GCP Managed Instance Groups, Azure VM Scale Sets. Интеграция с метриками, автоматическое управление.
- Kubernetes — Horizontal Pod Autoscaler (HPA) для подов, Cluster Autoscaler для нод. Kubernetes — стандарт для современных систем.
- Serverless — AWS Lambda, Cloudflare Workers, Vercel. Автоматическое масштабирование «из коробки», платите за запросы.
Метрики для масштабирования
По каким метрикам принимать решение о масштабировании:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 50
metrics:
# По CPU
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# По RPS (через Prometheus adapter)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: 100
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100 # Можем удвоить количество подов
periodSeconds: 60
- type: Pods
value: 10 # Или добавить до 10 подов
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300 # Ждём 5 минут перед scale down
policies:
- type: Percent
value: 10
periodSeconds: 60
Кэширование на всех уровнях
Кэширование — один из самых эффективных способов снизить нагрузку на систему. Если запрос отдаётся из кэша — база данных и бизнес-логика не задействованы.
Уровни кэширования
1. Browser Cache — браузер хранит статику локально. Настраивается через Cache-Control заголовки. Самый быстрый кэш, но вы не контролируете инвалидацию.
2. CDN Cache — статика и иногда HTML кэшируются на edge-серверах CDN. Очень эффективно для глобальной аудитории.
3. Application Cache (Redis/Memcached) — кэш на уровне приложения. Результаты запросов к БД, вычисления, сессии.
4. Database Cache — внутренний кэш базы данных. Query cache, buffer pool.
Стратегии кэширования
Основа
30-60 минут
Мини-защита
1 день
Система
1-2 недели
Устойчивость
Постоянно
const Redis = require('ioredis');
const redis = new Redis();
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// 1. Проверяем кэш
const cached = await redis.get(cacheKey);
if (cached) {
console.log('Cache HIT');
return JSON.parse(cached);
}
// 2. Cache miss — идём в БД
console.log('Cache MISS');
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
if (!user) return null;
// 3. Сохраняем в кэш с TTL 5 минут
await redis.setex(cacheKey, 300, JSON.stringify(user));
return user;
}
// Инвалидация при изменении
async function updateUser(userId, data) {
await db.query('UPDATE users SET ? WHERE id = ?', [data, userId]);
// Удаляем из кэша — следующий запрос обновит
await redis.del(`user:${userId}`);
}
Graceful Degradation
Graceful degradation — способность системы продолжать работать с ограниченной функциональностью, когда ресурсов не хватает. Это критически важно при DDoS: лучше показать упрощённую версию сайта, чем ошибку 503.
Уровни деградации
Заранее определите, какие функции можно отключать при нагрузке:
Уровень 1 (мягкая деградация):
- Отключение персонализации (показываем всем одинаковый контент)
- Отключение рекомендаций и «похожих товаров»
- Упрощение поиска (без фасетов и сортировки)
- Увеличение времени кэширования
Уровень 2 (средняя деградация):
- Отключение комментариев и отзывов
- Показ только основной информации о товаре
- Отключение live-чата
- Отложенная обработка заказов
Уровень 3 (жёсткая деградация):
- Переключение на статическую версию сайта
- Показ только главной страницы
- Режим «только для чтения»
- Сообщение «Технические работы» с формой сбора email
Static Fallback
Один из самых эффективных приёмов — подготовить статическую версию сайта заранее:
# Upstream к динамическому backend
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
server_name example.com;
# Статический fallback как отдельная location
location @static_fallback {
root /var/www/static-fallback;
try_files $uri /index.html =503;
# Агрессивное кэширование статики
add_header Cache-Control "public, max-age=3600";
}
location / {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
# Если все backend'ы недоступны — fallback
proxy_intercept_errors on;
error_page 502 503 504 = @static_fallback;
}
# Health endpoint не fallback'ится
location /health {
proxy_pass http://backend;
proxy_connect_timeout 2s;
}
}
Circuit Breaker Pattern
Circuit Breaker — паттерн, который предотвращает каскадные сбои. Если зависимый сервис не отвечает — перестаём к нему обращаться на время, а не забиваем очередь бессмысленными запросами.
Как это работает
Circuit Breaker имеет три состояния:
- Closed (нормальная работа) — запросы проходят к сервису. Если ошибок больше порога — переход в Open.
- Open (сервис недоступен) — запросы сразу отклоняются с fallback-ответом, без обращения к сервису. Через timeout — переход в Half-Open.
- Half-Open (проверка) — пропускаем несколько пробных запросов. Если успешны — возврат в Closed. Если ошибки — обратно в Open.
Зачем это при DDoS
При атаке один из микросервисов может захлебнуться. Без Circuit Breaker:
- Клиенты ждут timeout (30+ секунд)
- Потоки/воркеры заняты ожиданием
- Очередь растёт, память заканчивается
- Весь кластер ложится
С Circuit Breaker:
- После N ошибок circuit открывается
- Запросы сразу получают fallback (мгновенно)
- Ресурсы освобождаются для работающих функций
- Периодически проверяем, не ожил ли сервис
const CircuitBreaker = require('opossum');
// Функция, которую защищаем circuit breaker'ом
async function callPaymentService(order) {
const response = await fetch('http://payment-service/charge', {
method: 'POST',
body: JSON.stringify(order),
timeout: 5000
});
if (!response.ok) throw new Error('Payment failed');
return response.json();
}
// Настраиваем Circuit Breaker
const breaker = new CircuitBreaker(callPaymentService, {
timeout: 5000, // Timeout для одного запроса
errorThresholdPercentage: 50, // При 50% ошибок — открыть circuit
resetTimeout: 30000, // Через 30 сек попробовать снова
volumeThreshold: 10 // Минимум 10 запросов для статистики
});
// Fallback при открытом circuit
breaker.fallback((order) => {
console.log('Payment service unavailable, queuing order');
return queueOrderForLater(order); // Ставим в очередь
});
// События для мониторинга
breaker.on('open', () => console.log('Circuit OPENED - payment service down'));
breaker.on('close', () => console.log('Circuit CLOSED - payment service recovered'));
breaker.on('halfOpen', () => console.log('Circuit HALF-OPEN - testing...'));
// Использование
app.post('/checkout', async (req, res) => {
try {
const result = await breaker.fire(req.body.order);
res.json(result);
} catch (error) {
res.status(503).json({ error: 'Service temporarily unavailable' });
}
});
Multi-region и Disaster Recovery
Для критичных систем недостаточно нескольких серверов в одном дата-центре. Нужно географическое распределение — multi-region deployment.
Зачем нужен multi-region
- Региональные DDoS-атаки — атака может быть направлена на конкретный дата-центр или регион. Если есть резервный регион — трафик переключается туда.
- Отказ дата-центра — пожары, наводнения, отключения электричества случаются. AWS us-east-1 падал неоднократно.
- Сетевые проблемы — магистральные провайдеры иногда теряют связность. Несколько регионов = несколько путей.
- Лучший UX — пользователи подключаются к ближайшему региону, меньше latency.
Архитектуры multi-region
Active-Passive: Один регион активен, второй — горячий резерв. При сбое — ручное или автоматическое переключение. Проще, дешевле, но есть даунтайм при переключении.
Active-Active: Оба региона активны и обслуживают трафик. DNS или Global Load Balancer распределяет пользователей. Сложнее (нужна репликация данных), но нулевой даунтайм.
Репликация данных
Главная проблема multi-region — синхронизация данных:
- Синхронная репликация — write подтверждается только после записи во все регионы. Консистентно, но медленно и зависит от сети.
- Асинхронная репликация — write подтверждается сразу, данные реплицируются в фоне. Быстро, но возможна потеря данных при сбое.
- Conflict resolution — при active-active одни данные могут измениться в обоих регионах. Нужна стратегия разрешения конфликтов (last-write-wins, merge, manual).
Минимум в 2 раза больше, чем single-region (два комплекта инфраструктуры). Плюс трафик между регионами (cross-region data transfer в AWS стоит $0.02/GB). Плюс сложность разработки и поддержки. Для большинства проектов достаточно multi-AZ (несколько зон внутри региона) — это дешевле и проще.
Используйте Global Load Balancer (AWS Global Accelerator, GCP Global LB, Cloudflare Load Balancing). Они маршрутизируют трафик на основе health checks и latency. При отказе региона — автоматическое переключение за секунды. DNS failover работает медленнее (TTL) и хуже для пользовательского опыта.
Для active-active нужны распределённые БД: CockroachDB, YugabyteDB, Google Spanner, Amazon Aurora Global Database. Они сложнее обычных PostgreSQL/MySQL, но поддерживают multi-region из коробки. Для active-passive достаточно обычной БД с cross-region репликацией.