Мониторинг: от "прод упал, узнали от пользователей" до "прод думает что упасть и уже получил алерт"
Есть два типа DevOps. Первые узнают об авариях от пользователей. Вторые — за 5 минут до того как проблема станет аварией. Я прошёл путь от первого ко второму. Это было долго, больно и неочевидно. Расскажу как.
Уровень 0: Никакого мониторинга
Это страшное место. Узнаёшь что что-то не так, когда:
Пишет пользователь в поддержку
Звонит CEO в воскресенье
Видишь в Twitter "ваш сайт сломан"
Это лечится быстро — после первого воскресного звонка от CEO инстинкт самосохранения быстро мотивирует к действию.
Уровень 1: Uptime monitoring
Самое простое: проверяем что сайт отвечает:
class HealthCheck extends BaseCommand
{
protected $name = 'health:check';
public function run(array $params): void
{
$endpoints = config('HealthCheck')->endpoints;
foreach ($endpoints as $endpoint) {
$start = microtime(true);
try {
$response = \Config\Services::curlrequest()->get($endpoint['url'], [
'timeout' => 10,
'http_errors' => false,
]);
$duration = (microtime(true) - $start) * 1000;
$status = $response->getStatusCode();
if ($status !== 200 || $duration > $endpoint['threshold_ms']) {
$this->alertTeam($endpoint, $status, $duration);
}
} catch (\Exception $e) {
$this->alertTeam($endpoint, 0, 0, $e->getMessage());
}
}
}
}
Лучше чем ничего. Но это как термометр в одной комнате большого дома.
Уровень 2: Application метрики
Prometheus + Grafana. В CI4 интегрируем через After-фильтр:
<?php
namespace App\Filters;
class MetricsFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null): void
{
$GLOBALS['_request_start_time'] = microtime(true);
}
public function after(
RequestInterface $request,
ResponseInterface $response,
$arguments = null
): ResponseInterface {
$duration = microtime(true) - ($GLOBALS['_request_start_time'] ?? microtime(true));
$status = $response->getStatusCode();
$route = service('router')->controllerName();
$method = $request->getMethod();
$metrics = service('metricsCollector');
$metrics->histogram(
'http_request_duration_seconds',
$duration,
['method' => $method, 'route' => $route, 'status' => $status]
);
$metrics->counter(
'http_requests_total',
1,
['method' => $method, 'route' => $route, 'status' => (string)intdiv($status, 100) . 'xx']
);
if ($status >= 500) {
$metrics->counter('http_errors_total', 1, ['route' => $route]);
}
return $response;
}
}
Уровень 3: Предиктивные алерты
Настоящий прорыг — когда алерты стали срабатывать ДО того как всё упало.
Пример: за 8-12 минут до OOM Redis всегда происходило:
Memory usage рос быстрее обычного (+15% за 5 минут)
Cache hit rate начинал падать
Eviction rate был 0
Prometheus alerting rule:
groups:
- name: redis_predictive
rules:
- alert: RedisMemoryGrowthAnomaly
expr: |
(
redis_memory_used_bytes / redis_memory_max_bytes > 0.75
) and (
rate(redis_memory_used_bytes[5m]) > 0
) and (
redis_stat_keyspace_hits /
(redis_stat_keyspace_hits + redis_stat_keyspace_misses) < 0.85
)
for: 3m
labels:
severity: warning
annotations:
summary: "Redis memory growing fast, potential OOM in ~10min"
- alert: MySQLConnectionPoolDanger
expr: |
mysql_global_status_threads_connected /
mysql_global_variables_max_connections > 0.8
for: 2m
labels:
severity: critical
annotations:
summary: "MySQL connection pool at {{ $value | humanizePercentage }}"
Уровень 4: Synthetic monitoring
Проверяем что реальный пользовательский сценарий работает end-to-end. CLI-команда CI4 каждые 2 минуты симулирует key user journey:
namespace App\Commands\Synthetic;
class CheckoutFlow extends BaseCommand
{
protected $name = 'synthetic:checkout';
public function run(array $params): void
{
$client = service('curlrequest');
$baseUrl = env('SYNTHETIC_BASE_URL');
$metrics = service('metricsCollector');
$start = microtime(true);
$step = 'init';
try {
$step = 'login';
$loginResp = $client->post($baseUrl . '/api/auth/login', [
'json' => ['email' => env('SYNTHETIC_USER_EMAIL'), 'password' => env('SYNTHETIC_USER_PASSWORD')],
'timeout' => 5,
]);
assert($loginResp->getStatusCode() === 200);
$token = json_decode($loginResp->getBody(), true)['token'];
$step = 'product_list';
$productsResp = $client->get($baseUrl . '/api/products', [
'headers' => ['Authorization' => "Bearer $token"],
'timeout' => 3,
]);
assert($productsResp->getStatusCode() === 200);
// ... другие шаги
$metrics->counter('synthetic_checkout_flow_success_total', 1);
$this->write("✅ Checkout flow OK
"); } catch (\Throwable $e) {
$metrics->counter('synthetic_checkout_flow_failure_total', 1, ['step' => $step]);
service('alertManager')->fire(level: 'critical', title: "Synthetic checkout failed at: {$step}", body: $e->getMessage()); } } }
Текущий стек мониторинга
Prometheus — сбор метрик
Grafana — визуализация и алертинг
Loki — агрегация логов
Alertmanager — маршрутизация (Telegram, PagerDuty, Slack)
Synthetic monitoring — CI4 CLI команды в cron
OpenTelemetry + Jaeger — distributed tracing
MTTR за год:
Период | MTTR |
|---|---|
До нормального мониторинга | 47 минут |
После | 8 минут |
Это не просто красивая цифра. Это живые деньги. Мониторинг — это бизнес-инвестиция с прямым ROI. 📈
Recommended Comments