gendesign/scripts/setup-metrics-exporter-dsn.sh
bot-backend beafe6925b
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 / openapi-codegen-check (pull_request) Successful in 1m55s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Successful in 17m25s
fix(observability): агент не падает из-за переменной чужой роли (#3078)
Джоба agent-apps упала целиком:

    error while interpolating services.postgres-exporter-infra.environment.
    DATA_SOURCE_NAME: required variable INFRA_EXPORTER_DSN is missing a value

INFRA_EXPORTER_DSN нужен экспортеру с profiles: ["infra"], который на
продуктовом хосте не поднимается вовсе. Но compose интерполирует ВЕСЬ файл
до фильтрации по профилям, поэтому `${VAR:?}` роняет команду из-за чужой
переменной. Вместе с агентом не поднялись alloy, node-exporter и cadvisor,
которым никакой DSN не нужен. Симметрично упал бы и инфраструктурный агент -
на двух продуктовых переменных.

Второй дефект в той же цепочке: GENDESIGN_EXPORTER_DSN тоже отсутствовал.
setup-metrics-exporter-dsn.sh читает только runtime-файл окружения бэкенда, а
DATABASE_URL и TRADEIN_DATABASE_URL живут в основном. Значит подстановка
всегда была пустой, add_key печатал "нечем заполнить" и выходил с кодом 0 -
мягкий пропуск встречался с жёстким требованием compose.

Стало:
- compose: `:-` вместо `:?` у трёх DSN. Интерполяция больше не может упасть.
- deploy-metrics.yml: профиль экспортеров включается, только если нужные ЭТОЙ
  роли DSN заполнены; иначе ::warning и агент поднимается без экспортера.
  Громкость не убрана, а перенесена туда, где роль известна. Тот же приём, что
  уже применён к Alertmanager в джобе server.
- setup-metrics-exporter-dsn.sh: читает оба файла окружения (базовый, затем
  runtime - он перекрывает). Пишет по-прежнему только в runtime, лишних копий
  пароля не заводит.

Почему `:-` не ослабление: пустой DATA_SOURCE_NAME поднял бы экспортер,
который молча не отдаёт метрик, - ровно тот тихий отказ, ради которого весь
стек и заводится. Поэтому пустой DSN теперь означает "профиль не включаем",
а не "поднимаем пустым".

Известное следствие, отмеченное в коде: INFRA_EXPORTER_DSN не собирает никто -
скрипт знает только про GENDESIGN_/TRADEIN_ и работает на продуктовом хосте.
Пока это так, инфраструктурный агент будет честно предупреждать, что метрик
Postgres инфры нет, вместо того чтобы падать целиком.

Тесты (3) структурные, проверяют оба конца инварианта: обязательности не
вернулись в compose; каждая DSN-переменная проверяется в деплое; профили не
захардкожены. Фальсификация: на исходных файлах краснеют все три.
2026-08-26 12:45:31 +03:00

93 lines
4.2 KiB
Bash
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.

#!/usr/bin/env bash
# Собирает строки подключения для postgres_exporter на продуктовом хосте из
# паролей, которые там уже есть. Идемпотентен: заданные значения не трогает.
#
# ПОЧЕМУ НЕ ЗАВОДИМ ОТДЕЛЬНЫЕ СЕКРЕТЫ. Каждая новая копия пароля — ещё одно
# место, откуда он может утечь, и ещё одно, которое забудут повернуть при
# ротации. Экспортеру нужны те же учётки, что уже лежат в окружении хоста.
#
# Запускать на продуктовом хосте. Вызывается из deploy-metrics.yml.
set -euo pipefail
ENVF=/opt/gendesign/backend/.env.runtime
if [ ! -f "$ENVF" ]; then
echo " ОШИБКА: не найден файл окружения бэкенда." >&2
exit 1
fi
# Источники DSN лежат НЕ здесь. `DATABASE_URL` и `TRADEIN_DATABASE_URL` живут в
# основном файле окружения бэкенда, а $ENVF (runtime) хранит только то, что
# дописывается на хосте: SENTRY_RELEASE, METRICS_* и т. п. Скрипт читал лишь
# runtime, поэтому подстановка `${DATABASE_URL:-}` всегда была пустой, add_key
# молча писал «нечем заполнить» и выходил с кодом 0 — а compose затем падал на
# обязательной переменной. Мягкий пропуск встречался с жёстким требованием.
#
# Читаем оба, в порядке приоритета: сначала базовый, потом runtime (он должен
# перекрывать). Пишем по-прежнему ТОЛЬКО в runtime — лишних копий пароля не
# заводим.
BASE_ENVF=/opt/gendesign/backend/.env
set -a
if [ -f "$BASE_ENVF" ]; then
# shellcheck source=/dev/null
. "$BASE_ENVF"
fi
# shellcheck source=/dev/null
. "$ENVF"
set +a
add_key() {
local key="$1" value="$2"
if grep -qE "^${key}=" "$ENVF" 2>/dev/null; then
echo " $key — уже задан, не трогаю"
return 0
fi
if [ -z "$value" ]; then
echo " $key — нечем заполнить, пропускаю (метрики этой базы не поедут)"
return 0
fi
printf '%s=%s\n' "$key" "$value" >> "$ENVF"
echo " $key — записан"
}
# Экспортер ходит по сетевому алиасу, а не по IP: адреса контейнеров меняются
# при каждом пересоздании, и DSN с IP протух бы на первом же деплое.
add_key GENDESIGN_EXPORTER_DSN \
"${GENDESIGN_EXPORTER_DSN:-${DATABASE_URL:-}}"
add_key TRADEIN_EXPORTER_DSN \
"${TRADEIN_EXPORTER_DSN:-${TRADEIN_DATABASE_URL:-}}"
# postgres_exporter не понимает схему postgresql+psycopg:// из SQLAlchemy —
# ему нужна чистая postgresql://. Приводим, если досталась приложенческая форма.
python3 - "$ENVF" <<'PY'
import io, re, sys
path = sys.argv[1]
with io.open(path, encoding="utf-8") as fh:
lines = fh.readlines()
changed = False
out = []
for line in lines:
m = re.match(r'^((?:GENDESIGN|TRADEIN)_EXPORTER_DSN)=(.*)$', line.rstrip('\n'))
if not m:
out.append(line)
continue
key, dsn = m.group(1), m.group(2)
fixed = re.sub(r'^postgresql\+\w+://', 'postgresql://', dsn)
fixed = re.sub(r'^postgres\+\w+://', 'postgresql://', fixed)
if 'sslmode=' not in fixed:
fixed += ('&' if '?' in fixed else '?') + 'sslmode=disable'
if fixed != dsn:
changed = True
print(" %s — схема приведена к postgresql:// для экспортера" % key)
out.append("%s=%s\n" % (key, fixed))
if changed:
with io.open(path, "w", encoding="utf-8", newline="\n") as fh:
fh.writelines(out)
PY
echo " строки подключения экспортеров готовы"