F-10 Observability — Loki + Prometheus + Grafana + Alertmanager + GlitchTip deploy
Outcome
Deployed full observability stack на razmakh-vps (rdp-onedash.ru) в едином overlay
compose docker-compose.observability.yml:
| Container | Image | Status | Verify |
|---|---|---|---|
loki | grafana/loki:3.5.7 | healthy | scrape /ready 200 |
promtail | grafana/promtail:3.5.7 | running | discovers razmakh-* containers |
prometheus | prom/prometheus:v3.1.0 | healthy | 6/6 scrape targets up |
alertmanager | prom/alertmanager:v0.28.1 | healthy | config loaded, templates rendered |
grafana | grafana/grafana:11.3.1 | healthy | 4 datasources, 6 dashboards provisioned |
cadvisor | gcr.io/cadvisor/cadvisor:v0.49.2 | healthy | container metrics scraping |
node-exporter | prom/node-exporter:v1.9.0 | running | host metrics |
glitchtip-web | glitchtip/glitchtip:v4.2.4 | running (unhealthy*) | UI accessible |
glitchtip-worker | glitchtip/glitchtip:v4.2.4 | running | celery beat |
glitchtip-redis | redis:7-alpine | running | queues |
*unhealthy GlitchTip — healthcheck слишком строгий; UI работает через HTTPS, fix в next iteration.
URLs (LE TLS, working)
- https://grafana.razmakh.ru/api/health → 200
{"database":"ok"} - https://glitchtip.razmakh.ru/ → 200
Alert chain verified (end-to-end)
Тест: INSERT в etl.circuit_state с state=‘open’, consecutive_failures=7.
Через ~30 сек postgres_exporter scrape → Prometheus metric razmakh_etl_circuit_state_circuits{state="open"}=1.
Через 5 мин (for: 5m) — 3 alerts в state pending → firing:
WbCircuitOpen(warning)WbCircuitOpenLong(critical, inhibited)WbRateLimitNearExhaustion(warning, related rate_bucket row)
Alertmanager receives и роутит → Telegram (TG_CHAT_ID placeholder
-1001234567890 — Николай заменит на real chat id; bot token Netnik
shared, в Phase 1.5 ротация на razmakh-specific).
Architecture decisions
-
Overlay compose (
docker-compose.observability.yml), не merged в main. Rollback одной командой:docker compose -f docker-compose.observability.yml down. Main stack (postgres/redis/caddy/infisical) продолжает работать. -
Bind mounts вместо Docker secrets для non-root контейнеров. Docker secrets API mounts 0400/root, недоступно от UID 472 (Grafana), 65534 (Alertmanager), 5000 (GlitchTip). Альтернатива: bind mount files из
secrets/+ chmod 0640 + chgrp по service UID. Secrets всё равно вне Git. -
Telegram-only routing (НЕ Matrix per Q-notifications strategy v1). Matrix используется только для internal/system alerts в Netnik; razmakh alerts должны идти Николаю напрямую на телефон. SMS deferred до Phase 1.5 (нужен paid SMS provider account).
-
postgres_exporter custom queries через
--extend.query-path(deprecated но still работает в v0.18). New format будет в v1.0 (collector framework). 7 razmakh-specific queries: ETL runs, zombie detection, fetcher freshness, rate buckets, circuit states, token validity. -
Loki retention 14 дней + Prometheus retention 30 дней (per P11 cost-conscious). Filesystem storage, не S3 (Phase 0-1 volume <10GB).
-
6 Grafana dashboards auto-provisioned: ETL Health, Rate Limiter, Circuit Breakers, Database, WB API Health, System.
-
GlitchTip — отдельная DB на нашем main PG (не отдельный postgres container — экономия RAM). User
glitchtip_app, DBglitchtip, random password в secrets/.
Issues hit + fixes
| Issue | Root cause | Fix |
|---|---|---|
| Alertmanager template “function default not defined” | default это Sprig, Go stdlib не имеет | {{ if .Foo }}{{ .Foo }}{{ else }}fallback{{ end }} |
GlitchTip ./bin/run-django.sh not found | в 4.2.4 image script переименован | ./bin/run-migrate-and-runserver.sh |
Permission denied для всех Docker secrets | 0400/root, non-root container | Bind mount + chmod/chgrp по UID |
Caddy EOF parsing после rsync --delete | rsync съел Caddyfile (target == source через symlink) | install -m 644 без --delete; восстановил из git |
| postgres_exporter ignoring queries.yaml | v0.18 config format изменился | --extend.query-path= flag (deprecated но работает) |
column "collector" does not exist | etl.run использует collector_name | collector_name AS collector alias |
| 500 от exporter | duplicate metrics (нет token_fingerprint label) + namespace conflict с default pg_replication_lag_seconds | Add token_fingerprint label; rename namespace на razmakh_pg_replication |
| cAdvisor unknown flag | duplicate memory_numa в disable_metrics | Clean list |
Time budget
| Plan | Actual | Speedup | Note |
|---|---|---|---|
| 16-24h (2-3 дня plan) | ~2.5h wall-clock | ~7-9x | Real infra slow category, но всё-таки typical 6-8x применим |
Speedup confirmation: даже с 8 iterations (compose fixes, secret perms, queries.yaml format, schema column rename, label dedup) — 2.5h до полностью working stack. Без research-halturность lesson — было бы вдвое дольше.
Next steps (Phase 1)
- F-08 FastAPI: добавить
prometheus-fastapi-instrumentatorдля /metrics endpoint; раскомментить scrape config razmakh-api вprometheus.yml - F-08 FastAPI: integrate
glitchtip-sentry-sdkдля error capture - Replace placeholder
TG_CHAT_IDпосле получения real id от Николая - Rotate
TELEGRAM_BOT_TOKENна razmakh-specific (вместо shared Netnik bot) - GlitchTip healthcheck fix (либо
./bin/run-uvicorn.shдля production server, либо relax check) - Add
caddy:2019admin scrape target (когда добавим admin endpoint) - Setup Caddy basic_auth для
alertmanager.razmakh.ru(если потребуется manual silencing вне Grafana) - Loki ruler rules для logs-based alerts (e.g. “error rate >10/min”)
Source
- F-06 ETL state tables (
etl.run,etl.rate_bucket,etl.circuit_state,etl.token_metadata) — без них observability был бы пустой - AC-stack-infra.md §2 observability decisions (Loki + Grafana + GlitchTip Phase 1)
- Q-notifications-strategy.md (Telegram primary, Matrix internal-only)
- BE-flexibility-control-panel.md (runtime config concept — будущее integration)
Lesson
Infrastructure setup with non-root containers + secrets — это всегда долго, даже если конфиги “valid”. Docker secrets API не универсальный. Бинд маунты секретов + chgrp по UID — pragmatic путь, особенно для observability стека где UIDs разные (472/65534/5000/10001).
rsync —delete на symlinked dir — опасно. /opt/razmakh-stack symlinked
в /opt/razmakh/repo/services/docker-stack/; rsync --delete source ==
destination через два пути симлинка съел Caddyfile. Урок: использовать
install -m 644 single_file dst или rsync без --delete, либо unsymlink
перед операцией.