All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
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 / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Три независимых дефекта, найденных на живом проде после подъёма стека метрик.
1. cAdvisor-метрики не доезжали ВООБЩЕ. В Prometheus ноль имён container_*
при 2034 именах всего, хотя cAdvisor отдаёт 880 рядов, scrape-таргет в
alloy health=up с последним скрейпом 10 мс назад, а remote_write рабочий
(node/postgres идут через него же и доезжают). Методом исключения — потери
в prometheus.relabel.cadvisor_trim, во втором правиле:
rule { source_labels = ["name"], regex = "", action = "drop" }
Замысел был выкинуть безымянные cgroup-ряды (id="/"). Но regex в Alloy
документированно дефолтится в (.*), и пустая строка неотличима от
незаданного значения — такой drop рискует выкидывать вообще всё, что и
наблюдалось. Заменено на однозначное keep regex=".+" — тот же замысел,
без зависимости от того, как трактуется пустой regex.
2. healthcheck alloy не мог пройти никогда: дёргал wget, которого в образе
grafana/alloy нет (как и curl, и nc). Контейнер вечно unhealthy при
полностью исправном alloy — ложная тревога, маскирующая настоящие сбои.
Заменено на сырой HTTP через bash /dev/tcp, без внешних утилит.
3. Prometheus раз в минуту писал "lookup alertmanager: no such host" и держал
up{job="alertmanager"}=0. Alertmanager намеренно за профилем alerts до
решения #3078 — дефект не в профиле, а в безусловной ссылке на сервис.
Оба места (alerting.alertmanagers и job_name: alertmanager) переведены на
file_sd_configs с файлом целей, по умолчанию пустым: целей нет — ошибок
тоже нет. Prometheus перечитывает file_sd на лету, поэтому включение
профиля сведётся к наполнению файла, без рестарта и правки конфига.
Файл целей смонтирован в сервис prometheus явным volume.
Проверено на живом хосте, не на глаз:
- alloy fmt обоих .alloy в одноразовом контейнере grafana/alloy:v1.6.1 - exit 0
- promtool check config в prom/prometheus:v3.1.0 - valid, 16 rules found
- механизм нового healthcheck выполнен внутри работающего gendesign-alloy:
первая строка ответа "HTTP/1.0 200 OK", grep матчится, RESULT=HEALTHY
- наличие bash/head/grep/printf в образе alloy подтверждено command -v
206 lines
10 KiB
YAML
206 lines
10 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
|
||
# file_sd для job "alertmanager" — см. комментарий в prometheus.yml.
|
||
# Забыть этот монт — значит вернуть "no such host" из-за отсутствующего
|
||
# файла целей (был инцидент, когда похожий пропущенный монт положил Caddy).
|
||
- ./ops/metrics/prometheus/alertmanager_targets.yml:/etc/prometheus/alertmanager_targets.yml: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
|