Деплой метрик перечитывает конфигурацию, а не только кладёт её на диск (#3467) #3475

Closed
bot-backend wants to merge 2 commits from fix/metrics-deploy-reloads-prometheus into main
Collaborator

Что было

Правка ops/metrics/prometheus/rules/infra.yml доезжает до диска и не вступает в силу. Деплой при этом зелёный.

Улика (проверено чтением на инфраструктурном хосте 12.09.2026):

  1. #3464 поправил два правила в ops/metrics/prometheus/rules/infra.yml и смержен в main (cfcdb939).
  2. Deploy Metrics / server (push) на этом коммите — success.
  3. Новый текст правил лежит на диске и виден внутри контейнера:
    docker exec gendesign-prometheus grep -n "container_memory_working_set_bytes" /etc/prometheus/rules/infra.yml → строка 139, новая формулировка.
  4. API отдаёт СТАРОЕ правило: GET /api/v1/rulesContainerNearMemoryLimit с выражением container_spec_memory_limit_bytes{name!=""} > 0 and ....
  5. Причина: контейнер не перезапускался с 2026-08-27T18:12:24Z (16 суток), а deploy-metrics.yml перезагружал только Caddy. Prometheus не перезагружался никак.
  6. --web.enable-lifecycle у Prometheus включён (docker inspect … .Args), то есть POST /-/reload доступен и всё это время был доступен.

Следствие. Любая правка ops/metrics/prometheus/** (правила, prometheus.yml, scrape-конфиги) с момента появления стека доезжала до диска и вступала в силу только при случайном пересоздании контейнера. Тот же класс, что #3448: объявлено ≠ исполняется, и отрицательного признака у него нет.

Что сделано

Prometheus. Проверка конфигурации — потом применение, по образцу Caddy из этого же файла:

  • promtool check config и promtool check rules одноразовым контейнером того же образа (prom/prometheus:v3.1.0) по файлам с диска;
  • затем POST http://localhost:9090/-/reload из работающего контейнера;
  • провал проверки роняет шаг с внятным сообщением и без reload: работающий Prometheus остаётся на прежней конфигурации.

Почему проверка одноразовым контейнером, а не docker exec в работающий: prometheus.yml подключён бинд-маунтом одного файла, и после git reset --hard работающий контейнер читает старый инод — exec проверял бы не тот текст, который применится.

Правка по ревью: проверка переехала выше docker compose up -d. В первой версии она стояла между подъёмом и перезагрузкой, и утверждение «битый конфиг остановит деплой до пересоздания контейнера» было верно только для ветки с инодом: up -d пересоздаёт контейнер при смене образа и успел бы применить битый конфиг раньше. Теперь порядок честный, и его сторожит test_config_check_precedes_any_application.

Почему обе команды promtool: на стенде при пустом каталоге правил check config печатает «0 rule files found» и проходит, падает только check rules.

Соседи по стеку — вердикт и основание

Сервис Деплой меняет его конфигурацию? Кто перечитывает Вердикт
Prometheus — правила (маунт каталога) да, git reset --hard никто разрыв → проверка + POST /-/reload
Prometheusprometheus.yml (маунт файла) да контейнер держит старый инод, reload перечитывает его же разрыв → сверка инода + пересоздание
alertmanager_targets.gen.yml рендерится усечением на месте (:>), инод сохраняется Prometheus сам перечитывает file_sd разрыва нет, так и задумано
Alertmanager перерисовывается каждый деплой up -d --force-recreate alertmanager уже есть разрыва нет: контейнер StartedAt 2026-09-12T10:13:14Z — то есть пересоздание реально отрабатывает
Loki да, git reset --hard никто: ручки перезагрузки нет вовсе (POST /reload → 404 на живом контейнере), а маунт одного файла держит инод разрыв → сверка инода + пересоздание
Grafana — дашборды да провайдер пересканирует сам (updateIntervalSeconds: 30) разрыва нет — замер: новый файл появился без рестарта
Grafana — датасорсы да никто: провиженинг датасорсов применяется только при старте разрывPOST /api/admin/provisioning/datasources/reload (замер: изменённый url не применился и через 75 с, после ручки применился сразу)
Alloy (оба хоста) да up -d --force-recreate alloy + гейт по иноду уже есть разрыва нет: StartedAt 2026-09-12T10:13:38Z
alert-ack (app.py маунтом одного файла) да никто: python читает файл один раз при старте, up -d контейнер не трогает разрыв → сверка инода + пересоздание

Пароль Grafana берётся из окружения самого контейнера — в аргументы команд на хосте и в лог деплоя он не попадает.

Гейт

backend/tests/ops/test_3467_prometheus_reload.py — 7 проверок. Краснеет, если шаг reload исчез, оторвался от проверки конфигурации, если ветка «проверка не прошла» перестала обрывать деплой, если снят --web.enable-lifecycle, или если в compose появился новый одиночный файловый маунт без пути доезда (список берётся из compose, а не из воркфлоу).

Фальсификация (три сценария, git stash не использовался):

  • шаг reload убранtest_deploy_reloads_prometheus: «в серверной джобе нет POST /-/reload для Prometheus — правки правил лягут на диск и не вступят в силу, как это и было с #3464»; test_reload_comes_after_config_check: «в деплое нет перезагрузки Prometheus». 2 failed, 5 passed
  • проверка убрана, reload оставленtest_reload_comes_after_config_check: «в деплое нет проверки promtool (check config + check rules)»; test_failed_check_stops_deploy_without_reload: «проверка promtool не закрыта веткой обработки отказа». 2 failed, 5 passed
  • добавлен новый файловый маунт без сверки инодаtest_every_single_file_mount_has_a_way_to_arrive: «ожидались три одиночных файловых маунта …, найдено: […4 шт…]». 1 failed, 6 passed

После возврата — 7 passed; весь backend/tests/ops86 passed, 10 failed, и эти 10 краснеют и на main: локальный python 3.9 против write_text(newline=…) (нужен 3.10+), в CI стоит 3.12.

Правки по ревью (второй коммит)

Гейт был слеп в двух местах — обе мутации ревьюера проходили насквозь, обе теперь краснеют:

Мутация Было Стало
к POST /-/reload дописано || true 7 passed (regex кончался на URL, хвост строки не смотрел) 2 failed, 6 passedtest_deploy_reloads_prometheus, test_reload_comes_after_config_check
новый одиночный файловый маунт с :ro 1 failed 1 failed, 7 passedtest_every_single_file_mount_has_a_way_to_arrive
новый одиночный файловый маунт без :ro 7 passed (распознавание маунта требовало :ro) 1 failed, 7 passed — тот же тест
проверку promtool вернули под up -d проверки не было 1 failed, 7 passedtest_config_check_precedes_any_application

Права (:ro) к типу маунта отношения не имеют — инод он держит одинаково, поэтому требование снято из распознавания. Якоря границ серверной джобы и блока триггеров читаются через find с внятным assert: при переименовании шага гейт скажет, что сломалось, а не даст ValueError.

Прогоны: tests/ops/test_3467_prometheus_reload.py8 passed; весь backend/tests/ops87 passed, 10 failed (те же 10 красные и на main: локальный python 3.9 против write_text(newline=…), в CI 3.12). yaml.safe_load + bash -n по всем трём джобам — чисто.

ci.yml не трогал: фильтр backend там не перечисляет deploy-metrics.yml и docker-compose.metrics.ymlрасширен в PR #3465, чтобы две ветки не дрались за одни строки.

Критерий приёмки

После мержа и прохода Deploy Metrics / server:

GET http://localhost:9090/api/v1/rules  →  ContainerNearMemoryLimit

отдаёт выражение, начинающееся с container_memory_working_set_bytes, то есть правка #3464 наконец вступает в силу. Это и есть доказательство, что шаг работает: сейчас там container_spec_memory_limit_bytes.

Прод в рамках этой работы не трогался: только чтение (docker inspect, docker exec … stat/wget -qO- /api/v1/…, grep по файлам).

Closes #3467

## Что было Правка `ops/metrics/prometheus/rules/infra.yml` доезжает до диска и **не вступает в силу**. Деплой при этом зелёный. Улика (проверено чтением на инфраструктурном хосте 12.09.2026): 1. #3464 поправил два правила в `ops/metrics/prometheus/rules/infra.yml` и смержен в main (`cfcdb939`). 2. `Deploy Metrics / server (push)` на этом коммите — success. 3. Новый текст правил **лежит на диске** и **виден внутри контейнера**: `docker exec gendesign-prometheus grep -n "container_memory_working_set_bytes" /etc/prometheus/rules/infra.yml` → строка 139, новая формулировка. 4. **API отдаёт СТАРОЕ правило**: `GET /api/v1/rules` → `ContainerNearMemoryLimit` с выражением `container_spec_memory_limit_bytes{name!=""} > 0 and ...`. 5. Причина: контейнер не перезапускался с **2026-08-27T18:12:24Z** (16 суток), а `deploy-metrics.yml` перезагружал только Caddy. Prometheus не перезагружался никак. 6. `--web.enable-lifecycle` у Prometheus включён (`docker inspect … .Args`), то есть `POST /-/reload` доступен и всё это время был доступен. **Следствие.** Любая правка `ops/metrics/prometheus/**` (правила, `prometheus.yml`, scrape-конфиги) с момента появления стека доезжала до диска и вступала в силу только при случайном пересоздании контейнера. Тот же класс, что #3448: объявлено ≠ исполняется, и отрицательного признака у него нет. ## Что сделано **Prometheus.** Проверка конфигурации — потом применение, по образцу Caddy из этого же файла: - `promtool check config` **и** `promtool check rules` одноразовым контейнером того же образа (`prom/prometheus:v3.1.0`) по файлам **с диска**; - затем `POST http://localhost:9090/-/reload` из работающего контейнера; - провал проверки роняет шаг с внятным сообщением и **без** reload: работающий Prometheus остаётся на прежней конфигурации. Почему проверка одноразовым контейнером, а не `docker exec` в работающий: `prometheus.yml` подключён бинд-маунтом одного файла, и после `git reset --hard` работающий контейнер читает старый инод — `exec` проверял бы не тот текст, который применится. **Правка по ревью:** проверка переехала **выше `docker compose up -d`**. В первой версии она стояла между подъёмом и перезагрузкой, и утверждение «битый конфиг остановит деплой до пересоздания контейнера» было верно только для ветки с инодом: `up -d` пересоздаёт контейнер при смене образа и успел бы применить битый конфиг раньше. Теперь порядок честный, и его сторожит `test_config_check_precedes_any_application`. Почему обе команды promtool: на стенде при пустом каталоге правил `check config` печатает «0 rule files found» и **проходит**, падает только `check rules`. ## Соседи по стеку — вердикт и основание | Сервис | Деплой меняет его конфигурацию? | Кто перечитывает | Вердикт | |---|---|---|---| | **Prometheus** — правила (маунт каталога) | да, `git reset --hard` | никто | **разрыв** → проверка + `POST /-/reload` | | **Prometheus** — `prometheus.yml` (маунт файла) | да | контейнер держит старый инод, reload перечитывает его же | **разрыв** → сверка инода + пересоздание | | `alertmanager_targets.gen.yml` | рендерится усечением на месте (`:>`), инод сохраняется | Prometheus сам перечитывает file_sd | разрыва нет, так и задумано | | **Alertmanager** | перерисовывается каждый деплой | `up -d --force-recreate alertmanager` уже есть | разрыва нет: контейнер `StartedAt 2026-09-12T10:13:14Z` — то есть пересоздание реально отрабатывает | | **Loki** | да, `git reset --hard` | **никто**: ручки перезагрузки нет вовсе (`POST /reload` → 404 на живом контейнере), а маунт одного файла держит инод | **разрыв** → сверка инода + пересоздание | | **Grafana** — дашборды | да | провайдер пересканирует сам (`updateIntervalSeconds: 30`) | разрыва нет — замер: новый файл появился без рестарта | | **Grafana** — датасорсы | да | **никто**: провиженинг датасорсов применяется только при старте | **разрыв** → `POST /api/admin/provisioning/datasources/reload` (замер: изменённый url не применился и через 75 с, после ручки применился сразу) | | **Alloy** (оба хоста) | да | `up -d --force-recreate alloy` + гейт по иноду уже есть | разрыва нет: `StartedAt 2026-09-12T10:13:38Z` | | **alert-ack** (`app.py` маунтом одного файла) | да | никто: python читает файл один раз при старте, `up -d` контейнер не трогает | **разрыв** → сверка инода + пересоздание | Пароль Grafana берётся из окружения **самого контейнера** — в аргументы команд на хосте и в лог деплоя он не попадает. ## Гейт `backend/tests/ops/test_3467_prometheus_reload.py` — 7 проверок. Краснеет, если шаг reload исчез, оторвался от проверки конфигурации, если ветка «проверка не прошла» перестала обрывать деплой, если снят `--web.enable-lifecycle`, или если в compose появился новый одиночный файловый маунт без пути доезда (список берётся из compose, а не из воркфлоу). Фальсификация (три сценария, `git stash` не использовался): - **шаг reload убран** → `test_deploy_reloads_prometheus`: «в серверной джобе нет POST /-/reload для Prometheus — правки правил лягут на диск и не вступят в силу, как это и было с #3464»; `test_reload_comes_after_config_check`: «в деплое нет перезагрузки Prometheus». `2 failed, 5 passed` - **проверка убрана, reload оставлен** → `test_reload_comes_after_config_check`: «в деплое нет проверки promtool (check config + check rules)»; `test_failed_check_stops_deploy_without_reload`: «проверка promtool не закрыта веткой обработки отказа». `2 failed, 5 passed` - **добавлен новый файловый маунт без сверки инода** → `test_every_single_file_mount_has_a_way_to_arrive`: «ожидались три одиночных файловых маунта …, найдено: […4 шт…]». `1 failed, 6 passed` После возврата — `7 passed`; весь `backend/tests/ops` — `86 passed, 10 failed`, и эти 10 краснеют и на main: локальный python 3.9 против `write_text(newline=…)` (нужен 3.10+), в CI стоит 3.12. ## Правки по ревью (второй коммит) Гейт был слеп в двух местах — обе мутации ревьюера проходили насквозь, обе теперь краснеют: | Мутация | Было | Стало | |---|---|---| | к `POST /-/reload` дописано `\|\| true` | 7 passed (regex кончался на URL, хвост строки не смотрел) | `2 failed, 6 passed` — `test_deploy_reloads_prometheus`, `test_reload_comes_after_config_check` | | новый одиночный файловый маунт **с** `:ro` | 1 failed | `1 failed, 7 passed` — `test_every_single_file_mount_has_a_way_to_arrive` | | новый одиночный файловый маунт **без** `:ro` | 7 passed (распознавание маунта требовало `:ro`) | `1 failed, 7 passed` — тот же тест | | проверку promtool вернули под `up -d` | проверки не было | `1 failed, 7 passed` — `test_config_check_precedes_any_application` | Права (`:ro`) к типу маунта отношения не имеют — инод он держит одинаково, поэтому требование снято из распознавания. Якоря границ серверной джобы и блока триггеров читаются через `find` с внятным assert: при переименовании шага гейт скажет, что сломалось, а не даст `ValueError`. Прогоны: `tests/ops/test_3467_prometheus_reload.py` — **8 passed**; весь `backend/tests/ops` — **87 passed, 10 failed** (те же 10 красные и на main: локальный python 3.9 против `write_text(newline=…)`, в CI 3.12). `yaml.safe_load` + `bash -n` по всем трём джобам — чисто. `ci.yml` не трогал: фильтр `backend` там не перечисляет `deploy-metrics.yml` и `docker-compose.metrics.yml` — **расширен в PR #3465**, чтобы две ветки не дрались за одни строки. ## Критерий приёмки После мержа и прохода `Deploy Metrics / server`: ``` GET http://localhost:9090/api/v1/rules → ContainerNearMemoryLimit ``` отдаёт выражение, начинающееся с `container_memory_working_set_bytes`, то есть правка #3464 наконец вступает в силу. Это и есть доказательство, что шаг работает: сейчас там `container_spec_memory_limit_bytes`. Прод в рамках этой работы не трогался: только чтение (`docker inspect`, `docker exec … stat/wget -qO- /api/v1/…`, `grep` по файлам). Closes #3467
bot-backend added 1 commit 2026-09-12 10:52:21 +00:00
Деплой метрик перечитывает конфигурацию, а не только кладёт её на диск (#3467)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
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 13s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m48s
CI / backend-tests (pull_request) Successful in 18m46s
c31aff68a9
Правки ops/metrics/prometheus/** доезжали до диска и не вступали в силу:
контейнер Prometheus не перезапускался с 2026-08-27, а деплой перезагружал
один Caddy. Правила из #3464 лежали и на диске, и внутри контейнера, а
/api/v1/rules отдавал прежние — при зелёном деплое и без единого
отрицательного признака.

- проверка promtool (check config + check rules) одноразовым контейнером того
  же образа по файлам с диска, затем POST /-/reload; провал проверки роняет
  шаг и перезагрузки не делает — работающий Prometheus остаётся на прежней
  конфигурации;
- сверка инода у одиночных файловых маунтов (prometheus.yml, loki-config.yml,
  alert-ack/app.py) с пересозданием контейнера: маунт ОДНОГО файла держит
  старый инод, а у Loki ручки перезагрузки нет вовсе (404 на /reload);
- датасорсы Grafana перечитываются штатной ручкой: их провиженинг применяется
  только при старте, дашборды же провайдер пересканирует сам;
- гейт backend/tests/ops/test_3467_prometheus_reload.py краснеет, если
  перезагрузка исчезла, оторвалась от проверки конфигурации или у нового
  файлового маунта нет пути доезда.

Closes #3467

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Light1YT added 1 commit 2026-09-12 11:18:58 +00:00
Гейт #3467 больше не слеп к || true и к маунту без :ro (ревью PR #3475)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
CI / 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 Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m51s
CI / backend-tests (pull_request) Successful in 18m17s
3aef5c3631
Мутации ревьюера проходили гейт насквозь:

- к шагу `POST /-/reload` дописано `|| true` — все 7 тестов зелёные. Regex
  заканчивался на URL и хвост строки не смотрел; сегодня спасает `set -e`, но
  именно так («зелёная сводка ≠ зелёный выход») в этом репозитории уже ломали
  гейты. Добавлен отрицательный lookahead на `||` в той же строке;
- новый одиночный файловый маунт БЕЗ суффикса `:ro` — тест зелёный, потому что
  распознавание маунта требовало `:ro`. Права к типу маунта отношения не имеют:
  инод он держит одинаково. Требование снято.

Проверка promtool переехала ВЫШЕ `docker compose up -d`: прежнее место (между
подъёмом и перезагрузкой) не спасало от битого конфига, потому что `up -d`
пересоздаёт контейнер при смене образа. Новый тест сторожит и это.

Якоря границ серверной джобы и блока триггеров читаются через find с внятным
assert: при переименовании шага гейт скажет, что сломалось, а не даст ValueError.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
Collaborator

Закрываю как поглощённый. Ту же пару файлов — перечитывание конфигурации Prometheus при деплое метрик и тест на него — принёс PR #3476, слитый сегодня. Здесь ветка отстала от main и уже не сливается. Ничего из этой работы не потеряно, всё в main.

Закрываю как поглощённый. Ту же пару файлов — перечитывание конфигурации Prometheus при деплое метрик и тест на него — принёс PR #3476, слитый сегодня. Здесь ветка отстала от main и уже не сливается. Ничего из этой работы не потеряно, всё в main.
bot-backend closed this pull request 2026-09-12 11:38:52 +00:00
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
CI / 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 Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m51s
CI / backend-tests (pull_request) Successful in 18m17s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#3475
No description provided.