Files
han-app/VM3_signoz/Signoz/docs/BACKEND_OTLP.md
T

15 KiB
Raw Blame History

Подключение HAN Chat к SigNoz

Схема

Приложения на ВМ1 и ВМ2 отправляют OTLP только своему локальному otel-collector:

VM1 containers -> VM1 otel-collector:4317 -> 192.168.0.5:4317 -> SigNoz
VM2 containers -> VM2 otel-collector:4317 -> 192.168.0.5:4317 -> SigNoz

Локальный Collector выполняет редактирование чувствительных атрибутов, добавляет service.namespace=han-chat, окружение и версию, сохраняет очередь на диск и пересылает данные в SigNoz.

Collectors имеют отдельные bounded persistent queue volumes. ВМ2 не использует Docker hostname collector ВМ1. Недоступность SigNoz/Collector fail-open для business/Safety readiness; переполнение очереди создаёт alert и controlled drop.

Настройка backend

В non-secret env manifest каждой VM: cd /opt/han-chat/backend

OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
OTEL_REMOTE_ENDPOINT=192.168.0.5:4317
OTEL_REMOTE_AUTH_HEADER=
OTEL_REMOTE_TLS_INSECURE=true

OTEL_REMOTE_TLS_INSECURE=true допустим только потому, что трафик идёт по изолированной приватной сети. Self-hosted SigNoz по умолчанию не требует authorization header. Не подставляйте фиктивный токен.

Применение:

cd /opt/han-chat/backend
# Перед сменой sampling остановите producers и дайте persistent queue уйти.
docker compose stop api-backend sms-service sms-worker
until ! docker compose logs --since=2m otel-collector |
  grep -Eq 'queue is full|sending_queue.*[1-9][0-9]*'; do sleep 15; done

# Сначала validate той же версией Collector, которая закреплена в Compose.
docker run --rm \
  -e APP_ENV=validate -e RELEASE_VERSION=validate \
  -e OTEL_REMOTE_ENDPOINT=192.168.0.5:4317 \
  -e OTEL_REMOTE_AUTH_HEADER= -e OTEL_REMOTE_TLS_INSECURE=true \
  --tmpfs /var/lib/otelcol/queue:rw,noexec,nosuid,size=16m,mode=0777 \
  -v "$PWD/observability/otel-collector.yaml:/etc/otelcol/config.yaml:ro" \
  otel/opentelemetry-collector-contrib:0.117.0 \
  validate --config=/etc/otelcol/config.yaml

docker compose config >/dev/null
docker compose up -d --force-recreate otel-collector
docker compose up -d --build api-backend sms-service sms-worker
docker compose up -d --wait api-backend sms-service sms-worker

# Nginx хранит IP Docker upstream после загрузки config. После пересоздания
# API/SMS перечитайте Docker DNS без остановки edge.
docker compose exec -T nginx nginx -t -c /tmp/nginx.conf
docker compose kill -s HUP nginx

docker compose ps otel-collector
docker compose logs --tail=200 otel-collector
curl -fsS https://chat.han0107.ru/api/v1/public/app-config >/dev/null

Не удаляйте observability/.otel-queue: это bounded persistent queue, которая обеспечивает recovery после недоступности SigNoz.

Проверка сети с backend-хоста:

timeout 3 bash -c 'exec 3<>/dev/tcp/192.168.0.5/4317' \
  && echo 'OTLP gRPC reachable'
curl -fsS --max-time 5 \
  -H 'Content-Type: application/json' \
  --data '{"resourceSpans":[]}' \
  http://192.168.0.5:4318/v1/traces

Если используется только gRPC, после диагностики порт 4318 можно закрыть.

End-to-end тест канала

Одна лишь доступность TCP не доказывает запись в ClickHouse. Отправьте тестовые трейсы через локальный Collector из его Docker-сети:

cd /opt/han-chat/backend
chmod +x deployment/scripts/verify-observability.sh
./deployment/scripts/verify-observability.sh

Скрипт проверяет внутренние SMS metrics endpoints, отправляет canary traces и проверяет свежие exporter/queue ошибки. Эквивалентная ручная отправка:

docker run --rm --network han-chat-observability \
  ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
  traces --otlp-endpoint otel-collector:4317 --otlp-insecure \
  --service han-chat-otlp-smoke --traces 100 --rate 20

Collector сохраняет только 10% traces. Сто тестовых traces дают достаточно высокую вероятность увидеть результат; запуск с десятью traces может случайно не оставить ни одного.

