gendesign/ops/metrics/alloy/alloy-apps.alloy
bot-backend a72d39d74d
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-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
fix(observability): cAdvisor 0.55.1 — 0.52 не видит контейнеры на Docker 29
#3108 не починил пустые панели по контейнерам, и правило relabel там было ни
при чём. Настоящая причина глубже: Docker 29 на обоих хостах работает через
containerd-snapshotter (Storage Driver = overlayfs), а cAdvisor 0.52 ищет
метаданные слоя в легаси-хранилище:

    failed to identify the read-write layer ID for container "<id>"
    - open /rootfs/var/lib/docker/image/overlayfs/layerdb/mounts/<id>/mount-id:
      no such file or directory

При снапшоттере этого каталога нет вовсе — в /var/lib/docker/image/ лежит
только identity-cache.db, метаданные слоёв живут в containerd. Обработчик
контейнера не создаётся, и наружу уходит ровно один ряд: корневой cgroup
container_last_seen{id="/"}. Отсюда и «нодата» на панелях контейнеров при
живых node/postgres/app метриках.

0.55.1 умеет читать containerd-снапшоттер. Проверено пробами с ПРОДОВЫМИ
флагами на обоих хостах (одноразовые контейнеры, убраны за собой):

  Beget    (Docker 29.4.1): 22 ряда, все с name=, ошибок rw-layer 0
  Poincare (Docker 29.7.2): 23 ряда, все с name=, ошибок rw-layer 0

Примеры рядов — name="gendesign-alloy", name="gendesign-backend-1",
name="gendesign-osrm-1", с лейблом image. То есть именно то, чего не хватало
панелям.

Заодно поправлен комментарий, который я же вписал в cadvisor_trim в #3108: он
объяснял пустые панели трактовкой пустого regex, а это оказалось неверно.
Правило корректно и остаётся (корневой ряд приходит с name="" и должен
отсеиваться), но объяснение рядом с ним вводило в заблуждение.

Почему тег .1, а не .0: в реестре нет ни v0.53.0, ни v0.54.0, ни v0.55.0 —
только v0.54.1 и v0.55.1. Проверял по списку тегов, а не подбором.

Проверки: yaml.safe_load compose — ok; alloy fmt обоих .alloy в
grafana/alloy:v1.6.1 — exit 0.
2026-08-26 13:40:03 +03:00

