gendesign/ops/metrics/alloy/alloy-infra.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

142 lines
6.3 KiB
Text
Raw 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 — агент на ИНФРАСТРУКТУРНОМ хосте (Beget). Приёмник стоит рядом, на этом же
// хосте, поэтому пишем напрямую по docker-сети: без TLS, без basic_auth, без интернета.
//
// Вариант для продуктового хоста — alloy-apps.alloy: там тот же набор источников, но
// доставка идёт исходящим HTTPS через metrics.gendsgn.ru.
//
// Метка host проставляется здесь, один раз, в external_labels — если ставить её ещё и
// на стороне Prometheus, результат зависит от honor_labels и перестаёт быть очевидным.
logging {
level = "warn"
format = "logfmt"
}
// ═══ КУДА ОТПРАВЛЯЕМ ═══════════════════════════════════════════════════════════
prometheus.remote_write "central" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
external_labels = {
host = sys.env("METRICS_HOST_LABEL"),
}
}
loki.write "central" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
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"
}
// cAdvisor отдаёт ряды на каждый слой файловой системы и каждое устройство.
// Без прополки TSDB растёт на порядок ради данных, которые никто не открывает.
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"
}
}
// ═══ МЕТРИКИ ИНФРАСТРУКТУРНОЙ БД ═══════════════════════════════════════════════
// Профиль infra в docker-compose.metrics-agent.yml поднимает ровно один экспортер —
// по кластеру с базами forgejo и glitchtip.
prometheus.scrape "postgres_infra" {
targets = [
{ __address__ = "gendesign-pg-exporter-infra:9187", job = "postgres", db = "infra" },
]
forward_to = [prometheus.remote_write.central.receiver]
scrape_interval = "60s"
}
// ═══ ЛОГИ ══════════════════════════════════════════════════════════════════════
loki.source.journal "host" {
// Путь задан ЯВНО. Без него libsystemd включает SD_JOURNAL_LOCAL_ONLY, и из
// контейнера хостовый журнал не виден — при этом ошибки нет, просто пустой поток.
path = "/var/log/journal"
// При рестарте агента не тянем всю историю заново: journald держит ~13 суток,
// а повторная заливка создала бы дубликаты и упёрлась в reject_old_samples.
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 = []
// Внутренние поля journald приходят с префиксом __ и без переименования
// отбрасываются. Оставляем ровно те, по которым потом фильтруют.
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"
}