Compare commits
No commits in common. "127c9a5c2a04b7d4b4ab9660e0066d145421bae1" and "c53eaf70791eb1969effd94af0e0584e05f9c8fb" have entirely different histories.
127c9a5c2a
...
c53eaf7079
2 changed files with 0 additions and 157 deletions
|
|
@ -353,51 +353,6 @@ jobs:
|
||||||
done
|
done
|
||||||
docker compose -p gendesign-metrics -f docker-compose.metrics.yml ps
|
docker compose -p gendesign-metrics -f docker-compose.metrics.yml ps
|
||||||
|
|
||||||
# ── Prometheus: конфиг/правила лежат на диске, `up -d` их не
|
|
||||||
# перечитывает ────────────────────────────────────────────────
|
|
||||||
# Тот же класс бага, что у Caddyfile и alertmanager.yml выше:
|
|
||||||
# docker compose сравнивает описание сервиса, а НЕ содержимое
|
|
||||||
# бинд-маунта, поэтому уже работающий контейнер продолжает жить
|
|
||||||
# со старым конфигом сколько угодно — на проде дошло до 16 суток
|
|
||||||
# незамеченными (#3467): lastConfigTime совпадал со startTime
|
|
||||||
# контейнера при каждом зелёном деплое, менявшем ops/metrics/prometheus/**.
|
|
||||||
#
|
|
||||||
# У Prometheus, в отличие от Alertmanager (см. комментарий выше),
|
|
||||||
# /-/reload переоткрывает файлы ПО ПУТИ заново, поэтому новый инод
|
|
||||||
# после `git reset --hard` подхватывается без пересоздания
|
|
||||||
# контейнера. --web.enable-lifecycle уже включён в compose ради
|
|
||||||
# этого шага (см. docker-compose.metrics.yml) — просто раньше
|
|
||||||
# никто не звал сам reload.
|
|
||||||
#
|
|
||||||
# promtool проверяет ОБА файла ДО reload: битый конфиг не должен
|
|
||||||
# положить работающий Prometheus молчаливым откатом на дефолты.
|
|
||||||
if docker exec gendesign-prometheus promtool check config /etc/prometheus/prometheus.yml \
|
|
||||||
&& docker exec gendesign-prometheus sh -c 'promtool check rules /etc/prometheus/rules/*.yml'; then
|
|
||||||
LAST_CONFIG_BEFORE="$(docker exec gendesign-prometheus wget -qO- http://localhost:9090/api/v1/status/runtimeinfo | grep -oE '"lastConfigTime":"[^"]*"')"
|
|
||||||
|
|
||||||
docker exec gendesign-prometheus wget -q -O /dev/null --post-data='' http://localhost:9090/-/reload
|
|
||||||
|
|
||||||
# lastConfigTime обновляется на КАЖДЫЙ успешный reload, даже
|
|
||||||
# если содержимое конфига не поменялось — значит сравнение
|
|
||||||
# "было/стало" надёжно ловит и несостоявшийся reload, и
|
|
||||||
# изменившиеся правила.
|
|
||||||
LAST_CONFIG_AFTER=""
|
|
||||||
for i in $(seq 1 10); do
|
|
||||||
LAST_CONFIG_AFTER="$(docker exec gendesign-prometheus wget -qO- http://localhost:9090/api/v1/status/runtimeinfo | grep -oE '"lastConfigTime":"[^"]*"')"
|
|
||||||
[ -n "$LAST_CONFIG_AFTER" ] && [ "$LAST_CONFIG_AFTER" != "$LAST_CONFIG_BEFORE" ] && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$LAST_CONFIG_AFTER" ] || [ "$LAST_CONFIG_AFTER" = "$LAST_CONFIG_BEFORE" ]; then
|
|
||||||
echo "ОШИБКА: reload Prometheus не подтверждён — lastConfigTime не изменился ($LAST_CONFIG_BEFORE)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Prometheus: конфиг и правила проверены, reload подтверждён ($LAST_CONFIG_BEFORE -> $LAST_CONFIG_AFTER)."
|
|
||||||
else
|
|
||||||
echo "ОШИБКА: конфиг/правила Prometheus не проходят promtool — reload НЕ выполнен, работающий Prometheus остаётся на прежнем конфиге."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ═══ АГЕНТЫ — оба хоста ═══════════════════════════════════════════════════
|
# ═══ АГЕНТЫ — оба хоста ═══════════════════════════════════════════════════
|
||||||
agent-apps:
|
agent-apps:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
"""Правки Prometheus-конфига/правил обязаны доезжать до работающего процесса.
|
|
||||||
|
|
||||||
ЧТО СЛУЧИЛОСЬ НА ПРОДЕ. `GET /api/v1/status/runtimeinfo` внутри
|
|
||||||
`gendesign-prometheus` 12.09 отдавал `lastConfigTime`, совпадающий со
|
|
||||||
`startTime` контейнера, — конфиг и правила не перечитывались 16 суток.
|
|
||||||
Деплой при этом был зелёный: `docker compose up -d` не пересоздаёт
|
|
||||||
контейнер из-за изменения содержимого бинд-маунта (он сравнивает только
|
|
||||||
описание сервиса), а `--web.enable-lifecycle` был включён в
|
|
||||||
docker-compose.metrics.yml, но эндпоинт `/-/reload` никто не вызывал.
|
|
||||||
|
|
||||||
Тот же класс бага, что уже пойман и починен для Caddy (`caddy reload`)
|
|
||||||
и для Alertmanager (`--force-recreate`, см. test_3xxx_alertmanager_inode.py)
|
|
||||||
в этом же workflow — только для Prometheus починка не пересоздание
|
|
||||||
контейнера, а именно `POST /-/reload`: он переоткрывает файлы конфига по
|
|
||||||
пути заново, так что новый инод после `git reset --hard` подхватывается
|
|
||||||
без даунтайма.
|
|
||||||
|
|
||||||
Проверяется здесь: (1) валидация promtool ЕСТЬ, (2) reload вызывается
|
|
||||||
ТОЛЬКО после успешной валидации, (3) шаг обязан упасть, если reload не
|
|
||||||
подтверждён сменой lastConfigTime.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
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_promtool_checks_config_and_rules() -> None:
|
|
||||||
"""promtool обязан проверять и конфиг, и правила — не только один файл."""
|
|
||||||
text = _text()
|
|
||||||
assert "promtool check config /etc/prometheus/prometheus.yml" in text, (
|
|
||||||
"нет проверки конфига promtool'ом — битый prometheus.yml долетит до reload"
|
|
||||||
)
|
|
||||||
assert "promtool check rules" in text, (
|
|
||||||
"нет проверки правил promtool'ом — битое правило долетит до reload"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_reload_endpoint_is_called() -> None:
|
|
||||||
"""Сам reload обязан вызываться — иначе валидация ничего не решает."""
|
|
||||||
text = _text()
|
|
||||||
assert "localhost:9090/-/reload" in text, (
|
|
||||||
"нет вызова POST /-/reload — конфиг/правила проверяются, но в силу не вступают "
|
|
||||||
"(#3467: lastConfigTime не менялся 16 суток при зелёном деплое)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_reload_happens_after_validation_not_before() -> None:
|
|
||||||
"""Reload обязан идти ПОСЛЕ promtool, а не до/вместо него."""
|
|
||||||
text = _text()
|
|
||||||
check_pos = text.index("promtool check config /etc/prometheus/prometheus.yml")
|
|
||||||
reload_pos = text.index("localhost:9090/-/reload")
|
|
||||||
assert check_pos < reload_pos, (
|
|
||||||
"reload стоит раньше проверки конфига — битый конфиг мог бы применяться вслепую"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_reload_is_guarded_by_the_promtool_check() -> None:
|
|
||||||
"""Reload обязан быть ВНУТРИ `if promtool ...; then`, а не безусловным."""
|
|
||||||
text = _text()
|
|
||||||
guard_start = text.index("if docker exec gendesign-prometheus promtool check config")
|
|
||||||
else_pos = text.index("else", guard_start)
|
|
||||||
reload_pos = text.index("localhost:9090/-/reload")
|
|
||||||
assert guard_start < reload_pos < else_pos, (
|
|
||||||
"вызов reload лежит вне ветки успешной проверки promtool — "
|
|
||||||
"битый конфиг всё равно приведёт к reload, либо reload вообще не защищён проверкой"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_failed_validation_skips_reload_and_fails_the_step() -> None:
|
|
||||||
"""При провале promtool — reload НЕ вызывается, и шаг падает (exit 1)."""
|
|
||||||
text = _text()
|
|
||||||
guard_start = text.index("if docker exec gendesign-prometheus promtool check config")
|
|
||||||
else_pos = text.index("else", guard_start)
|
|
||||||
fi_pos = text.index("fi", else_pos)
|
|
||||||
else_branch = text[else_pos:fi_pos]
|
|
||||||
assert "localhost:9090/-/reload" not in else_branch, (
|
|
||||||
"reload вызывается даже в ветке провалившейся проверки"
|
|
||||||
)
|
|
||||||
assert "exit 1" in else_branch, (
|
|
||||||
"провал promtool не роняет шаг — деплой останется зелёным при битом конфиге"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_acceptance_checks_last_config_time_actually_changed() -> None:
|
|
||||||
"""Приёмка обязана сверять `lastConfigTime` до/после, а не доверять коду ответа reload.
|
|
||||||
|
|
||||||
`wget` на POST /-/reload может отрапортовать успех, даже если Prometheus
|
|
||||||
молча остался на старом конфиге (например, если бинарь внутри образа не
|
|
||||||
поддерживает --post-data так, как ожидалось) — единственное надёжное
|
|
||||||
подтверждение реального перечитывания конфига это смена таймстемпа.
|
|
||||||
"""
|
|
||||||
text = _text()
|
|
||||||
assert text.count("lastConfigTime") >= 2, (
|
|
||||||
"нет сравнения lastConfigTime до/после — reload не проверяется по факту"
|
|
||||||
)
|
|
||||||
assert "LAST_CONFIG_BEFORE" in text and "LAST_CONFIG_AFTER" in text, (
|
|
||||||
"нет явного до/после сравнения таймстемпа последней перезагрузки конфига"
|
|
||||||
)
|
|
||||||
verify_start = text.index("LAST_CONFIG_AFTER")
|
|
||||||
verify_block_end = text.index("Prometheus: конфиг и правила проверены", verify_start)
|
|
||||||
verify_block = text[verify_start:verify_block_end]
|
|
||||||
assert "exit 1" in verify_block, (
|
|
||||||
"если lastConfigTime не изменился, шаг обязан падать, а не считаться успешным"
|
|
||||||
)
|
|
||||||
Loading…
Add table
Reference in a new issue