Метрики: правки prometheus.yml, конфига Loki, датасорсов Grafana и запросов postgres-экспортёров вступают в силу после деплоя #3557

Merged
bot-backend merged 2 commits from fix/metrics-deploy-reload into main 2026-09-17 09:15:20 +00:00
Collaborator

Что было

Деплой метрик клал конфиги на диск, но часть сервисов их не перечитывала. Деплой при этом оставался зелёным. Часть про правила Prometheus уже смержена в #3476. Остались хвосты #3467 и #3486.

#3467: prometheus.yml, loki-config.yml и датасорсы Grafana

prometheus.yml. Файл смонтирован в контейнер одиночным файлом. В main рядом с reload стоял комментарий: «/-/reload переоткрывает файлы ПО ПУТИ заново, поэтому новый инод после git reset --hard подхватывается». Это неверно. Правила подключены каталогом, поэтому для них reload работает, а для prometheus.yml нет. Проверил на стенде prom/prometheus:v3.1.0 17.09:

host inode before: 587417, mount inode: 587417
host inode after replace: 587448, mount inode: 587417
host file job:   - job_name: new_job
reload rc=0
after reload, running config: old_job

reload возвращает rc=0, lastConfigTime обновляется, а в работе остаётся старый job. Шаг из #3476 подтверждает reload по lastConfigTime, поэтому такую ситуацию он принимает за успех. Вторая проблема в том, что promtool запускался через docker exec в работающем контейнере и проверял старый инод, а не файл, который собирались применить. То, что git reset --hard пишет изменённый файл новым инодом, проверил отдельно: 66651554 → 66651563.

loki-config.yml. Тоже одиночный файл, а перезагрузки основного конфига у Loki нет. В main не было ни пересоздания контейнера, ни сверки инода.

Датасорсы Grafana. Каталог provisioning смонтирован целиком, так что контейнер видит новый файл сразу. Но датасорсы Grafana читает только при старте: на стенде в #3475 изменённый url не применился и через 75 с. Вызова /api/admin/provisioning/datasources/reload в main нет (grep = 0). Об этом bot-backend написал в комментарии к #3467 12.09.

#3486: queries.yml у трёх postgres-экспортёров

queries.yml смонтирован одиночным файлом и читается только при старте. Джобы agent-apps и agent-infra пересоздают только alloy. Гейта на docker-compose.metrics-agent.yml не было: test_every_single_file_mount_has_a_way_to_arrive остался в закрытом #3475.

Сейчас это не проявляется, проверил 17.09 только чтением. Beget: prometheus.yml 578436=578436, loki-config.yml 569350=569350, datasources.yml 569347=569347, queries.yml у pg-exporter-infra 569352=569352. Poincare: queries.yml у обоих экспортёров 5112170=5112170. Дыра заложена в самой конструкции: сработает при первой же правке любого из этих файлов.

Что сделано

  • ops/metrics/recreate-stale-mount.sh ПРОЕКТ COMPOSE СЕРВИС ФАЙЛ ПУТЬ — один скрипт для всех одиночных маунтов. Сравнивает stat -c %i на хосте и внутри контейнера (контейнер находит через compose ps -q). Если инод разошёлся, делает up -d --no-deps --force-recreate и сверяет инод ещё раз. Если после пересоздания не сошлось, выходит с exit 1. Если контейнер не запущен, выходит с 0: старый инод держать некому. Ошибку compose ps не глушит. Если прочитать инод в контейнере не удалось, это считается расхождением. Команды скрипта прогнал только на чтение на обоих хостах (docker compose v5.1.3 / v5.5.0, ps -q → id, docker exec <id> stat → инод). На текущем состоянии скрипт ничего не пересоздаст.
  • Джоба server. promtool (check config, check rules, test rules) теперь проверяет файлы с диска: одноразовый контейнер того же образа, ops/metrics/prometheus смонтирован в /etc/prometheus (прогнал локально: верный конфиг rc=0, битый rc=1). Сверка prometheus.yml идёт между успешной проверкой и reload. Ожидание готовности перенесено после возможного пересоздания и теперь ждёт и Prometheus, и Grafana. Loki сверяется после подъёма стека. Датасорсы Grafana: POST /api/admin/provisioning/datasources/reload. Пароль раскрывается внутри контейнера. Успех засчитывается только по тексту Datasources config reloaded (строка из pkg/api/admin_provisioning.go v11.5.1), иначе exit 1. Заголовок авторизации проверил на проде GET-запросом /api/datasources: 200.
  • agent-apps и agent-infra. Сверка queries.yml у postgres-exporter-gendesign, -tradein и -infra, под тем же гейтом EXPORTER_PROFILE: up с явным именем сервиса сам включает его профиль.
  • Гейт test_metrics_single_file_mounts.py берёт одиночные маунты из обоих compose-файлов и для каждого требует путь доезда в той джобе, которая поднимает сервис: вызов скрипта с теми же аргументами или --force-recreate <сервис>. Отказ не должен глушиться || true. Производные alertmanager.yml и alertmanager_targets.gen.yml перечислены явно. В ci.yml backend-фильтр теперь срабатывает и на docker-compose.metrics-agent.yml.

