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
Метрик в проекте не было ни одной: ни экспортеров, ни /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
202 lines
9.9 KiB
YAML
202 lines
9.9 KiB
YAML
# Стек наблюдаемости — СЕРВЕРНАЯ сторона. Живёт на инфраструктурном хосте (Beget).
|
||
#
|
||
# Деплоится ОТДЕЛЬНО от продуктового стека, своим project-name:
|
||
# docker compose -p gendesign-metrics -f docker-compose.metrics.yml up -d
|
||
#
|
||
# ПОЧЕМУ ЗДЕСЬ, А НЕ РЯДОМ С ПРОДОМ. После переезда 25.08 продукт живёт на Selectel
|
||
# Poincare, инфраструктура (Forgejo, GlitchTip, CouchDB) осталась на Beget. Наблюдатель
|
||
# сознательно поставлен у ДРУГОГО провайдера, чем наблюдаемое: если ляжет Poincare,
|
||
# мониторинг должен об этом сказать, а не лечь вместе с ним.
|
||
#
|
||
# ТРАНСПОРТ — PUSH, А НЕ PULL. Prometheus не ходит на Poincare за метриками: там агент
|
||
# Alloy сам шлёт remote_write и loki.write исходящим HTTPS через metrics.gendsgn.ru.
|
||
# Поэтому на Poincare не открывается НИ ОДНОГО входящего порта — ufw там остаётся
|
||
# 22/80/443. Приёмник включён флагом --web.enable-remote-write-receiver ниже.
|
||
#
|
||
# СЕТИ. Обе внешние, обе уже существуют на Beget:
|
||
# gendesign_shared — через неё Caddy ходит на grafana:3000 (и couchdb, garmin-mcp)
|
||
# gendesign_default — в ней infra-postgres, glitchtip, forgejo: их скрейпит агент
|
||
# Тот же приём, что у Caddy — он подключён к обеим.
|
||
#
|
||
# ПОРТЫ НАРУЖУ НЕ ПУБЛИКУЮТСЯ. Только expose. Наружу выходит один хост
|
||
# metrics.gendsgn.ru через Caddy под basic_auth (caddy/sites/infra.caddy).
|
||
# 3000/9090/3100/9093 на 0.0.0.0 не биндятся сознательно: на Beget уже был случай,
|
||
# когда redis уехал в интернет из-за лишней записи ports в базовом compose-файле.
|
||
|
||
x-logging: &default-logging
|
||
driver: journald
|
||
|
||
services:
|
||
# ── Prometheus: хранилище временных рядов + движок правил ────────────────────
|
||
prometheus:
|
||
image: prom/prometheus:v3.1.0
|
||
container_name: gendesign-prometheus
|
||
restart: unless-stopped
|
||
user: "65534:65534" # nobody — том создаётся снаружи, см. deploy-metrics.yml
|
||
command:
|
||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||
- "--storage.tsdb.path=/prometheus"
|
||
# Ретенция задана ЯВНО и по обоим измерениям. Без --storage.tsdb.retention.size
|
||
# диск съедается молча: по умолчанию ограничение только по времени, а сколько
|
||
# это в байтах — зависит от числа рядов, которое растёт само.
|
||
- "--storage.tsdb.retention.time=30d"
|
||
- "--storage.tsdb.retention.size=8GB"
|
||
# Приёмник push-метрик от агентов. Без флага remote_write отвечает 404,
|
||
# и агент на Poincare будет молча копить в WAL, а графики останутся пустыми.
|
||
- "--web.enable-remote-write-receiver"
|
||
# Нужен для перезагрузки правил без рестарта (deploy-metrics.yml дёргает).
|
||
- "--web.enable-lifecycle"
|
||
- "--web.external-url=https://metrics.gendsgn.ru/prometheus"
|
||
- "--web.route-prefix=/"
|
||
volumes:
|
||
- ./ops/metrics/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||
- ./ops/metrics/prometheus/rules:/etc/prometheus/rules:ro
|
||
- prometheus_data:/prometheus
|
||
expose:
|
||
- "9090"
|
||
networks:
|
||
- shared
|
||
- infra
|
||
mem_limit: 2g
|
||
logging: *default-logging
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/healthy"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
|
||
# ── Loki: логи. Monolithic-режим — рекомендован до ~20 ГБ/сутки ──────────────
|
||
# У нас ~39 МБ/сутки (замер 24.08), запас в 500 раз. Микросервисный режим здесь
|
||
# был бы чистой сложностью без выигрыша.
|
||
loki:
|
||
image: grafana/loki:3.3.2
|
||
container_name: gendesign-loki
|
||
restart: unless-stopped
|
||
user: "10001:10001"
|
||
command: ["-config.file=/etc/loki/loki-config.yml"]
|
||
volumes:
|
||
- ./ops/metrics/loki/loki-config.yml:/etc/loki/loki-config.yml:ro
|
||
- loki_data:/loki
|
||
expose:
|
||
- "3100"
|
||
networks:
|
||
- shared
|
||
mem_limit: 1g
|
||
logging: *default-logging
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 10
|
||
start_period: 60s
|
||
|
||
# ── Alertmanager: маршрутизация и дедупликация алертов ───────────────────────
|
||
# Шлёт в Telegram НАПРЯМУЮ с Beget, а не через бэкенд МЕРЫ. Это осознанно:
|
||
# доставка алерта не должна зависеть от хоста, про который алерт. Если Poincare
|
||
# лёг, сообщение об этом обязано уйти без его участия.
|
||
alertmanager:
|
||
image: prom/alertmanager:v0.28.0
|
||
container_name: gendesign-alertmanager
|
||
restart: unless-stopped
|
||
user: "65534:65534"
|
||
# За профилем: канал доставки алертов — открытый вопрос #3078 (тот же чат,
|
||
# что у вебхука GlitchTip, или отдельный; порог ночной побудки). Пока он не
|
||
# решён, стек метрик поднимается и работает без Alertmanager, а не ждёт его.
|
||
# Включается добавлением `alerts` в COMPOSE_PROFILES — см. часть 5.
|
||
profiles: ["alerts"]
|
||
command:
|
||
- "--config.file=/etc/alertmanager/alertmanager.yml"
|
||
- "--storage.path=/alertmanager"
|
||
- "--web.external-url=https://metrics.gendsgn.ru/alertmanager"
|
||
env_file:
|
||
- path: ./backend/.env.runtime
|
||
required: false
|
||
- path: ./backend/.env
|
||
required: false
|
||
environment:
|
||
# Токен и чат берутся из окружения — в репозиторий не попадают.
|
||
# Отсутствие проверяется в deploy-metrics.yml ДО подъёма: пустой токен
|
||
# даёт Alertmanager, который стартует зелёным и молча ничего не шлёт.
|
||
METRICS_TELEGRAM_BOT_TOKEN: ${METRICS_TELEGRAM_BOT_TOKEN:-}
|
||
METRICS_TELEGRAM_CHAT_ID: ${METRICS_TELEGRAM_CHAT_ID:-}
|
||
volumes:
|
||
- ./ops/metrics/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
||
- alertmanager_data:/alertmanager
|
||
expose:
|
||
- "9093"
|
||
networks:
|
||
- shared
|
||
mem_limit: 256m
|
||
logging: *default-logging
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9093/-/healthy"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
|
||
# ── Grafana: витрина ─────────────────────────────────────────────────────────
|
||
grafana:
|
||
image: grafana/grafana:11.5.1
|
||
container_name: gendesign-grafana
|
||
restart: unless-stopped
|
||
user: "472:472"
|
||
env_file:
|
||
- path: ./backend/.env.runtime
|
||
required: false
|
||
- path: ./backend/.env
|
||
required: false
|
||
environment:
|
||
# Доступ закрыт Caddy basic_auth снаружи. Внутренний вход Grafana оставлен
|
||
# включённым намеренно: анонимный Viewer + отключённый логин лишили бы
|
||
# возможности что-то настроить, а один общий пароль в Caddy не даёт
|
||
# разделения ролей внутри.
|
||
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
|
||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-}
|
||
GF_SERVER_ROOT_URL: https://metrics.gendsgn.ru/
|
||
GF_SERVER_SERVE_FROM_SUB_PATH: "false"
|
||
# Телеметрия наружу — выключена. Отдельный хост, отдельный провайдер, и не
|
||
# хочется, чтобы наблюдатель сам ходил в интернет без нужды.
|
||
GF_ANALYTICS_REPORTING_ENABLED: "false"
|
||
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
|
||
GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES: "false"
|
||
GF_NEWS_NEWS_FEED_ENABLED: "false"
|
||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||
# Датасорс к БД GlitchTip: пароль read-only роли из окружения.
|
||
GLITCHTIP_RO_PASSWORD: ${GLITCHTIP_RO_PASSWORD:-}
|
||
TRADEIN_RO_PASSWORD: ${TRADEIN_RO_PASSWORD:-}
|
||
GENDESIGN_RO_PASSWORD: ${GENDESIGN_RO_PASSWORD:-}
|
||
volumes:
|
||
- ./ops/metrics/grafana/provisioning:/etc/grafana/provisioning:ro
|
||
- ./ops/metrics/grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||
- grafana_data:/var/lib/grafana
|
||
expose:
|
||
- "3000"
|
||
networks:
|
||
- shared
|
||
- infra
|
||
mem_limit: 512m
|
||
logging: *default-logging
|
||
depends_on:
|
||
- prometheus
|
||
- loki
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
|
||
volumes:
|
||
prometheus_data:
|
||
loki_data:
|
||
grafana_data:
|
||
alertmanager_data:
|
||
|
||
networks:
|
||
shared:
|
||
external: true
|
||
name: gendesign_shared
|
||
infra:
|
||
external: true
|
||
name: gendesign_default
|