Если имя образа/синтаксис изменились, используйте актуальный telemetrygen из релиза OpenTelemetry Collector Contrib. На production-хосте образ нужен только для smoke-теста и затем может быть удалён.

В SigNoz откройте Services или Traces и отфильтруйте:

service.name = han-chat-otlp-smoke
service.namespace = han-chat

Проверьте, что timestamps свежие. Затем изучите логи Collector:

docker compose logs --since=10m otel-collector

Не должно быть connection refused, tls, Unauthenticated, переполнения очереди или постоянных retry.

Outage/recovery acceptance

Проводите тест в согласованное окно. Нужны доступ к security group VM SigNoz, две SSH-сессии на backend и открытый UI SigNoz. Бизнес-сервисы и локальный Collector не останавливайте.

1. Зафиксировать baseline

На backend:

cd /opt/han-chat/backend
date -u '+baseline_started=%Y-%m-%dT%H:%M:%SZ'
docker compose ps otel-collector api-backend sms-service sms-worker
./deployment/scripts/verify-observability.sh

Получить внутренние метрики очереди через Python, уже имеющийся в sms-service:

collector_metrics() {
  docker compose exec -T sms-service python -c \
    'import urllib.request; print(urllib.request.urlopen(
      "http://otel-collector:8888/metrics", timeout=3
    ).read().decode())'
}

collector_metrics |
  grep -E 'otelcol_exporter_(queue_size|queue_capacity|send_failed)'

Перед тестом queue_size для exporter otlp/remote должен быть 0 или стабильно уменьшаться. Сохраните baseline:

collector_metrics > /tmp/otel-metrics-before.txt
docker compose logs --since=10m otel-collector \
  > /tmp/otel-collector-before.log

2. Отключить только remote OTLP

В security group VM SigNoz временно отключите входящее правило:

source: приватный IP backend
destination: 192.168.0.5
protocol/port: TCP 4317

Не меняйте TCP 22, маршруты приватной сети и правила самого backend. Запишите UTC-время изменения. На backend проверьте, что блокировка действительно применилась:

date -u '+outage_started=%Y-%m-%dT%H:%M:%SZ'
if timeout 5 bash -c 'exec 3<>/dev/tcp/192.168.0.5/4317'; then
  echo 'ОШИБКА: 4317 всё ещё доступен'
else
  echo 'OK: remote OTLP недоступен'
fi

3. Проверить fail-open бизнес-запросов

Укажите реальный публичный URL HAN Chat. Используйте read-only endpoint, чтобы не создавать бизнес-данные:

export HAN_BASE_URL='https://<HAN_CHAT_HOST>'
rm -f /tmp/otel-outage-http.tsv

for i in $(seq 1 24); do
  request_id="$(cat /proc/sys/kernel/random/uuid)"
  if ! code="$(curl -sS -o /dev/null -w '%{http_code}' \
    -H "X-Request-ID: $request_id" \
    "$HAN_BASE_URL/api/v1/public/content")"; then
    code=000
  fi
  printf '%s\t%s\t%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$code" "$request_id" |
    tee -a /tmp/otel-outage-http.tsv
  sleep 5
done

awk -F '\t' '$2 !~ /^2/ {print "FAIL", $0; failed=1} END {exit failed}' \
  /tmp/otel-outage-http.tsv

Все 24 запроса должны вернуть 2xx. Это подтверждает, что недоступность remote exporter не останавливает API. 429, 5xx или 000 требуют разбора; не считайте такой запуск успешным.

Чтобы очередь гарантированно получила достаточное число traces, пока порт заблокирован, отправьте canary через локальный Collector:

docker run --rm --network han-chat-observability \
  ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
  traces --otlp-endpoint otel-collector:4317 --otlp-insecure \
  --service han-chat-outage-canary --traces 100 --rate 20

4. Контролировать bounded queue

Во второй SSH-сессии сначала повторно объявите функцию collector_metrics из шага 1, затем каждые 10 секунд выполняйте:

collector_metrics() { docker compose exec -T sms-service python -c
'import urllib.request; print(urllib.request.urlopen("http://otel-collector:8888/metrics", timeout=3).read().decode())' }

collector_metrics > /tmp/otel-metrics-before.txt docker compose logs --since=10m otel-collector \

/tmp/otel-collector-before.log

collector_metrics | grep otelcol_exporter_queue

