Перейти к основному содержимому

Наблюдаемость

api и worker предоставляют служебный (ops) листенер (по умолчанию 0.0.0.0:9090, настраивается через METRICS_ADDR, пустое значение отключает) со следующими эндпоинтами:

ЭндпоинтНазначение
GET /metricsЭндпоинт сбора метрик Prometheus
GET /healthzLiveness — 200, пока процесс запущен, без проверки зависимостей
GET /readyzReadiness — пинг Postgres, пинг Redis, миграции актуальны

Служебный порт намеренно не защищён аутентификацией: метрики раскрывают операционные детали (имена кластеров, частоту ошибок), поэтому привязывайте его к внутреннему интерфейсу/сети и снимайте метрики оттуда. Никогда не публикуйте его через обратный прокси. В dev-стеке compose api пробрасывается на хостовый порт 9090, а worker — на 9091.

Кроме того, api оставляет /healthz и /readyz на своём публичном API-порту (часть контракта OpenAPI); обе поверхности выполняют одни и те же проверки.

Семантика readiness

/readyz возвращает 503 {"status":"not_ready","dependency":"..."} с именем первой отказавшей зависимости: postgres, redis или migrations.

Проверка migrations сравнивает версию схемы в goose_db_version с самой новой миграцией, встроенной в бинарник на момент сборки: база данных, отстающая от бинарника, не готова (сначала примените миграции — автоматически они никогда не применяются); база данных, опережающая бинарник, принимается (rolling upgrade). Ни разу не мигрированная база данных не готова.

Конфигурация scrape для Prometheus

scrape_configs:
- job_name: planeon-api
static_configs:
- targets: ["your-host:9090"]
- job_name: planeon-worker
static_configs:
- targets: ["your-host:9091"]

Справочник метрик

Кардинальность ограничена намеренно: метки — это kind задания, имя cluster, state сессии, status, outcome и method HTTP — никогда не vmid, id задания или путь запроса.

МетрикаТипМеткиЗначение
planeon_jobs_depthgaugekind, status, clusterЗадания в очереди/в работе прямо сейчас (экспортируют и api, и worker — агрегируйте экспортеры через max by; cluster пуст для заданий, не привязанных к кластеру)
planeon_jobs_oldest_queued_age_secondsgaugekind, clusterВозраст самого старого задания в очереди; 0, когда очередь пуста
planeon_jobs_processed_totalcounterkind, outcomeИсходы выполнения раннера: succeeded, retried, failed (финальный), released (повторная постановка в очередь при graceful shutdown), lease_lost (результат отброшен после reclaim)
planeon_job_duration_secondshistogramkindВремя выполнения обработчика
planeon_job_lease_reclaims_totalcounterkindПросроченные lease worker-процессов, возвращённые в оборот (задание поставлено в очередь заново или провалено после падения worker)
planeon_reconcile_duration_secondshistogramОдин цикл reconcile по всем активным пулам
planeon_reconcile_cycles_totalcounteroutcomeЦиклы reconcile: ok / error
planeon_pve_requests_totalcountercluster, method, outcomeВызовы Proxmox API: success / error / timeout
planeon_pve_request_duration_secondshistogramclusterВремя round-trip вызова Proxmox API
planeon_sessionsgaugestate, clusterСессии по состоянию жизненного цикла и кластеру PVE
planeon_db_pool_*gauge/counterПул pgx: соединения (max/total/idle/acquired/constructing), счётчики acquire и суммарное время ожидания
go_*, process_*Стандартные коллекторы рантайма

Gauge-метрики очереди и сессий намеренно вычисляются из Postgres на момент scrape обоими приложениями: если worker недоступен, растущая очередь всё равно видна через api. Настраивайте дашборды на конкретный job или агрегируйте через max by (...).

Дашборд Grafana

Скачайте planeon-overview.json (раздаётся этим сайтом документации), импортируйте его в Grafana и выберите свой источник данных Prometheus. Панели сгруппированы в ряды Availability, Jobs, Reconcile, PVE, Sessions и DB pool; единицы измерения и пороги согласованы с рекомендациями по алертингу ниже. Переменные вверху фильтруют все панели: $job / $instance выбирают цель скрейпа (api или worker — оба экспортируют gauge-метрики из БД), $cluster фильтрует по имени кластера PVE, а $tenant — задел на будущее: остаётся в значении «All», пока метрики не получат лейбл tenant.

Рекомендации по алертингу

Платформа не поставляется со встроенным алертингом — используйте собственный стек. Рекомендуемые правила:

groups:
- name: planeon
rules:
- alert: PlaneonReadyzDown
expr: up == 0
for: 3m
annotations:
summary: "Planeon {{ $labels.job }} scrape target down"
- alert: PlaneonQueueBacklogAge
expr: max(planeon_jobs_oldest_queued_age_seconds) > 300
for: 10m
annotations:
summary: "Jobs waiting more than 5 minutes — worker down or stuck?"
- alert: PlaneonJobFailures
expr: sum(rate(planeon_jobs_processed_total{outcome="failed"}[15m])) > 0
for: 15m
annotations:
summary: "Jobs failing terminally"
- alert: PlaneonLeaseReclaims
expr: sum(increase(planeon_job_lease_reclaims_total[15m])) > 0
annotations:
summary: "A worker crashed mid-job or stalled past its lease — check worker restarts/logs"
- alert: PlaneonPVEErrors
expr: |
sum by (cluster) (rate(planeon_pve_requests_total{outcome!="success"}[10m]))
/ sum by (cluster) (rate(planeon_pve_requests_total[10m])) > 0.2
for: 10m
annotations:
summary: "More than 20% of PVE API calls failing on {{ $labels.cluster }}"
- alert: PlaneonDBPoolSaturated
expr: planeon_db_pool_acquired_conns >= planeon_db_pool_max_conns
for: 5m
annotations:
summary: "pgx pool exhausted on {{ $labels.instance }}"