"""Правки конфига Alertmanager обязаны доезжать до работающего контейнера. ЧТО СЛУЧИЛОСЬ НА ПРОДЕ 27.08. После мержа #3136 на диске лежал уже новый конфиг — `webhook_configs` на сервис кнопки подтверждения, — `amtool check-config` его одобрил, деплой был зелёный. А контейнер продолжал слать алерты напрямую в Telegram: на диске: - name: telegram-clients webhook_configs: - url: "http://alert-ack:8080/alertmanager" в контейнере: - name: telegram-clients telegram_configs: ПОЧЕМУ. Конфиг подключён бинд-маунтом ФАЙЛА, а рендер в деплое делает `rm` и создаёт файл заново (иначе не перезаписать: после chown он принадлежит 65534 с правами 600). `rm` + создание — это НОВЫЙ инод, а открытый дескриптор внутри работающего контейнера продолжает смотреть на прежний, уже удалённый. `up -d` контейнер не трогает: он сравнивает описание сервиса, а содержимое бинд-маунта в сравнение не входит. Отказ беззвучный: ни одного красного признака нигде. Значит и прежние правки маршрутов применялись лишь тогда, когда контейнер пересоздавался по совпадению. Перезагрузка по SIGHUP/API не лечит — она перечитывает тот же инод. Лечит только пересоздание контейнера, и именно его наличие здесь и проверяется. """ from __future__ import annotations import re from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[3] WORKFLOW = REPO_ROOT / ".forgejo" / "workflows" / "deploy-metrics.yml" def _text() -> str: return WORKFLOW.read_text(encoding="utf-8") def test_rerendered_config_forces_container_recreate() -> None: """После перерисовки конфига контейнер обязан пересоздаваться.""" text = _text() assert "--force-recreate alertmanager" in text, ( "нет пересоздания Alertmanager — правка конфига не доедет до " "работающего контейнера, он останется на старом иноде" ) def test_recreate_is_guarded_by_the_rerender_flag() -> None: """Пересоздание — только когда конфиг действительно перерисовали. Безусловное пересоздание на каждом деплое метрик рвало бы доставку алертов там, где ничего не менялось. """ text = _text() assert "ALERTMANAGER_RERENDERED=1" in text, "флаг перерисовки не выставляется" guard = re.search( r'if \[ "\$\{ALERTMANAGER_RERENDERED:-0\}" = "1" \]; then(.+?)\bfi\b', text, re.S, ) assert guard, "пересоздание не закрыто проверкой флага" assert "--force-recreate alertmanager" in guard.group(1), ( "пересоздание стоит вне проверки флага — оно будет выполняться всегда" ) def test_flag_is_set_only_after_config_passed_validation() -> None: """Флаг выставляется ПОСЛЕ проверки конфига, а не до неё. Иначе битый конфиг пересоздавал бы живой контейнер и ронял алертинг целиком вместо того, чтобы оставить работать прежний. """ text = _text() check = text.index("check-config /tmp/am.yml") flag = text.index("ALERTMANAGER_RERENDERED=1") assert check < flag, ( "флаг перерисовки выставлен раньше проверки конфига — битый конфиг " "убил бы работающий Alertmanager" )