Задание 1

Что нужно сделать

Соберите следующие метрики:

  • node_cpu_seconds_total — режим iowait.
  • node_cpu_seconds_total — загрузка процессора в процентах. Подсказка: без CPU.
  • node_filesystem_avail_bytesmountpoint /, исключить device tmpfs. Занятое место в процентах.
  • node_load за 15 минут (примечание: тут есть подвох).

Метрики:

node_cpu_seconds_total{mode="iowait"}
100 - (avg without (cpu) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
100 - ((node_filesystem_avail_bytes{mountpoint="/",fstype!~"tmpfs"} / node_filesystem_size_bytes) * 100)
node_load15

Тоже самое в Prometheus WebUI:

Задание 2

Что нужно сделать

Используя Grafana, прикрутите визуализацию для всех метрик, что мы указали выше, плюсом добавьте свои, на ваш вкус и цвет. Пять штук будет вполне достаточно. Чтобы было интереснее, визуализируйте все метрики, используя Stat, Graph, Gauge, Time Series и Pie Chart. Для каких метрик что использовать — на ваше усмотрение.

Graph и Time Series

Раньше был тип визуализации “Graph”, у которого, как я понимаю, имелся формат “Time Series”, а теперь это просто одна сущность в виде типа визуализации “Time Series” — визуализации под именем “Graph” больше нет.

Использование Slideshow

В разделе “Что оценивается” требовалось “Использование Slideshow”.
Как я понял, имелись в виду плейлисты; использовать их буду в Задании 4, где будет несколько дашбордов, и, следовательно, будет в них нужда.

Dashboard


Data sources

Задание 3

Что нужно сделать

Сделайте четыре алерта:

  1. Хост получает очень много трафика за минуту. Пусть будет выше 50 Mb/s.
  2. Загрузка процессора выше 85%.
  3. У нас упал какой-то таргет up == 0.
  4. У нас упало ВСЁ.

Configuration

docker-compose.yml
# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
x-common: &common
  networks:
    - monitoring
  restart: unless-stopped
 
x-shared-env: &shared-env
  TZ: "Europe/Moscow"
 
name: monitoring-practical-work
 
services:
  alertmanager:
    image: prom/alertmanager:v0.28.0
    container_name: alertmanager
    depends_on:
      - envsubst
      - prometheus
    volumes:
      - ./alertmanager:/etc/alertmanager
    environment: *shared-env
    ports:
      - "127.0.0.1:9093:9093"
    command:
      - --config.file=/etc/alertmanager/alertmanager.yml
      - --log.level=debug
    <<: *common
 
  grafana:
    image: grafana/grafana:11.5.2
    container_name: grafana
    depends_on:
      - prometheus
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning/datasources/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
      - ./grafana/provisioning/dashboards/dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yml
      - ./grafana/provisioning/dashboards/json:/var/lib/grafana/dashboards
    environment:
      <<: *shared-env
      GF_AUTH_ANONYMOUS_ENABLED: "true"
      GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
    ports:
      - "127.0.0.1:3000:3000"
    <<: *common
 
  loki:
    image: grafana/loki:3.4.2
    container_name: loki
    volumes:
      - ./loki/loki-config.yml:/etc/loki/loki-config.yml:ro
    environment: *shared-env
    ports:
      - "127.0.0.1:3100:3100"
    command: -config.file=/etc/loki/loki-config.yml
    <<: *common
 
  node-exporter:
    image: prom/node-exporter:v1.9.0
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    environment: *shared-env
    ports:
      - "127.0.0.1:9100:9100"
    command:
      - --path.procfs=/host/proc
      - --path.sysfs=/host/sys
      - --collector.filesystem.ignored-mount-points
      - ^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)
    <<: *common
 
  prometheus:
    image: prom/prometheus:v3.2.1
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./prometheus/alerts.yml:/etc/prometheus/alerts.yml
      - prometheus-data:/prometheus
    environment: *shared-env
    ports:
      - "127.0.0.1:9090:9090"
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus/data
      - --web.enable-lifecycle
    <<: *common
 
  promtail:
    image: grafana/promtail:3.4.2
    container_name: promtail
    depends_on:
      - loki
    volumes:
      - ./loki/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
      - /var/log:/var/log:ro
    environment: *shared-env
    command: -config.file=/etc/promtail/promtail-config.yml
    <<: *common
 
