June 11, 2025

Модуль 9: Мониторинг и наблюдаемость в DevOps

Курс DevOps для новичков 2025


📊 Зачем нужен мониторинг?

Мониторинг — это процесс непрерывного наблюдения за состоянием системы, сбора метрик и генерации предупреждений. Без мониторинга вы узнаете о проблемах только когда пользователи начнут жаловаться. Это как водить машину с закрытыми глазами — опасно и безответственно.

По данным исследований 2025 года, 87% компаний с зрелыми практиками мониторинга обнаруживают проблемы до того, как они повлияют на пользователей1. Prometheus и Grafana стали стандартом де-факто для мониторинга в DevOps — как нож и вилка, вместе они делают мониторинг инфраструктуры и приложений простым и наглядным1.


🎯 Четыре золотых сигнала мониторинга

Google SRE определила четыре ключевые метрики, которые должен отслеживать любой сервис:

1. Latency (Задержка)

Время ответа на запросы. Критично для пользовательского опыта.

2. Traffic (Трафик)

Количество запросов в секунду. Показывает нагрузку на систему.

3. Errors (Ошибки)

Процент неуспешных запросов. Индикатор качества сервиса.

4. Saturation (Насыщенность)

Использование ресурсов (CPU, память, диск). Предупреждает о приближении к лимитам.


🔥 Prometheus — сердце мониторинга

Prometheus — open-source система мониторинга, которая собирает метрики, хранит их в виде временных рядов и предоставляет мощный язык запросов PromQL1. Это движок мониторинга, который сам опрашивает сервисы, собирает данные и сохраняет их в своей базе1.

Архитектура Prometheus

Prometheus Server — основной компонент, который:

  • Собирает метрики с целевых систем (pull-модель)
  • Хранит данные в формате временных рядов
  • Предоставляет HTTP API для запросов
  • Оценивает правила алертов

Exporters — программы, которые собирают метрики:

  • Node Exporter — метрики ОС Linux
  • cAdvisor — метрики Docker контейнеров
  • MySQL Exporter — метрики базы данных
  • Blackbox Exporter — проверка доступности сервисов

Alertmanager — компонент для управления алертами и уведомлениями


⚙️ Установка стека мониторинга

Docker Compose для полного стека

# docker-compose.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./prometheus/rules:/etc/prometheus/rules
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=200h'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($|/)'
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    privileged: true
    restart: unless-stopped

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:

Конфигурация Prometheus

# prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "rules/*.yml"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'my-app'
    static_configs:
      - targets: ['my-app:3000']
    metrics_path: '/metrics'
    scrape_interval: 5s

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093

📈 Основы PromQL

PromQL — язык запросов Prometheus для анализа метрик:

Базовые запросы

# Текущее значение метрики
up

# Фильтрация по лейблам
up{job="node-exporter"}

# Арифметические операции
node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes

# Процент использования памяти
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

# Загрузка CPU за последние 5 минут
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

Функции времени

# Количество запросов в секунду
rate(http_requests_total[5m])

# Увеличение за период
increase(http_requests_total[1h])

# 95-й процентиль времени ответа
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Среднее значение за период
avg_over_time(cpu_usage[10m])

🛠️ Практические задания

Задание 9.1: Запуск стека мониторинга

# 1. Создание структуры проекта
mkdir monitoring-stack
cd monitoring-stack
mkdir -p prometheus/rules grafana/provisioning alertmanager

# 2. Создание docker-compose.yml (используйте пример выше)

# 3. Запуск стека
docker-compose up -d

# 4. Проверка сервисов
curl http://localhost:9090/targets    # Prometheus targets
curl http://localhost:3000           # Grafana (admin/admin123)
curl http://localhost:9100/metrics   # Node Exporter metrics

Задание 9.2: Мониторинг приложения

// app.js - Node.js приложение с метриками
const express = require('express');
const promClient = require('prom-client');

const app = express();
const port = process.env.PORT || 3000;

// Создание реестра метрик
const register = new promClient.Registry();

// Счетчик запросов
const httpRequestsTotal = new promClient.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
  registers: [register]
});

// Гистограмма времени ответа
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
  registers: [register]
});

// Gauge для активных подключений
const activeConnections = new promClient.Gauge({
  name: 'active_connections',
  help: 'Number of active connections',
  registers: [register]
});

// Middleware для сбора метрик
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    const route = req.route?.path || req.path;
    
    httpRequestsTotal
      .labels(req.method, route, res.statusCode.toString())
      .inc();
    
    httpRequestDuration
      .labels(req.method, route, res.statusCode.toString())
      .observe(duration);
  });
  
  next();
});

// Эндпоинт для метрик
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// API эндпоинты
app.get('/', (req, res) => {
  res.json({ 
    message: 'Hello Monitoring!',
    timestamp: new Date().toISOString()
  });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.get('/api/users', (req, res) => {
  // Симуляция времени обработки
  setTimeout(() => {
    res.json({ users: ['Alice', 'Bob', 'Charlie'] });
  }, Math.random() * 100);
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Задание 9.3: Настройка алертов

# prometheus/rules/alerts.yml
groups:
  - name: system.rules
    rules:
      - alert: InstanceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Instance {{ $labels.instance }} down"
          description: "{{ $labels.instance }} has been down for more than 1 minute."

      - alert: HighCPUUsage
        expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is above 80% for more than 2 minutes."

      - alert: HighMemoryUsage
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage is above 90% for more than 2 minutes."

      - alert: DiskSpaceLow
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Low disk space on {{ $labels.instance }}"
          description: "Disk space is below 10% on {{ $labels.instance }}."

      - alert: HighErrorRate
        expr: rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 10% for more than 5 minutes."

Конфигурация Alertmanager

# alertmanager/alertmanager.yml
global:
  smtp_smarthost: 'localhost:587'
  smtp_from: 'alerts@company.com'

route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'web.hook'

receivers:
- name: 'web.hook'
  email_configs:
  - to: 'admin@company.com'
    subject: 'Alert: {{ .GroupLabels.alertname }}'
    body: |
      {{ range .Alerts }}
      Alert: {{ .Annotations.summary }}
      Description: {{ .Annotations.description }}
      {{ end }}
  
  slack_configs:
  - api_url: 'YOUR_SLACK_WEBHOOK_URL'
    channel: '#alerts'
    title: 'Alert: {{ .GroupLabels.alertname }}'
    text: |
      {{ range .Alerts }}
      {{ .Annotations.summary }}
      {{ .Annotations.description }}
      {{ end }}

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'dev', 'instance']

📊 Grafana — визуализация данных

Создание дашборда

json
# grafana/provisioning/dashboards/system-overview.json
{
  "dashboard": {
    "title": "System Overview",
    "tags": ["system", "monitoring"],
    "panels": [
      {
        "title": "CPU Usage",
        "type": "stat",
        "targets": [
          {
            "expr": "100 - (avg(irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
            "legendFormat": "CPU Usage %"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "percent",
            "thresholds": {
              "steps": [
                {"color": "green", "value": null},
                {"color": "yellow", "value": 70},
                {"color": "red", "value": 90}
              ]
            }
          }
        }
      },
      {
        "title": "Memory Usage",
        "type": "timeseries",
        "targets": [
          {
            "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100",
            "legendFormat": "Memory Usage %"
          }
        ]
      },
      {
        "title": "HTTP Requests Rate",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(http_requests_total[5m])",
            "legendFormat": "{{ method }} {{ route }}"
          }
        ]
      }
    ]
  }
}

🔍 Логирование с ELK Stack

Docker Compose для ELK

# elk-stack.yml
version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data

  kibana:
    image: kibana:8.11.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

  logstash:
    image: logstash:8.11.0
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    ports:
      - "5000:5000"
    depends_on:
      - elasticsearch

volumes:
  elasticsearch_data:

Конфигурация Logstash

ruby
# logstash/pipeline/logstash.conf
input {
  beats {
    port => 5044
  }
  
  tcp {
    port => 5000
    codec => json
  }
}

filter {
  if [fields][service] == "nginx" {
    grok {
      match => { "message" => "%{NGINXACCESS}" }
    }
    
    date {
      match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "logs-%{+YYYY.MM.dd}"
  }
  
  stdout {
    codec => rubydebug
  }
}

📖 Полезные ресурсы


✅ Чек-лист модуля

  • Понимаю важность мониторинга в DevOps
  • Знаю четыре золотых сигнала мониторинга
  • Установил и настроил Prometheus
  • Освоил основы PromQL для запросов метрик
  • Настроил сбор метрик с Node Exporter и cAdvisor
  • Создал дашборды в Grafana
  • Настроил алерты в Prometheus и Alertmanager
  • Добавил метрики в собственное приложение
  • Изучил основы логирования с ELK Stack
  • Понимаю разницу между мониторингом и наблюдаемостью

🚀 Что дальше?

После освоения мониторинга переходите к Модулю 10: Облачные платформы для изучения современных облачных решений и их интеграции с системами мониторинга.