Тесты

  • Скрипт проверяется исполнением с подставным docker: смотрим, что он сделал (пересоздал, не тронул или упал), а не текст скрипта. Семь сценариев: инод совпадает, инод старый, после пересоздания всё ещё старый, инод не читается, контейнер не запущен, ошибка compose, нет файла на хосте.
  • backend/tests/ops/test_metrics_single_file_mounts.py + test_3467_prometheus_reload.py на голове ec85ea18: 20 passed (11 + 9), rc=0.
  • tests/ops целиком: 145 passed, 4 failed, rc=1. Упали только test_2203_backup_trailer_grep_dashdash — известная проблема BSD mktemp на macOS, к этой ветке не относится.
  • Весь сьют backend: uv run python -m pytest tests/ -q -p no:cacheprovider: 5046 passed, 84 skipped, 4 failed (те же test_2203_*, macOS), rc=1.
  • ruff check: All checks passed. ruff format --check по изменённым тестам: чисто. bash -n по ssh-скриптам трёх джоб: ok.

Фальсификация

Сломал фикс руками: сохранил копии в scratchpad, после каждого прогона восстановил и сверил diff -q.

  1. В скрипте убрал пересоздание, в workflow убрал вызов для Loki, вернул docker exec … promtool, из проверки ответа Grafana убрал текст:
FAILED test_metrics_single_file_mounts.py::test_stale_inode_recreates_that_service_and_confirms
FAILED test_metrics_single_file_mounts.py::test_unreadable_container_view_counts_as_stale
FAILED test_metrics_single_file_mounts.py::test_every_single_file_mount_has_a_way_to_arrive[docker-compose.metrics.yml]
E  server: loki ops/metrics/loki/loki-config.yml → /etc/loki/loki-config.yml
FAILED test_3467_prometheus_reload.py::test_promtool_checks_files_from_disk_not_the_running_container
FAILED test_3467_prometheus_reload.py::test_grafana_datasources_are_reloaded_and_failure_is_red
5 failed, 14 passed — rc=1
  1. ps -q … 2>/dev/null || true и exit 0 после неудачной перепроверки:
E  AssertionError: контейнер всё ещё на старом иноде, а скрипт вышел с 0
E  AssertionError: ошибка compose проглочена, скрипт вышел с 0
2 failed, 8 passed — rc=1
  1. #3486: убрал вызов для postgres-exporter-infra, к вызову для -tradein дописал || true:
E  agent-apps: postgres-exporter-tradein — отказ проглочен: recreate-stale-mount.sh … || true
E  agent-infra: postgres-exporter-infra ops/metrics/postgres/queries.yml → /etc/pg-queries.yml
FAILED test_every_single_file_mount_has_a_way_to_arrive[docker-compose.metrics-agent.yml]
1 failed, 10 passed — rc=1