networks:
  monitoring:
    driver: bridge
 
volumes:
  prometheus-data: {}
  grafana-data: {}
alertmanager.yml.j2
global:
  resolve_timeout: 5m
  telegram_api_url: 'https://api.telegram.org'
 
templates:
  - /etc/alertmanager/templates/telegram.tmpl
 
receivers:
  # Source: https://prometheus.io/docs/alerting/latest/configuration/#telegram_config
  - name: 'telegram'
    telegram_configs:
      - chat_id: {{ telegram.chat_id }}
        bot_token: {{ telegram.bot_token }}
        parse_mode: 'HTML'
        message: '{% raw %}{{ template "telegram.message". }}{% endraw %}'
 
route:
  group_by: ['alertname']
  group_wait: 15s
  group_interval: 30s
  repeat_interval: 4h
  receiver: telegram
  routes:
    - receiver: telegram
      continue: true
      matchers:
        - severity =~ "warning|critical"
alerts.yml
groups:
  - name: "Main rules"
    rules:
 
      - alert: HighIncomingTraffic
        expr: sum by (instance) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*"}[15s])) / (1024 * 1024) > 50
        for: 15s
        labels:
          severity: warning
        annotations:
          summary: "High Incoming network Traffic"
          description: "Incoming network Traffic > 50 MB/s on {{ $labels.instance }}"
 
      - alert: HighCpuLoad
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100) > 85
        for: 5s
        labels:
          severity: warning
        annotations:
          summary: "High CPU Load"
          description: "CPU Load > 85% on {{ $labels.instance }}"
 
      - alert: TargetDown
        expr: up == 0
        for: 5s
        labels:
          severity: critical
        annotations:
          summary: "Prometheus target is DOWN"
          description: "{{ $labels.instance }} is DOWN"
 
      - alert: AllTargetsDown
        expr: count by (job) (up) == 0
        for: 5s
        labels:
          severity: critical
        annotations:
          summary: "All Prometheus targets are DOWN"
          description: "Prometheus can't find NONE of the targets"

Alert messages

Алерты отправляются напрямую в telegram без посредников-сервисов.

Простой шаблон алерт-сообщений для telegram, позволяющий понять, что произошло и где это произошло:

telegram.tmpl
{{ define "telegram.message" }}
{{ range .Alerts }}
{{ if eq .Status "firing" }}
{{ if eq .Labels.severity "critical" }}🚨 <code>{{ .Labels.alertname }}</code> {{ else }}⚠️ <code>{{ .Labels.alertname }}</code>{{ end }}
<b>{{ .Annotations.summary }}</b> on <b>{{ .Labels.instance }}</b>
{{ .Annotations.description }}
{{ else if eq .Status "resolved" }}
✅ <b>RESOLVED</b>: <code>{{ .Labels.alertname }}</code> on <b>{{ .Labels.instance }}</b>
{{ end }}
{{ end }}
{{ end }}

Задание 4

Что нужно сделать

Пускай у нас будет условный интернет-магазин.

  1. На первом инстансе у нас веб-приложение.
  2. На втором — база данных.
  3. На третьем — Prometheus, Grafana и так далее.

Разверните систему мониторинга, чтобы за всем этим делом следить.

Описание инфраструктуры

На первом инстансе расположено веб-приложение (Gitea).
На втором — база данных (PostgreSQL), в которую и пишет Gitea свои данные.
На третьем — система мониторинга, следящая за всей инфраструктурой.

Разворачиваться всё будет в Yandex Cloud

Репозиторий с кодом инфраструктуры: gitlab.com/skillbox-shi/monitoring-practical-work

Getting started

Для запуска инфраструктуры нужно:

  1. Задать конфигурацию Yandex Cloud как провайдера для Terraform в terraform.tfvars
    cp terraform/terraform.tfvars.example terraform/terraform.tfvars
    $EDITOR terraform/terraform.tfvars
  2. Задать переменные инфраструктуры в infra_vars.yml
    cp infra_vars.example.yml infra_vars.yml
    $EDITOR infra_vars.yml
  3. Поднять инфраструктуру
    terraform -chdir=terraform apply

