Настройка алертов по метрикам веб-приложения
Алертинг без продуманной методологии превращается в шум: 200 уведомлений за ночь, половина из которых «resolved» через 2 минуты. Команда перестаёт реагировать — и именно тогда приходит реальная проблема. Цель настройки — алерты только на ситуации, требующие действий человека.
Принципы перед конфигурацией
Alerting on symptoms, not causes. Алерт на «сайт недоступен для пользователей» важнее, чем «CPU > 80%». Высокий CPU — причина, которая может и не влиять на пользователей.
Правило четырёх золотых сигналов (Google SRE Book):
- Latency — время ответа
- Traffic — rps/rpm
- Errors — процент ошибок
- Saturation — утилизация ресурсов
Начинайте с первых трёх.
Burn rate вместо порогов. «Error rate > 5% на протяжении 5 минут» лучше, чем «1 ошибка за 1 минуту». Burn rate показывает, как быстро вы «сжигаете» свой SLO-бюджет ошибок.
Стек: Prometheus + Alertmanager + Grafana
# docker-compose.yml
services:
prometheus:
image: prom/prometheus:v2.51.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./rules:/etc/prometheus/rules
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
ports:
- "9090:9090"
alertmanager:
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
ports:
- "9093:9093"
grafana:
image: grafana/grafana:11.0.0
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3000:3000"
volumes:
prometheus_data:
grafana_data:
Метрики из приложения
Для Laravel — экспорт метрик через prometheus-laravel:
composer require spatie/laravel-prometheus
php artisan vendor:publish --provider="Spatie\LaravelPrometheus\LaravelPrometheusServiceProvider"
Кастомные метрики:
// app/Providers/AppServiceProvider.php
use Prometheus\CollectorRegistry;
public function boot(): void
{
$registry = app(CollectorRegistry::class);
// Counter — количество HTTP-запросов
$httpRequests = $registry->getOrRegisterCounter(
'app',
'http_requests_total',
'Total HTTP requests',
['method', 'route', 'status']
);
// Histogram — время ответа
$httpDuration = $registry->getOrRegisterHistogram(
'app',
'http_request_duration_seconds',
'HTTP request duration',
['method', 'route'],
[0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
);
// Gauge — очередь задач
$queueSize = $registry->getOrRegisterGauge(
'app',
'queue_size',
'Current queue size',
['queue']
);
}
Middleware для автоматической записи метрик:
// app/Http/Middleware/PrometheusMetrics.php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Prometheus\CollectorRegistry;
class PrometheusMetrics
{
public function __construct(private CollectorRegistry $registry) {}
public function handle(Request $request, \Closure $next): mixed
{
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
$route = $request->route()?->getName() ?? 'unknown';
$this->registry->getOrRegisterCounter('app', 'http_requests_total', '', ['method', 'route', 'status'])
->inc([$request->method(), $route, $response->getStatusCode()]);
$this->registry->getOrRegisterHistogram('app', 'http_request_duration_seconds', '', ['method', 'route'], [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5])
->observe($duration, [$request->method(), $route]);
return $response;
}
}
Конфигурация Prometheus
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
rule_files:
- 'rules/*.yml'
scrape_configs:
- job_name: 'web-app'
static_configs:
- targets: ['app:9000']
metrics_path: /metrics
- job_name: 'nginx-exporter'
static_configs:
- targets: ['nginx-exporter:9113']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
Правила алертинга
# rules/web-app.yml
groups:
- name: web-app
rules:
# Высокий процент ошибок (5xx)
- alert: HighErrorRate
expr: |
sum(rate(app_http_requests_total{status=~"5.."}[5m]))
/
sum(rate(app_http_requests_total[5m])) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Error rate {{ $value | humanizePercentage }}"
description: "5xx rate exceeded 5% for 2 minutes"
# Медленные ответы (P95 > 2 секунды)
- alert: HighLatencyP95
expr: |
histogram_quantile(0.95,
sum by (le) (rate(app_http_request_duration_seconds_bucket[5m]))
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P95 latency {{ $value | humanizeDuration }}"
# Отсутствие трафика (аномальное падение)
- alert: TrafficDrop
expr: |
sum(rate(app_http_requests_total[5m])) < 0.1
and sum(rate(app_http_requests_total[1h] offset 1h)) > 1
for: 5m
labels:
severity: critical
annotations:
summary: "Traffic almost zero — possible outage"
# Большая очередь задач
- alert: QueueBacklog
expr: app_queue_size{queue="default"} > 1000
for: 10m
labels:
severity: warning
annotations:
summary: "Queue backlog: {{ $value }} jobs"
# SSL сертификат истекает
- alert: SSLCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 14 * 24 * 3600
for: 1h
labels:
severity: warning
annotations:
summary: "SSL cert expires in {{ $value | humanizeDuration }}"
Alertmanager — маршрутизация и дедупликация
# alertmanager.yml
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'severity']
group_wait: 30s # ждать перед первой нотификацией
group_interval: 5m # интервал между нотификациями одной группы
repeat_interval: 4h # повтор если не resolved
receiver: 'telegram-critical'
routes:
- match:
severity: critical
receiver: 'telegram-critical'
continue: false
- match:
severity: warning
receiver: 'telegram-warning'
group_interval: 15m
repeat_interval: 12h
receivers:
- name: 'telegram-critical'
telegram_configs:
- bot_token: 'your_bot_token'
chat_id: -1001234567890
message: |
🔴 *{{ .CommonLabels.alertname }}*
{{ range .Alerts }}
{{ .Annotations.summary }}
{{ if .Annotations.description }}{{ .Annotations.description }}{{ end }}
{{ end }}
- name: 'telegram-warning'
telegram_configs:
- bot_token: 'your_bot_token'
chat_id: -1001234567890
message: |
⚠️ *{{ .CommonLabels.alertname }}*
{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}
inhibit_rules:
# Если критичный алерт активен — подавить предупреждения того же типа
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname']
Grafana: дашборд алертов
В Grafana можно дублировать алерты через Alerting → Alert rules, используя те же PromQL-выражения. Преимущество — алерты видны прямо на дашборде с контекстом.
Обязательные панели на главном дашборде:
- Alert state overview — текущие активные алерты
- Error rate — с порогом 5% как пунктирная линия
- P50/P95 latency — две линии на одном графике
- RPS — с аннотациями деплоев
Сроки
Настройка Prometheus scraping, написание 8-12 alert rules для web-приложения, конфигурация Alertmanager с Telegram, базовые дашборды: 1-2 рабочих дня.







