fix(observability): контейнерные метрики терялись целиком + два хвоста стека
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped

Три независимых дефекта, найденных на живом проде после подъёма стека метрик.

1. cAdvisor-метрики не доезжали ВООБЩЕ. В Prometheus ноль имён container_*
   при 2034 именах всего, хотя cAdvisor отдаёт 880 рядов, scrape-таргет в
   alloy health=up с последним скрейпом 10 мс назад, а remote_write рабочий
   (node/postgres идут через него же и доезжают). Методом исключения — потери
   в prometheus.relabel.cadvisor_trim, во втором правиле:

       rule { source_labels = ["name"], regex = "", action = "drop" }

   Замысел был выкинуть безымянные cgroup-ряды (id="/"). Но regex в Alloy
   документированно дефолтится в (.*), и пустая строка неотличима от
   незаданного значения — такой drop рискует выкидывать вообще всё, что и
   наблюдалось. Заменено на однозначное keep regex=".+" — тот же замысел,
   без зависимости от того, как трактуется пустой regex.

2. healthcheck alloy не мог пройти никогда: дёргал wget, которого в образе
   grafana/alloy нет (как и curl, и nc). Контейнер вечно unhealthy при
   полностью исправном alloy — ложная тревога, маскирующая настоящие сбои.
   Заменено на сырой HTTP через bash /dev/tcp, без внешних утилит.

3. Prometheus раз в минуту писал "lookup alertmanager: no such host" и держал
   up{job="alertmanager"}=0. Alertmanager намеренно за профилем alerts до
   решения #3078 — дефект не в профиле, а в безусловной ссылке на сервис.
   Оба места (alerting.alertmanagers и job_name: alertmanager) переведены на
   file_sd_configs с файлом целей, по умолчанию пустым: целей нет — ошибок
   тоже нет. Prometheus перечитывает file_sd на лету, поэтому включение
   профиля сведётся к наполнению файла, без рестарта и правки конфига.
   Файл целей смонтирован в сервис prometheus явным volume.

Проверено на живом хосте, не на глаз:
- alloy fmt обоих .alloy в одноразовом контейнере grafana/alloy:v1.6.1 - exit 0
- promtool check config в prom/prometheus:v3.1.0 - valid, 16 rules found
- механизм нового healthcheck выполнен внутри работающего gendesign-alloy:
  первая строка ответа "HTTP/1.0 200 OK", grep матчится, RESULT=HEALTHY
- наличие bash/head/grep/printf в образе alloy подтверждено command -v
This commit is contained in:
bot-backend 2026-08-26 13:14:28 +03:00
parent 9ede71d0b1
commit c6e15954ba
6 changed files with 51 additions and 16 deletions

View file

@ -75,7 +75,13 @@ services:
mem_limit: 512m
logging: *default-logging
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:12345/-/ready || exit 1"]
# В образе grafana/alloy нет wget/curl/nc, только bash — шлём
# сырой HTTP-запрос через /dev/tcp и проверяем код 200 в ответе.
test:
[
"CMD-SHELL",
"bash -c 'exec 3<>/dev/tcp/localhost/12345 && printf \"GET /-/ready HTTP/1.0\r\n\r\n\" >&3 && head -1 <&3 | grep -q 200' || exit 1",
]
interval: 30s
timeout: 10s
retries: 5

View file

@ -51,6 +51,10 @@ services:
volumes:
- ./ops/metrics/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./ops/metrics/prometheus/rules:/etc/prometheus/rules:ro
# file_sd для job "alertmanager" — см. комментарий в prometheus.yml.
# Забыть этот монт — значит вернуть "no such host" из-за отсутствующего
# файла целей (был инцидент, когда похожий пропущенный монт положил Caddy).
- ./ops/metrics/prometheus/alertmanager_targets.yml:/etc/prometheus/alertmanager_targets.yml:ro
- prometheus_data:/prometheus
expose:
- "9090"

View file

@ -92,10 +92,14 @@ prometheus.relabel "cadvisor_trim" {
action = "keep"
}
// Служебные контейнеры docker без имени только зашумляют графики.
// keep .+ вместо drop с пустым regex: в Alloy незаданный regex
// документированно дефолтится в (.*), а пустая строка неотличима от
// незаданного значения — такой drop рискует выкинуть вообще все ряды.
rule {
source_labels = ["name"]
regex = ""
action = "drop"
regex = ".+"
action = "keep"
}
}

View file

@ -64,10 +64,13 @@ prometheus.relabel "cadvisor_trim" {
}
// Служебные контейнеры docker без имени только зашумляют графики.
// keep .+ вместо drop с пустым regex: в Alloy незаданный regex
// документированно дефолтится в (.*), а пустая строка неотличима от
// незаданного значения — такой drop рискует выкинуть вообще все ряды.
rule {
source_labels = ["name"]
regex = ""
action = "drop"
regex = ".+"
action = "keep"
}
}

View file

@ -0,0 +1,14 @@
# file_sd target-файл для job "alertmanager" (см. prometheus.yml).
#
# Пустой список ([]) — штатное состояние, пока профиль "alerts" в
# docker-compose.metrics.yml выключен (#3078, канал доставки не решён):
# сервиса alertmanager не существует, и Prometheus просто не видит для
# него ни одной цели — без ошибок разрешения имени и без up{job=...}=0.
#
# Prometheus перечитывает этот файл на лету (file_sd), рестарт не нужен.
# Когда профиль alerts включат — заменить [] на:
#
# - targets: ["alertmanager:9093"]
# labels:
# host: infra
[]

View file

@ -19,15 +19,18 @@ global:
rule_files:
- /etc/prometheus/rules/*.yml
# Пока профиль alerts выключен, этой цели не существует и Prometheus раз в
# интервал пишет в лог, что не смог её разрешить. Это шум, а не отказ: правила
# считаются и видны в интерфейсе, просто уведомлять некому. Молча выключать
# alerting не стали — тогда включение алертов потребовало бы правки конфига,
# а не одной переменной.
# Пока профиль alerts выключен, сервиса alertmanager не существует. Со
# static_configs это раз в интервал давало "lookup alertmanager: no such
# host" в логах и up{job="alertmanager"}=0. file_sd_configs вместо
# static_configs делает цель опциональной идиоматично для Prometheus: файл
# целей по умолчанию содержит пустой список ([]) — алертов нет и ошибок
# разрешения имени тоже нет. Prometheus перечитывает file_sd на лету, так что
# включение профиля alerts сводится к наполнению файла, без рестарта и без
# правки этого конфига.
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
- file_sd_configs:
- files: ["/etc/prometheus/alertmanager_targets.yml"]
scrape_configs:
# Сам Prometheus. Нужен не для красоты: по нему строится алерт на здоровье
@ -38,11 +41,12 @@ scrape_configs:
labels:
host: infra
# Тот же опциональный source, что и в alerting.alertmanagers выше: пока
# профиль alerts не включён, файл целей пуст — up{job="alertmanager"}
# просто не появится в выдаче вместо ошибки "no such host".
- job_name: alertmanager
static_configs:
- targets: ["alertmanager:9093"]
labels:
host: infra
file_sd_configs:
- files: ["/etc/prometheus/alertmanager_targets.yml"]
- job_name: loki
static_configs: