gendesign/scripts/setup-metrics-grafana-role.sh
bot-backend 309d273f3f
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
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 / openapi-codegen-check (pull_request) Has been skipped
feat(observability): стек метрик и логов — Prometheus, Loki, Grafana, агенты на обоих хостах
Метрик в проекте не было ни одной: ни экспортеров, ни /metrics в бэкендах,
единственный канал наблюдения — journald, единственный сигнал об аварии —
исключение в GlitchTip. Из-за этого целый класс отказов невидим в принципе:
задача рапортует done, строк ноль, исключения нет. Так протухли данные на семь
месяцев (#2998), 34 дня был мёртв house_imv_backfill (#2698), 8 суток писал ноль
newbuilding_enrich (#2767), 91 день копилось раздутие listings (#2992).

Grafana не заменяет GlitchTip: ошибки остаются там. Grafana OSS не принимает
Sentry DSN ни одним компонентом, а скрубберы в before_send — требование 152-ФЗ.
Здесь появляется другой класс данных: ряды и алерты по трендам.

Наблюдатель поставлен у ДРУГОГО провайдера, чем наблюдаемое: серверная сторона
на Beget, рядом с GlitchTip. Если ляжет Poincare, мониторинг должен об этом
сказать, а не лечь вместе с ним.

Транспорт push, а не pull: агент на Poincare шлёт remote_write и логи исходящим
HTTPS, поэтому там не открывается ни одного входящего порта сверх 22/80/443.
При обрыве канала Alloy копит в WAL и досылает — pull-скрейп в той же ситуации
терял бы точки именно в аварии, ради которой мониторинг и нужен.

Два контура доступа с разными учётками. Пароль приёмника по построению лежит
открытым на продуктовом хосте, значит его компрометация неизбежна вместе с
хостом; будь это учётка витрины, утёк бы и доступ к дашбордам.

GlitchTip читается прямым SQL, а не Sentry-плагином: у плагина на 6.1.6
stats_v2 отдаёт 500 (баг GlitchTip #381), Events/Discover — 404 (#416), а в
grafana/sentry-datasource слово glitchtip не встречается ни разу. Схема сверена
на живой базе: колонка времени называется timestamp, а не received, и отдельной
таблицы IssueIndex не существует — агрегаты лежат на самой issue_events_issue.

Алерты за профилем alerts: канал доставки — открытый вопрос #3078, и стек не
должен на нём стоять. Деплой предупреждает, что уведомлять пока некому.

Каждая настройка, способная отказать молча, закрыта явно: ретенция Prometheus
задана и по времени и по размеру, retention_enabled у компактора Loki (без него
retention_period не работает вовсе), путь к журналу и запуск Alloy от root
(иначе агент читает ноль записей без ошибки), проверка Caddy до перезагрузки
(на этом хосте тот же Caddy держит git, errors и obsidian).

Refs #3078
2026-08-26 10:45:54 +03:00

78 lines
3.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Заводит read-only роль grafana_ro в базе glitchtip — под датасорс Grafana.
# Идемпотентен: повторный запуск только досогласует права.
#
# ПОЧЕМУ ОТДЕЛЬНАЯ РОЛЬ, А НЕ ВЛАДЕЛЕЦ БАЗЫ. Датасорс Grafana доступен всякому,
# кто вошёл в интерфейс, и позволяет выполнять произвольный SQL в панелях.
# Владельческая роль сделала бы витрину способом уронить трекер ошибок. Здесь
# прав на запись нет вовсе, поэтому худшее, что можно сделать через панель, —
# медленный SELECT.
#
# Запускать на инфраструктурном хосте. Вызывается из deploy-metrics.yml.
set -euo pipefail
CONT=gendesign-infra-postgres
DB=glitchtip
ROLE=grafana_ro
: "${GLITCHTIP_RO_PASSWORD:?GLITCHTIP_RO_PASSWORD не задан — запусти scripts/setup-metrics-secrets.sh}"
if ! docker inspect "$CONT" >/dev/null 2>&1; then
echo " $CONT не найден — пропускаю создание роли."
exit 0
fi
# Ищем роль с правом заводить других. Имя суперпользователя в этом кластере
# нигде не зафиксировано, а угадывать «postgres» неверно: в образе оно задаётся
# переменной POSTGRES_USER и здесь ею не является.
SUPER=""
for candidate in glitchtip forgejo postgres; do
if docker exec "$CONT" psql -U "$candidate" -d postgres -tAc \
"SELECT 1 FROM pg_roles WHERE rolname = CURRENT_USER AND (rolsuper OR rolcreaterole)" 2>/dev/null \
| grep -q 1; then
SUPER="$candidate"
break
fi
done
if [ -z "$SUPER" ]; then
echo " ОШИБКА: не нашёл роль с правом CREATE ROLE в $CONT." >&2
echo " Проверь вручную: docker exec $CONT psql -U <роль> -c '\\du'" >&2
exit 1
fi
echo " привилегированная роль: $SUPER"
# Пароль передаётся через переменную окружения psql, а не в тексте запроса —
# иначе он осел бы в pg_stat_statements и в логе запросов.
docker exec -e RO_PASS="$GLITCHTIP_RO_PASSWORD" -i "$CONT" \
psql -U "$SUPER" -d "$DB" -v ON_ERROR_STOP=1 <<'SQL'
\set ro_pass `echo "$RO_PASS"`
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
CREATE ROLE grafana_ro LOGIN;
RAISE NOTICE 'роль grafana_ro создана';
ELSE
RAISE NOTICE 'роль grafana_ro уже есть';
END IF;
END
$$;
ALTER ROLE grafana_ro WITH PASSWORD :'ro_pass';
-- Ограничение на число соединений: панель с автообновлением способна открыть
-- их десятками, а это тот же кластер, где живёт Forgejo.
ALTER ROLE grafana_ro CONNECTION LIMIT 8;
GRANT CONNECT ON DATABASE glitchtip TO grafana_ro;
GRANT USAGE ON SCHEMA public TO grafana_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro;
-- Таблицы событий партиционированы по неделям: новые партиции появляются сами,
-- и без этой строки датасорс начал бы отдавать пустоту на свежих данных,
-- оставаясь при этом «рабочим». Тихий отказ ровно того сорта, который мы ловим.
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro;
SQL
echo " grafana_ro: права выданы на $DB"