All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
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 2m0s
CI / backend-tests (pull_request) Successful in 17m28s
Пойман на проде 27.08 сразу после мержа #3136. На диске лежал новый конфиг — `webhook_configs` на сервис кнопки подтверждения, — `amtool check-config` его одобрил, деплой зелёный. А контейнер продолжал слать алерты напрямую: на диске: webhook_configs: url http://alert-ack:8080/alertmanager в контейнере: telegram_configs: Конфиг подключён бинд-маунтом ФАЙЛА, а рендер делает `rm` и создаёт файл заново — иначе не перезаписать: после chown он принадлежит 65534 с правами 600, а каталог принадлежит деплой-пользователю. `rm` + создание даёт НОВЫЙ инод, тогда как открытый дескриптор внутри работающего контейнера продолжает смотреть на прежний, уже удалённый. `up -d` контейнер не трогает: он сравнивает описание сервиса, а содержимое бинд-маунта в сравнение не входит. Отказ беззвучный — ни одного красного признака нигде. Значит и все прежние правки маршрутизации применялись лишь тогда, когда контейнер пересоздавался по совпадению. Перезагрузка по SIGHUP/API не лечит: она перечитывает тот же открытый инод. Лечит только пересоздание контейнера — его и добавляю, под флагом, который выставляется ПОСЛЕ успешной проверки конфига. Порядок важен: при обратном битый конфиг убивал бы работающий Alertmanager вместо того, чтобы оставить прежний работать. Прод уже приведён в соответствие вручную — контейнер пересоздан, маршрут клиентских инцидентов теперь идёт через кнопку. Эта правка нужна, чтобы следующая правка конфига доехала сама. Три теста: пересоздание есть, оно закрыто проверкой флага (безусловное рвало бы доставку на каждом деплое метрик), флаг выставляется после проверки. Прогон: 78 ops-тестов зелёные, ruff чист.
82 lines
4.4 KiB
Python
82 lines
4.4 KiB
Python
"""Правки конфига 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"
|
||
)
|