179 lines
7.4 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Alloy — агент на ПРОДУКТОВОМ хосте (Poincare). Приёмник стоит у другого провайдера,
// поэтому доставка идёт исходящим HTTPS через metrics.gendsgn.ru под basic_auth.
//
// ПОЧЕМУ PUSH. Альтернатива — открыть на Poincare входящие порты под скрейп из центра.
// ufw там держит только 22/80/443, и расширять периметр ради мониторинга — плохой
// размен. Исходящий HTTPS уже разрешён, ничего открывать не нужно.
//
// ПРИ ОБРЫВЕ КАНАЛА агент копит в WAL и досылает. Pull-скрейп в этой ситуации просто
// потерял бы точки — а обрыв между площадками это ровно тот случай, ради которого
// мониторинг и заводится.
logging {
level = "warn"
format = "logfmt"
}
// ═══ КУДА ОТПРАВЛЯЕМ ═══════════════════════════════════════════════════════════
prometheus.remote_write "central" {
endpoint {
url = "https://metrics.gendsgn.ru/ingest/prometheus/api/v1/write"
basic_auth {
username = sys.env("METRICS_INGEST_USER")
password = sys.env("METRICS_INGEST_PASSWORD")
}
// Очередь на случай недоступности приёмника. Дефолты рассчитаны на облако с
// быстрым каналом; здесь между площадками 17 мс и приёмник может уехать в
// рестарт при деплое — даём запас, чтобы не терять точки на ровном месте.
queue_config {
capacity = 10000
max_shards = 5
min_shards = 1
max_samples_per_send = 2000
batch_send_deadline = "10s"
retry_on_http_429 = true
}
}
// Досыл после обрыва. Держим сутки: суточного окна хватает и на ночной рестарт
// приёмника, и на разбор утром.
wal {
truncate_frequency = "2h"
max_keepalive_time = "24h"
}
external_labels = {
host = sys.env("METRICS_HOST_LABEL"),
}
}
loki.write "central" {
endpoint {
url = "https://metrics.gendsgn.ru/ingest/loki/loki/api/v1/push"
basic_auth {
username = sys.env("METRICS_INGEST_USER")
password = sys.env("METRICS_INGEST_PASSWORD")
}
}
external_labels = {
host = sys.env("METRICS_HOST_LABEL"),
}
}
// ═══ МЕТРИКИ ХОСТА И КОНТЕЙНЕРОВ ═══════════════════════════════════════════════
prometheus.scrape "node" {
targets = [
{ __address__ = "node-exporter:9100", job = "node" },
]
forward_to = [prometheus.remote_write.central.receiver]
scrape_interval = "30s"
}
prometheus.scrape "cadvisor" {
targets = [
{ __address__ = "cadvisor:8080", job = "cadvisor" },
]
forward_to = [prometheus.relabel.cadvisor_trim.receiver]
scrape_interval = "30s"
}
prometheus.relabel "cadvisor_trim" {
forward_to = [prometheus.remote_write.central.receiver]
rule {
source_labels = ["__name__"]
regex = "container_(memory_(usage_bytes|working_set_bytes|rss)|cpu_(usage_seconds_total|cfs_throttled_seconds_total)|network_(receive|transmit)_bytes_total|fs_(usage|limit)_bytes|last_seen|spec_memory_limit_bytes|start_time_seconds|processes)"
action = "keep"
}
// Служебные ряды без имени (корневой cgroup, id="/") только зашумляют графики.
// keep .+ вместо drop с пустым regex — та же мысль, но без зависимости от
// трактовки пустого regex: в Alloy он неотличим от незаданного, а незаданный
// дефолтится в (.*).
//
// NB: на пустые панели это правило НЕ влияло. Причина была в cAdvisor 0.52 на
// Docker 29 — до сюда доезжал ровно один ряд, корневой. Лечится версией 0.55.1.
rule {
source_labels = ["name"]
regex = ".+"
action = "keep"
}
}
// ═══ МЕТРИКИ ОБЕИХ БОЕВЫХ БД ═══════════════════════════════════════════════════
// Именно здесь живут ряды, из-за отсутствия которых раздутие `listings` копилось
// 91 день незамеченным: WAL в сутки, n_tup_upd против n_tup_hot_upd, рост TOAST.
prometheus.scrape "postgres" {
targets = [
{ __address__ = "gendesign-pg-exporter-gendesign:9187", job = "postgres", db = "gendesign" },
{ __address__ = "gendesign-pg-exporter-tradein:9187", job = "postgres", db = "tradein" },
]
forward_to = [prometheus.remote_write.central.receiver]
scrape_interval = "60s"
}
// ═══ МЕТРИКИ ПРИЛОЖЕНИЙ ════════════════════════════════════════════════════════
// Эндпоинты появляются в части 3. До этого скрейп просто отдаёт `up 0` — и это
// правильно: цель видна как недоступная, а не отсутствует молча.
prometheus.scrape "apps" {
targets = [
{ __address__ = "gendesign-backend-1:8000", job = "app", app = "sitefinder" },
{ __address__ = "tradein-backend:8000", job = "app", app = "mera" },
]
metrics_path = "/metrics"
forward_to = [prometheus.remote_write.central.receiver]
scrape_interval = "30s"
scrape_timeout = "20s"
}
// ═══ ЛОГИ ══════════════════════════════════════════════════════════════════════
loki.source.journal "host" {
path = "/var/log/journal"
max_age = "12h"
format_as_json = false
labels = {
job = "journal",
}
relabel_rules = loki.relabel.journal.rules
forward_to = [loki.write.central.receiver]
}
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
rule {
source_labels = ["__journal__hostname"]
target_label = "node"
}
rule {
source_labels = ["__journal_container_name"]
target_label = "container"
}
rule {
source_labels = ["__journal_priority_keyword"]
target_label = "level"
}
}
// ═══ САМОКОНТРОЛЬ ══════════════════════════════════════════════════════════════
prometheus.exporter.self "alloy" {}
prometheus.scrape "alloy_self" {
targets = prometheus.exporter.self.alloy.targets
forward_to = [prometheus.remote_write.central.receiver]
scrape_interval = "60s"
}