Что пересоздаст деплой

  • Deploy Metrics (правка ops/metrics/** и workflow). Beget: alert-ack и tg-relay пересоздаются, как и раньше. Prometheus и Loki пересоздаются только при расхождении инода, сейчас иноды совпадают, значит не пересоздадутся. Grafana перечитает датасорсы без рестарта. Poincare и Beget: alloy пересоздаётся, как и раньше. Экспортёры пересоздаются только при расхождении, сейчас его нет.
  • Deploy (ПТИЦА) тоже запустится, потому что изменены файлы backend/tests/**: пересоздаст backend, worker и beat на Poincare. Трейдин и скрейпер этот деплой не трогает. Если в gendesign-worker идёт долгая задача, её стоит проверить до мержа.
  • Миграций нет.

Приёмка на проде (после мержа, 17–18.09.2026)

  • #3467. В логе джобы server есть строки loki: … доехал до контейнера (инод …), prometheus: … доехал…, Prometheus: … reload подтверждён и Grafana: датасорсы перечитаны.. docker logs gendesign-grafana --since <время деплоя> показывает записи provisioning датасорсов, а StartedAt у gendesign-grafana по-прежнему 2026-09-12T11:09:20Z: датасорсы перечитаны без рестарта.
  • #3486. В логах agent-apps и agent-infra есть строки postgres-exporter-{gendesign,tradein,infra}: ops/metrics/postgres/queries.yml доехал…, StartedAt у экспортёров не изменился.
  • Эффект на реальной правке проверится при следующем изменении prometheus.yml, loki-config.yml, datasources.yml или queries.yml. После деплоя инод на хосте должен совпасть с инодом в контейнере, /api/v1/status/config у Prometheus, GET /api/datasources у Grafana и метрики экспортёра должны отдавать новое значение. Если такой правки не будет до 01.10.2026, прогнать workflow_dispatch с безвредной правкой комментария в queries.yml и сверить инод и StartedAt экспортёров.

Closes #3467
Closes #3486

🤖 Generated with Claude Code

## Что было Деплой метрик клал конфиги на диск, но часть сервисов их не перечитывала. Деплой при этом оставался зелёным. Часть про правила Prometheus уже смержена в #3476. Остались хвосты #3467 и #3486. ### #3467: prometheus.yml, loki-config.yml и датасорсы Grafana **prometheus.yml.** Файл смонтирован в контейнер одиночным файлом. В main рядом с reload стоял комментарий: «/-/reload переоткрывает файлы ПО ПУТИ заново, поэтому новый инод после `git reset --hard` подхватывается». Это неверно. Правила подключены каталогом, поэтому для них reload работает, а для prometheus.yml нет. Проверил на стенде `prom/prometheus:v3.1.0` 17.09: ``` host inode before: 587417, mount inode: 587417 host inode after replace: 587448, mount inode: 587417 host file job: - job_name: new_job reload rc=0 after reload, running config: old_job ``` reload возвращает rc=0, `lastConfigTime` обновляется, а в работе остаётся старый job. Шаг из #3476 подтверждает reload по `lastConfigTime`, поэтому такую ситуацию он принимает за успех. Вторая проблема в том, что promtool запускался через `docker exec` в работающем контейнере и проверял старый инод, а не файл, который собирались применить. То, что `git reset --hard` пишет изменённый файл новым инодом, проверил отдельно: 66651554 → 66651563. **loki-config.yml.** Тоже одиночный файл, а перезагрузки основного конфига у Loki нет. В main не было ни пересоздания контейнера, ни сверки инода. **Датасорсы Grafana.** Каталог provisioning смонтирован целиком, так что контейнер видит новый файл сразу. Но датасорсы Grafana читает только при старте: на стенде в #3475 изменённый url не применился и через 75 с. Вызова `/api/admin/provisioning/datasources/reload` в main нет (`grep` = 0). Об этом bot-backend написал в комментарии к #3467 12.09. ### #3486: queries.yml у трёх postgres-экспортёров queries.yml смонтирован одиночным файлом и читается только при старте. Джобы `agent-apps` и `agent-infra` пересоздают только alloy. Гейта на `docker-compose.metrics-agent.yml` не было: `test_every_single_file_mount_has_a_way_to_arrive` остался в закрытом #3475. **Сейчас это не проявляется, проверил 17.09 только чтением.** Beget: prometheus.yml 578436=578436, loki-config.yml 569350=569350, datasources.yml 569347=569347, queries.yml у pg-exporter-infra 569352=569352. Poincare: queries.yml у обоих экспортёров 5112170=5112170. Дыра заложена в самой конструкции: сработает при первой же правке любого из этих файлов. ## Что сделано - **`ops/metrics/recreate-stale-mount.sh ПРОЕКТ COMPOSE СЕРВИС ФАЙЛ ПУТЬ`** — один скрипт для всех одиночных маунтов. Сравнивает `stat -c %i` на хосте и внутри контейнера (контейнер находит через `compose ps -q`). Если инод разошёлся, делает `up -d --no-deps --force-recreate` и сверяет инод ещё раз. Если после пересоздания не сошлось, выходит с exit 1. Если контейнер не запущен, выходит с 0: старый инод держать некому. Ошибку `compose ps` не глушит. Если прочитать инод в контейнере не удалось, это считается расхождением. Команды скрипта прогнал только на чтение на обоих хостах (`docker compose v5.1.3 / v5.5.0`, `ps -q` → id, `docker exec <id> stat` → инод). На текущем состоянии скрипт ничего не пересоздаст. - **Джоба server.** promtool (check config, check rules, test rules) теперь проверяет файлы **с диска**: одноразовый контейнер того же образа, `ops/metrics/prometheus` смонтирован в `/etc/prometheus` (прогнал локально: верный конфиг rc=0, битый rc=1). Сверка prometheus.yml идёт между успешной проверкой и reload. Ожидание готовности перенесено после возможного пересоздания и теперь ждёт и Prometheus, и Grafana. Loki сверяется после подъёма стека. Датасорсы Grafana: `POST /api/admin/provisioning/datasources/reload`. Пароль раскрывается внутри контейнера. Успех засчитывается только по тексту `Datasources config reloaded` (строка из `pkg/api/admin_provisioning.go` v11.5.1), иначе exit 1. Заголовок авторизации проверил на проде GET-запросом `/api/datasources`: 200. - **agent-apps и agent-infra.** Сверка queries.yml у `postgres-exporter-gendesign`, `-tradein` и `-infra`, под тем же гейтом `EXPORTER_PROFILE`: `up` с явным именем сервиса сам включает его профиль. - **Гейт `test_metrics_single_file_mounts.py`** берёт одиночные маунты **из обоих compose-файлов** и для каждого требует путь доезда в той джобе, которая поднимает сервис: вызов скрипта с теми же аргументами или `--force-recreate <сервис>`. Отказ не должен глушиться `|| true`. Производные alertmanager.yml и alertmanager_targets.gen.yml перечислены явно. В `ci.yml` backend-фильтр теперь срабатывает и на `docker-compose.metrics-agent.yml`. ## Тесты - Скрипт проверяется **исполнением** с подставным `docker`: смотрим, что он сделал (пересоздал, не тронул или упал), а не текст скрипта. Семь сценариев: инод совпадает, инод старый, после пересоздания всё ещё старый, инод не читается, контейнер не запущен, ошибка compose, нет файла на хосте. - `backend/tests/ops/test_metrics_single_file_mounts.py` + `test_3467_prometheus_reload.py` на голове ec85ea18: 20 passed (11 + 9), rc=0. - `tests/ops` целиком: 145 passed, 4 failed, rc=1. Упали только `test_2203_backup_trailer_grep_dashdash` — известная проблема BSD mktemp на macOS, к этой ветке не относится. - Весь сьют backend: `uv run python -m pytest tests/ -q -p no:cacheprovider`: 5046 passed, 84 skipped, 4 failed (те же `test_2203_*`, macOS), rc=1. - `ruff check`: All checks passed. `ruff format --check` по изменённым тестам: чисто. `bash -n` по ssh-скриптам трёх джоб: ok. ## Фальсификация Сломал фикс руками: сохранил копии в scratchpad, после каждого прогона восстановил и сверил `diff -q`. 1. В скрипте убрал пересоздание, в workflow убрал вызов для Loki, вернул `docker exec … promtool`, из проверки ответа Grafana убрал текст: ``` FAILED test_metrics_single_file_mounts.py::test_stale_inode_recreates_that_service_and_confirms FAILED test_metrics_single_file_mounts.py::test_unreadable_container_view_counts_as_stale FAILED test_metrics_single_file_mounts.py::test_every_single_file_mount_has_a_way_to_arrive[docker-compose.metrics.yml] E server: loki ops/metrics/loki/loki-config.yml → /etc/loki/loki-config.yml FAILED test_3467_prometheus_reload.py::test_promtool_checks_files_from_disk_not_the_running_container FAILED test_3467_prometheus_reload.py::test_grafana_datasources_are_reloaded_and_failure_is_red 5 failed, 14 passed — rc=1 ``` 2. `ps -q … 2>/dev/null || true` и `exit 0` после неудачной перепроверки: ``` E AssertionError: контейнер всё ещё на старом иноде, а скрипт вышел с 0 E AssertionError: ошибка compose проглочена, скрипт вышел с 0 2 failed, 8 passed — rc=1 ``` 3. #3486: убрал вызов для `postgres-exporter-infra`, к вызову для `-tradein` дописал `|| true`: ``` E agent-apps: postgres-exporter-tradein — отказ проглочен: recreate-stale-mount.sh … || true E agent-infra: postgres-exporter-infra ops/metrics/postgres/queries.yml → /etc/pg-queries.yml FAILED test_every_single_file_mount_has_a_way_to_arrive[docker-compose.metrics-agent.yml] 1 failed, 10 passed — rc=1 ``` ## Что пересоздаст деплой - **Deploy Metrics** (правка `ops/metrics/**` и workflow). Beget: alert-ack и tg-relay пересоздаются, как и раньше. Prometheus и Loki пересоздаются **только при расхождении инода**, сейчас иноды совпадают, значит не пересоздадутся. Grafana перечитает датасорсы без рестарта. Poincare и Beget: alloy пересоздаётся, как и раньше. Экспортёры пересоздаются только при расхождении, сейчас его нет. - **Deploy (ПТИЦА)** тоже запустится, потому что изменены файлы `backend/tests/**`: пересоздаст backend, worker и beat на Poincare. Трейдин и скрейпер этот деплой не трогает. Если в gendesign-worker идёт долгая задача, её стоит проверить до мержа. - Миграций нет. ## Приёмка на проде (после мержа, 17–18.09.2026) - #3467. В логе джобы server есть строки `loki: … доехал до контейнера (инод …)`, `prometheus: … доехал…`, `Prometheus: … reload подтверждён` и `Grafana: датасорсы перечитаны.`. `docker logs gendesign-grafana --since <время деплоя>` показывает записи provisioning датасорсов, а `StartedAt` у gendesign-grafana по-прежнему `2026-09-12T11:09:20Z`: датасорсы перечитаны без рестарта. - #3486. В логах agent-apps и agent-infra есть строки `postgres-exporter-{gendesign,tradein,infra}: ops/metrics/postgres/queries.yml доехал…`, `StartedAt` у экспортёров не изменился. - Эффект на реальной правке проверится при следующем изменении prometheus.yml, loki-config.yml, datasources.yml или queries.yml. После деплоя инод на хосте должен совпасть с инодом в контейнере, `/api/v1/status/config` у Prometheus, `GET /api/datasources` у Grafana и метрики экспортёра должны отдавать новое значение. Если такой правки не будет до **01.10.2026**, прогнать `workflow_dispatch` с безвредной правкой комментария в queries.yml и сверить инод и `StartedAt` экспортёров. Closes #3467 Closes #3486 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 2 commits 2026-09-17 07:49:06 +00:00
Хвосты #3467, не попавшие в main вместе с #3476:

- prometheus.yml смонтирован одним файлом: после git reset --hard reload
  перечитывал СТАРЫЙ инод с rc=0 и новым lastConfigTime (стенд
  prom/prometheus:v3.1.0, 17.09). Комментарий в деплое утверждал обратное.
  Теперь promtool проверяет файлы С ДИСКА одноразовым контейнером, а при
  расхождении инода контейнер пересоздаётся до reload.
- loki-config.yml — тот же пофайловый маунт, перезагрузки у Loki нет:
  пересоздание при расхождении инода.
- Датасорсы Grafana применяются только при старте: POST
  /api/admin/provisioning/datasources/reload, отказ роняет деплой.

Общий шаг — ops/metrics/recreate-stale-mount.sh: пересоздаёт только при
расхождении инода и перепроверяет после; тесты исполняют его с подставным
docker. Гейт берёт пофайловые маунты из docker-compose.metrics.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(metrics): правка queries.yml доезжает до postgres-экспортёров (#3486)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 19s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m59s
CI / openapi-codegen-check (pull_request) Successful in 3m19s
CI / backend-tests (pull_request) Successful in 8m6s
ec85ea1861
queries.yml смонтирован трём экспортёрам одним файлом и читается только при
старте, а агентские джобы деплоя пересоздавали лишь alloy: правка ложилась на
диск новым инодом, экспортёры продолжали отдавать старые запросы при зелёном
деплое. Разрыв латентный — 17.09 иноды хоста и контейнеров совпадают
(Poincare 5112170, Beget 569352).

agent-apps и agent-infra после подъёма сверяют инод queries.yml у своих
экспортёров через ops/metrics/recreate-stale-mount.sh и пересоздают только при
расхождении, под гейтом профиля. Гейт пофайловых маунтов теперь читает и
docker-compose.metrics-agent.yml (сервис → джоба по профилю), а ci.yml
запускает backend-тесты на правку этого файла.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit 1812a1ed18 into main 2026-09-17 09:15:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3557
No description provided.