Худшее, что может случиться во время DDoS-атаки — узнать о ней от клиентов. «Ваш сайт не работает уже час» — такое сообщение означает, что вы потеряли продажи, репутацию и нервы. Правильно настроенный мониторинг позволяет узнать о проблеме за секунды и начать реагировать до того, как пользователи заметят.
Зачем нужен мониторинг для защиты от DDoS
DDoS-атака редко начинается мгновенно с максимальной мощности. Обычно есть паттерн нарастания: сначала небольшой всплеск трафика, потом рост, потом пиковая нагрузка. Если вы отслеживаете метрики в реальном времени — заметите аномалию на первом этапе и успеете включить защиту.
Кроме того, мониторинг помогает:
- Отличить атаку от легитимного всплеска — вышла реклама, попали в топ Hacker News, упомянули в СМИ. Это не атака, хотя метрики похожи.
- Понять тип атаки — L3/L4 (сетевая) или L7 (прикладная). Для каждого типа нужны разные меры.
- Оценить эффективность защиты — включили Cloudflare Under Attack, но трафик не упал? Значит атака проходит защиту, нужны другие меры.
- Собрать данные для анализа — после атаки нужно понять, что случилось, откуда шёл трафик, какие endpoint'ы атаковали.
- Документировать инциденты — для отчётов руководству, страховых случаев, работы с правоохранительными органами.
Что мониторить
Для эффективного обнаружения атак нужно отслеживать метрики на нескольких уровнях: сетевом, серверном и прикладном. Рассмотрим каждый уровень подробно.
flowchart LR
subgraph Metrics["Метрики"]
App[App] --> Prom[Prometheus]
Nginx[Nginx] --> Prom
Sys[System] --> Prom
end
subgraph Alerting["Алерты"]
Prom --> Alert[Alertmanager]
Alert --> TG[Telegram]
Alert --> Email[Email]
end
subgraph Viz["Дашборды"]
Prom --> Grafana
end
Анатомия Slowloris-атаки
Атакующий открывает сотни HTTP-соединений и отправляет заголовки медленно, по байту.
Соединения никогда не завершаются. Сервер ждёт окончания заголовков для каждого.
Все слоты web-сервера заняты незавершёнными запросами. Новые клиенты не могут подключиться.
Агрессивные таймауты заголовков и лимит соединений с одного IP решают проблему.
Ключевые метрики для мониторинга
Базовые пороги для алертов
Пороги сильно зависят от вашего обычного трафика, но вот ориентировочные значения для среднего сайта:
- RPS — алерт если в 5 раз выше обычного
- CPU — warning при >70%, critical при >90%
- Response time — warning при >1s, critical при >5s
- 5xx errors — warning при >1%, critical при >5%
- Active connections — алерт если в 10 раз выше обычного
Важно: сначала соберите baseline (обычные значения) за 1-2 недели, потом настраивайте пороги. Без baseline будет много ложных срабатываний.
Prometheus + Grafana
Prometheus + Grafana — самая популярная связка для мониторинга. Prometheus собирает и хранит метрики, Grafana визуализирует их и отправляет алерты. Оба инструмента бесплатны и open-source.
Архитектура
Prometheus работает по pull-модели: он сам ходит к вашим сервисам и собирает метрики. На каждом сервере/сервисе нужно поднять exporter — маленький агент, который отдаёт метрики в формате Prometheus.
Основные exporters для мониторинга DDoS:
- node_exporter — системные метрики Linux (CPU, RAM, disk, network)
- nginx-prometheus-exporter — метрики nginx
- blackbox_exporter — проверка доступности извне (HTTP checks, DNS, TCP)
- cloudflare_exporter — метрики из Cloudflare (если используете)
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
ports:
- "9090:9090"
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: grafana
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=your_secure_password
- GF_USERS_ALLOW_SIGN_UP=false
ports:
- "3000:3000"
restart: unless-stopped
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
ports:
- "9100:9100"
restart: unless-stopped
volumes:
prometheus_data:
grafana_data:
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
# Сам Prometheus
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Системные метрики серверов
- job_name: 'node'
static_configs:
- targets:
- 'server1:9100'
- 'server2:9100'
# Nginx метрики
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
# Blackbox (внешние проверки)
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://yoursite.com
- https://yoursite.com/api/health
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox:9115
Настройка алертов
Метрики без алертов — как пожарная сигнализация без звука. Вы узнаете о проблеме только когда сами посмотрите в дашборд, а это может быть слишком поздно.
Alertmanager
Prometheus использует отдельный компонент — Alertmanager — для управления алертами. Он умеет:
- Группировать похожие алерты (чтобы не получать 100 сообщений об одной проблеме)
- Подавлять алерты во время maintenance window
- Маршрутизировать разные алерты разным командам
- Отправлять уведомления в разные каналы: email, Slack, Telegram, PagerDuty, webhook
Правила алертов для DDoS
Создайте файл с правилами алертов. Вот примеры ключевых правил для обнаружения атак:
groups:
- name: ddos-detection
interval: 10s # Проверяем чаще для быстрого обнаружения
rules:
# Аномальный всплеск трафика
- alert: TrafficSpike
expr: rate(nginx_http_requests_total[1m]) > 10 * avg_over_time(rate(nginx_http_requests_total[1m])[1h:5m])
for: 2m
labels:
severity: warning
annotations:
summary: "Всплеск трафика на {{ $labels.instance }}"
description: "RPS в 10 раз выше среднего за последний час"
# Много 5xx ошибок
- alert: HighErrorRate
expr: sum(rate(nginx_http_requests_total{status=~"5.."}[5m])) / sum(rate(nginx_http_requests_total[5m])) > 0.05
for: 1m
labels:
severity: critical
annotations:
summary: "Высокий уровень 5xx ошибок"
description: "Более 5% запросов возвращают 5xx. Текущее значение: {{ $value | humanizePercentage }}"
# Высокая нагрузка на CPU
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 5m
labels:
severity: critical
annotations:
summary: "Высокая нагрузка CPU на {{ $labels.instance }}"
description: "CPU usage > 90% в течение 5 минут"
# Сервер недоступен извне
- alert: SiteDown
expr: probe_success{job="blackbox-http"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Сайт недоступен: {{ $labels.instance }}"
description: "HTTP проверка не прошла в течение 1 минуты"
# Медленные ответы
- alert: SlowResponseTime
expr: histogram_quantile(0.95, rate(nginx_http_request_duration_seconds_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "Медленные ответы сервера"
description: "95-й перцентиль времени ответа > 5 секунд"
global:
resolve_timeout: 5m
route:
receiver: 'telegram-notifications'
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: 'telegram-critical'
match:
severity: critical
group_wait: 10s
repeat_interval: 1h
receivers:
- name: 'telegram-notifications'
telegram_configs:
- bot_token: 'YOUR_BOT_TOKEN'
chat_id: YOUR_CHAT_ID
message: |
{{ .Status | toUpper }}
{{ range .Alerts }}
Alert: {{ .Labels.alertname }}
Severity: {{ .Labels.severity }}
{{ .Annotations.summary }}
{{ .Annotations.description }}
{{ end }}
- name: 'telegram-critical'
telegram_configs:
- bot_token: 'YOUR_BOT_TOKEN'
chat_id: YOUR_CHAT_ID
message: |
CRITICAL ALERT
{{ range .Alerts }}
{{ .Annotations.summary }}
{{ .Annotations.description }}
Started: {{ .StartsAt.Format "15:04:05" }}
{{ end }}
# Чтобы создать Telegram бота:
# 1. Напишите @BotFather в Telegram
# 2. Создайте бота командой /newbot
# 3. Получите token
# 4. Добавьте бота в группу или получите chat_id через @userinfobot
Внешний мониторинг (Uptime Monitoring)
Внутренний мониторинг (Prometheus на вашем сервере) не увидит проблему, если сервер полностью недоступен или проблема на уровне сети/DNS. Поэтому критически важно иметь внешний мониторинг — проверки из разных точек мира.
Варианты внешнего мониторинга
- UptimeRobot — бесплатно до 50 мониторов с интервалом 5 минут. Отличный выбор для старта.
- Pingdom — более функциональный, платный. Детальные отчёты, Real User Monitoring.
- StatusCake — бесплатный план с хорошими возможностями.
- Better Uptime — современный интерфейс, status pages, on-call расписания.
- Self-hosted Blackbox Exporter — разместите на VPS в другом дата-центре для независимых проверок.
Что проверять
- Главная страница — базовая проверка доступности
- /api/health или /health — специальный endpoint, который проверяет состояние приложения (БД доступна, кэш работает)
- Критичные endpoint'ы — страница логина, корзина, API
- DNS resolution — проверка что DNS отвечает корректно
- SSL certificate expiry — алерт за 30 дней до истечения
Health Check Endpoint
Создайте специальный endpoint для проверки здоровья приложения. Он должен проверять критические зависимости:
app.get('/health', async (req, res) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
checks: {}
};
// Проверка базы данных
try {
await db.query('SELECT 1');
health.checks.database = { status: 'ok' };
} catch (err) {
health.checks.database = { status: 'error', message: err.message };
health.status = 'degraded';
}
// Проверка Redis
try {
await redis.ping();
health.checks.redis = { status: 'ok' };
} catch (err) {
health.checks.redis = { status: 'error', message: err.message };
health.status = 'degraded';
}
// Проверка внешнего API (если критичен)
try {
const response = await fetch('https://api.payment.com/health', { timeout: 5000 });
health.checks.payment_api = { status: response.ok ? 'ok' : 'error' };
} catch (err) {
health.checks.payment_api = { status: 'error' };
// Не меняем общий статус — внешний API не критичен
}
const statusCode = health.status === 'ok' ? 200 : 503;
res.status(statusCode).json(health);
});
Логирование и анализ
Метрики показывают что происходит, логи показывают почему. Во время атаки логи — основной источник информации для анализа.
Что должно быть в логах nginx
Стандартный формат логов nginx недостаточен для анализа атак. Расширьте его:
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$http_x_forwarded_for $http_cf_connecting_ip '
'$http_cf_ipcountry $connection $connection_requests';
access_log /var/log/nginx/access.log detailed;
# Отдельный лог для ошибок с большей детализацией
error_log /var/log/nginx/error.log warn;
Быстрый анализ логов
Во время атаки нужно быстро понять: откуда идёт трафик, на какие URL, с какими User-Agent. Вот полезные команды:
Автоматизация реагирования
Когда атака случается ночью или в выходные, автоматизация может спасти ситуацию до того, как человек успеет отреагировать.
Примеры автоматических действий
- Автоматическое включение Cloudflare Under Attack Mode — при обнаружении аномального трафика
- Автоматическая блокировка IP — если IP превышает лимит запросов
- Включение дополнительных серверов — auto-scaling при росте нагрузки
- Переключение на статическую версию сайта — если динамика не справляется
- Уведомление дежурного — эскалация по on-call расписанию
Пример: автоматическое включение Under Attack при аномалии
#!/bin/bash
# Настройки
ZONE_ID="your_cloudflare_zone_id"
API_TOKEN="your_cloudflare_api_token"
THRESHOLD_RPS=1000 # Порог RPS для включения защиты
LOG_FILE="/var/log/nginx/access.log"
STATE_FILE="/tmp/under_attack_enabled"
# Получить текущий RPS
CURRENT_RPS=$(tail -1000 "$LOG_FILE" | awk -v now="$(date +%s)" -v window=60 '
BEGIN { count=0 }
{
# Парсим время из лога nginx
gsub(/\[|\]/, "", $4)
cmd = "date -d "" $4 "" +%s"
cmd | getline log_time
close(cmd)
if (now - log_time < window) count++
}
END { print count/window }
')
echo "Current RPS: $CURRENT_RPS"
# Если RPS выше порога и защита не включена
if (( $(echo "$CURRENT_RPS > $THRESHOLD_RPS" | bc -l) )) && [ ! -f "$STATE_FILE" ]; then
echo "Enabling Under Attack Mode..."
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/security_level" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value":"under_attack"}'
touch "$STATE_FILE"
# Отправить уведомление
curl -s -X POST "https://api.telegram.org/bot$TG_BOT_TOKEN/sendMessage" \
-d "chat_id=$TG_CHAT_ID" \
-d "text= Auto-enabled Under Attack Mode. RPS: $CURRENT_RPS"
fi
# Если RPS нормализовался и защита была включена автоматически
if (( $(echo "$CURRENT_RPS < $THRESHOLD_RPS / 2" | bc -l) )) && [ -f "$STATE_FILE" ]; then
echo "Disabling Under Attack Mode..."
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/security_level" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value":"medium"}'
rm -f "$STATE_FILE"
curl -s -X POST "https://api.telegram.org/bot$TG_BOT_TOKEN/sendMessage" \
-d "chat_id=$TG_CHAT_ID" \
-d "text= Auto-disabled Under Attack Mode. RPS normalized: $CURRENT_RPS"
fi