Разворачивание автоматизировано, поэтому terraform apply — единственное (кроме задания пары необходимых переменных), что требуется для получения готовой к работе инфраструктуры.

Terraform в итоге закончит свою работу и выдаст такой output с IP-адресами нод и URL сервисов:

stdout
service_urls = {
  "gitea" = "http://89.169.150.111:80"
  "grafana" = "http://158.160.34.246:3000"
  "prometheus" = "http://158.160.34.246:9090/targets"
}
vm_ips = {
  "mpw-db" = "89.169.151.90"
  "mpw-mon" = "158.160.34.246"
  "mpw-web" = "89.169.150.111"
}

Через некоторое время сервисы поднимутся с помощью cloud-init и можно перейти по ссылке из terraform output на веб-интерфейс Gitea, например:

На скриншоте выше был создан тестовый репозиторий в Gitea, чтобы показать, что сервис функционирует.

Поменяй дефолтный пароль на ВМ!

После поднятия ВМ не забудь поменять дефолтный пароль юзера admin, который указан в конфигурации cloud-init

terraform/cloud-init.template.yml:14
passwd: "changeme"

Grafana

Итоговый список дашбордов:

Gitea dashboard:

PostgreSQL dashboard:

Системные (node-exporter based) дашборды для каждого инстанса в настроенном плейлисте (на гифке просто показана работа плейлиста — дашборды те же):

Плейлисты в grafana не поддаются provisioning-у, поэтому в поднимаемой инфраструктуре заранее настроенных плейлистов нет — их нужно настраивать самому.

Ну, и loki:

Alerting

Кроме алертов из третьего задания сделал еще 4:

alerts.yml
groups:
  - name: "Extra rules"
    rules:
      
    - alert: PostgresqlDown
      expr: pg_up{job="db-postgres-exporter"} == 0
      for: 0m
      labels:
        severity: critical
      annotations:
        summary: Postgresql down (instance {{ $labels.instance }})
        description: "Postgresql instance is down\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"
 
    - alert: GiteaWebErrorRateHigh
      expr: sum(rate(promhttp_metric_handler_requests_total{job="web-gitea", code!="200"}[5m])) > 5
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "High error rate detected on Gitea web endpoint\n VALUE = {{ $value }}\n (instance {{ $labels.instance }})"
        description: "Non-200 HTTP requests to Gitea exceeded 5/sec over the last 5 minutes."
 
    - alert: HostOomKillDetected
      expr: (increase(node_vmstat_oom_kill[1m]) > 0)
      for: 0m
      labels:
        severity: warning
      annotations:
        summary: Host OOM kill detected (instance {{ $labels.instance }})
        description: "OOM kill detected\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"
 
    - alert: HostClockNotSynchronising
      expr: (min_over_time(node_timex_sync_status[1m]) == 0 and node_timex_maxerror_seconds >= 16)
      for: 2m
      labels:
        severity: warning
      annotations:
        summary: Host clock not synchronising (instance {{ $labels.instance }})
        description: "Clock not synchronising. Ensure NTP is configured on this host.\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"
АлертОписание
pg_up{job="db-postgres-exporter"} == 0Самый обычный up == 0 для экземпляра PostgreSQL. Поможет избежать потенциальной потери данных и простоя зависящих от БД сервисов.
sum(rate(promhttp_metric_handler_requests_total{job="web-gitea", code!="200"}[5m])) > 5Отслеживает высокую частоту ошибок (не 200) на веб-интерфейсе Gitea. Поможет выявить проблемы с приложением / сетью до того, как конечные юзеры начнут жаловаться.
(increase(node_vmstat_oom_kill[1m]) > 0)Срабатывает при обнаружении OOM kill-a процессов из-за нехватки памяти. Поможет избежать потери данных и сбоев приложений из-за нехватки ресурсов.
(min_over_time(node_timex_sync_status[1m]) == 0 and node_timex_maxerror_seconds >= 16)Срабатывание указывает на проблемы с синхронизацией системных часов. Поможет избежать огромной кучи сложновыявляемых и неочевидных ошибок.

А работа связки alertmanager - telegram уже показана в третьем задании.