```bash
collector_metrics |
  grep -E 'otelcol_exporter_(queue_size|queue_capacity|send_failed)'
docker compose logs --since=30s otel-collector |
  grep -Ei 'retry|queue is full|dropp|refused|permanent error' || true

Ожидаемо: queue_size растёт, Collector пишет о временных retry, а приложения остаются healthy. Немедленно восстановите правило TCP 4317, если:

  • queue_size / queue_capacity >= 0.8;
  • появился queue is full, permanent error или dropped telemetry;
  • любой бизнес-запрос перестал возвращать 2xx;
  • тест длится более 5 минут.

Зафиксируйте максимальный queue_size. Не ждите заполнения очереди специально.

collector_metrics |
  awk '/^otelcol_exporter_queue_(size|capacity)\{/ &&
       /exporter="otlp\/remote"/ {print}'


### 5. Восстановить связь и дождаться drain

Верните исходное правило TCP 4317 в security group и проверьте сеть:

```bash
date -u '+recovery_started=%Y-%m-%dT%H:%M:%SZ'
timeout 5 bash -c 'exec 3<>/dev/tcp/192.168.0.5/4317' \
  && echo 'OK: remote OTLP доступен'

Наблюдайте очередь до нуля:

while true; do
  date -u '+%Y-%m-%dT%H:%M:%SZ'
  output="$(collector_metrics)"
  printf '%s\n' "$output" |
    grep -E 'otelcol_exporter_(queue_size|queue_capacity|send_failed)'
  size="$(printf '%s\n' "$output" |
    awk '/otelcol_exporter_queue_size.*exporter="otlp\/remote"/ {print $NF; exit}')"
  [[ "${size:-unknown}" == "0" ]] && break
  sleep 10
done
date -u '+drain_finished=%Y-%m-%dT%H:%M:%SZ'

Если версия Collector использует другие labels, найдите фактическую строку командой collector_metrics | grep otelcol_exporter_queue и скорректируйте только выражение awk.

После drain:

docker compose logs --since=10m otel-collector |
  grep -Ei 'queue is full|dropp|refused|permanent error' || true

В SigNoz установите период от outage_started до drain_finished и найдите:

service.name = han-chat-outage-canary
service.namespace = han-chat
deployment.environment = production-like

Из-за tail sampling сохраняется около 10% обычных успешных traces, поэтому ожидайте не все 100, а хотя бы один свежий canary trace. Проверьте также свежий trace api-backend с правильными service.version и environment.

6. Записать результат

В runbook/протоколе теста зафиксируйте:

  • UTC outage_started, recovery_started, drain_finished;
  • длительность outage и recovery;
  • количество/коды synthetic HTTP responses;
  • baseline, peak и конечный queue size, queue capacity;
  • были ли queue is full, dropped/refused/permanent errors;
  • ссылку на canary trace в SigNoz;
  • итог PASS только при 2xx, bounded queue, успешном drain и наличии trace.

Не удаляйте queue-файлы и не имитируйте outage остановкой локального Collector: это проверяет другой failure mode.

Реализованный MVP и ограничения

После развёртывания актуальной версии backend в SigNoz поступают:

  • traces и OTLP runtime metrics api-backend, sms-service, sms-worker;
  • FastAPI, HTTPX, SQLAlchemy, Redis и botocore dependency spans по месту использования;
  • ручной span sms.process без SMS/user identifiers;
  • Prometheus metrics sms-service, sms-worker, Keycloak и Collector;
  • API-метрики han_http_*, bootstrap и rate-limit decisions;
  • errors и traces медленнее 2 секунд полностью, остальные успешные — 10% через tail sampling.

Structlog получает настоящий trace_id/span_id, но JSON stdout пока остаётся только локальным аварийным журналом Docker и не экспортируется в SigNoz. Audit events продолжают храниться отдельно в PostgreSQL.

Следующая фаза:

  1. инструментировать message-safety, Bitrix-сервисы и остальные workers;
  2. провести redaction-аудит и выбрать OTLP logs либо ограниченный filelog;
  3. добавить Redis/nginx exporters и безопасный nginx route class;
  4. экспортировать проверенный Dashboard V2 JSON;
  5. уточнить sampling, retention и alert thresholds после baseline/load test.

Канонический контракт telemetry, SLO и сквозных dashboards — architectory/arch-07-observability.md. Сервисные метрики, dashboards и alerts — modules/module-09-observability-vm1.md и modules/module-09-observability-vm2.md. Указатель: modules/module-09-observability.md.

До прохождения end-to-end и outage/recovery acceptance не используйте отсутствие ошибок в SigNoz как доказательство здоровья HAN Chat.