4 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c093eaafe5 |
fix(observability): пароли не уезжают в Loki — скруббер учётных данных (#3114)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
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 / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m58s
CI / backend-tests (pull_request) Successful in 17m33s
postgres_exporter при неудачном скрейпе печатает полный DSN вместе с паролем. Замер по Loki за сутки: 104 такие строки на инфра-хосте и 106 на продуктовом. При ретенции 30 дней это порядка 6000 строк с паролями БД в хранилище, доступ к которому даёт вход в Grafana - причём внешний basic_auth с витрины сегодня же снят (#3113). Проверено экспериментом, а не предположено. Напрашивалось передать пароль отдельно от строки подключения (DATA_SOURCE_URI + DATA_SOURCE_USER + DATA_SOURCE_PASS_FILE). Прогон на скретч-контейнерах с настоящим файлом запросов показал: НЕ помогает - экспортер собирает строку сам и логирует её целиком. Первые три попытки воспроизведения были неинформативны (контейнер падал сразу; скрейпа не было; не подключён файл кастомных запросов) - утечка воспроизводится только при неудачном скрейпе с нашим queries.yml. Стало: ступень loki.process между источником журнала и loki.write, маскирует пароль в любом URL вида scheme://user:pass@host. Пользователь и адрес остаются - без них строка ошибки перестаёт годиться для диагностики. Ровно одна группа захвата: Alloy заменяет содержимое групп, вторая затёрла бы имя пользователя. Это защита в глубину, а не замена причине. Конкретно эта ошибка уходит грантом pg_monitor (запрос pg_wal_bytes в нашем queries.yml требует pg_ls_waldir) - это боевая БД и остаётся за владельцем. Скруббер же ловит любой пароль в URL, включая компоненты, о которых мы ещё не знаем. Регулярка без экранирования намеренно: Alloy не принимает \s в строке (unknown escape sequence), поэтому класс задан явным пробелом. Оба конфига прогнаны через `alloy fmt` образом grafana/alloy:v1.6.1 - синтаксис ok. Тесты (8) берут выражение ИЗ КОНФИГА и применяют к настоящей строке из прода: пароль исчезает; пользователь и адрес остаются; обычные URL и почтовые адреса не портятся; журнал направлен в скруббер, а не мимо него. Фальсификация: на исходных конфигах краснеют все 8. tests/ops целиком - 43 passed. |
||
|
|
a72d39d74d |
fix(observability): cAdvisor 0.55.1 — 0.52 не видит контейнеры на Docker 29
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
#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. |
||
|
|
c6e15954ba |
fix(observability): контейнерные метрики терялись целиком + два хвоста стека
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
|
||
|
|
309d273f3f |
feat(observability): стек метрик и логов — Prometheus, Loki, Grafana, агенты на обоих хостах
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 |