feat(observability): стек метрик и логов — Prometheus, Loki, Grafana на Beget, агенты на обоих хостах #3099
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3099
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/observability-metrics-stack"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Части 1-2 из пяти по #3078: фундамент плюс метрики хоста и баз. Прод-код не тронут ни строкой.
Что появляется
Метрик в проекте не было ни одной —
grep prometheus|statsd|opentelemetryпо репозиторию давал ноль. Единственным каналом наблюдения оставался journald, единственным сигналом об аварии — исключение в GlitchTip. Из-за этого целый класс отказов был невидим в принципе: задача рапортуетdone, строк ноль, исключения нет, метрики нет.Именно так протухли данные на семь месяцев (#2998), 34 дня был мёртв
house_imv_backfill(#2698), 8 суток писал ноль записейnewbuilding_enrich(#2767), 91 день копилось раздутиеlistings(#2992).Grafana не заменяет GlitchTip и не претендует: Grafana OSS не принимает Sentry DSN ни одним компонентом, а 549 строк скрубберов в
before_send— это 152-ФЗ, а не фича трекера. Появляется другой класс данных: числовые ряды и алерты по трендам вместо алертов по событиям.Решения, которые стоит проверить при ревью
Наблюдатель у другого провайдера, чем наблюдаемое. Серверная сторона на Beget, рядом с GlitchTip. Ляжет Poincare — мониторинг обязан выжить и сказать об этом.
Транспорт push, а не pull. Агент на Poincare шлёт исходящим HTTPS, поэтому там не открывается ни одного входящего порта сверх 22/80/443. При обрыве канала Alloy копит в WAL и досылает; pull-скрейп в той же ситуации терял бы точки — то есть ровно в аварии, ради которой мониторинг и заводится.
Две учётки, а не одна. Пароль приёмника по построению лежит открытым на продуктовом хосте — агенту нужно им авторизоваться. Значит, компрометация Poincare раскрывает его автоматически. Будь это учётка витрины, вместе с ним утёк бы доступ к дашбордам и к Prometheus, где видна вся инфраструктура.
GlitchTip читается прямым SQL, а не Sentry-плагином. У плагина на нашей 6.1.6:
stats_v2с фильтром по проекту → 500 (открытый баг GlitchTip #381 с 2025-01-10), Events/Discover → 404 (#416), Metrics/Spans/Tags — Sentry-only. В самомgrafana/sentry-datasourceслово «glitchtip» не встречается ни разу. Рольgrafana_roбез единого права на запись: датасорс доступен всякому, кто вошёл в интерфейс, и позволяет произвольный SQL — владельческая роль сделала бы витрину способом уронить трекер.Алерты за профилем. Канал доставки — открытый вопрос #3078 (тот же чат, что у вебхука GlitchTip, или отдельный; порог ночной побудки). Стек не должен на нём стоять, поэтому Alertmanager не поднимается, пока токен не задан, а деплой пишет предупреждением, что уведомлять пока некому. Поднимать его с пустым токеном нельзя: он стартует зелёным и молча ничего не шлёт.
Проверено на живой инфраструктуре
Схема GlitchTip 6.1.6 сверена глазами, и две вещи из прежнего описания задачи оказались неверны:
receivedtimestamp(плюсcreated);receivedне существуетIssueIndexcount,first_seen,last_seen,status,levelлежат на самойissue_events_issueissue_events_issueaggregate (issue_id, organization_id, date, count)подтверждена, партиционирована по неделям с почасовыми под-партициями. Событий в базе 31 703.Caddy проверен
caddy validateдо коммита — в обоих режимах,Valid configuration. Первый прогон поймал настоящую ошибку:importрезолвится относительно файла, где написан, поэтому изcaddy/sites/путь должен быть../metrics-*.caddy.snippet, как это уже сделано вapps.caddy:219. Без проверки это положило быgit.,errors.иobsidian.разом — включая Forgejo, из которого идёт деплой. По той же причине в workflowvalidateстоит передreload, и при неудаче работающий Caddy не трогается.Ресурсы посчитаны по факту, а не по оценке. Beget после переезда и уборки: занято 2987 МБ из 11960, доступно 8972, диск 62 из 145 ГБ. Стек с
mem_limitпросит ~3,8 ГБ потолка при ожидаемом потреблении заметно ниже.Настройки, способные отказать молча — закрыты явно
retention_enabledу компактора Loki — без негоretention_periodне работает вовсе, конфиг принимается и данные копятся вечно--web.enable-remote-write-receiver— без флага приёмник отдаёт 404, агент молча копит в WAL, графики остаются пустымиadm/systemd-journalи безpathчитает ноль записей без ошибкиALTER DEFAULT PRIVILEGESдляgrafana_ro: таблицы событий партиционированы по неделям, без этого свежие данные отдавались бы пустыми при «рабочем» датасорсеTest plan
caddy validateприCADDY_SITES=infraиCADDY_SITES=apps— обеValid configurationcaddy adaptпоказывает четыре хоста приinfra:errors,git,metrics,obsidianmetrics.gendsgn.ru→46.173.16.127заведена и разошлась (проверено через 1.1.1.1)Deploy Metrics→ Grafana открывается, хостовые графики живые с обоих хостовgit.,errors.,obsidian.продолжают отвечать (Caddy перезагружался)host="apps"присутствует в PrometheusЧто ещё впереди
Части 3-5:
/metricsв обоих бэкендах (единственная часть, трогающая прод-код), экспортер поверхscrape_runsс воспроизведением кейса «doneпри нуле строк», алерты и синтетический heartbeat.Refs #3078
Ревью: технических возражений нет. Мержить не стал — вопрос в сроке, а он твой
Прошёл по диффу (2463 строки, все добавления, прод-код действительно не тронут). Отдельно проверил то, что в таких PR ломается чаще всего.
Безопасность нового публичного контура — сделано правильно
metrics.gendsgn.ru— новая точка в интернете, причём с ingest-эндпоинтами, а незащищённый приёмник Prometheus/Loki был бы серьёзной дырой. Здесь:GF_USERS_ALLOW_SIGN_UP=false), внешний basic_auth — не единственный;Ретенция задана честно
--storage.tsdb.retention.time=30dи--storage.tsdb.retention.size=8GB— по обоим измерениям, а не только по времени. Ловушка Loki (retention_periodне работает безretention_enabledу компактора) вынесена в документацию. Это ровно тот класс граблей, на которых такие стеки съедают диск молча.Что измерил: бюджет памяти на Beget
Лимиты по compose:
Состояние Beget сейчас (26.08, замер):
Запас есть — 8.59 ГБ против 5.15 ГБ лимитов, и лимиты это потолки, а не резервирование. Диска под 8 ГБ ретенции Prometheus хватает с большим избытком.
Чего в замере НЕТ и что стоит посмотреть после включения: пика во время сборки CI. Раннеры в покое едят 20–80 МБ, но job-контейнер на сборке образов — совсем другая величина, и в снимке выше сборка не шла. Beget несёт Forgejo и все три раннера, то есть OOM там ударил бы по самому деплой-конвейеру. Предлагаю после первого включения посмотреть
free -mво время ближайшей сборки, прежде чем добавлять части 3-5.Свободные 840 МБ рядом: на Beget до сих пор работает
tradein-postgres— замороженная копия (последняя запись 25.08 17:23, живая база на Poincare ушла вперёд), которую я оставил как якорь отката. Она ест 840 МБ. Если решишь её гасить, это почти четверть бюджета стека.Почему не мержу сам
CI зелёный, замечаний по коду у меня нет — merge-кнопка не заблокирована ничем техническим. Но это +5 ГБ и новый публичный домен на машине, несущей CI, за четыре дня до окна переезда. Срок относительно окна — решение твоё, не моё, поэтому оставляю открытым. Скажешь «вливай» — волью сразу, ревью закончено.
Мелочь на будущее: A-запись
metrics.gendsgn.ruзаведена 26.08 и смотрит на 46.173.16.127 (Beget) — то есть в #3027 к восьми доменам добавился девятый. На переезд не влияет: Beget остаётся.