# Подключение HAN Chat к SigNoz ## Схема Приложения в Docker-сети отправляют OTLP локальному `otel-collector`: ```text HAN containers -> otel-collector:4317 -> 192.168.0.5:4317 -> SigNoz ``` Локальный Collector выполняет редактирование чувствительных атрибутов, добавляет `service.namespace=han-chat`, окружение и версию, сохраняет очередь на диск и пересылает данные в SigNoz. ## Настройка backend В `codebase/backend/.env`: cd /opt/han-chat/backend ```dotenv 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. Не подставляйте фиктивный токен. Применение: ```bash 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-хоста: ```bash 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-сети: ```bash cd /opt/han-chat/backend chmod +x deployment/scripts/verify-observability.sh ./deployment/scripts/verify-observability.sh ``` Скрипт проверяет внутренние SMS metrics endpoints, отправляет canary traces и проверяет свежие exporter/queue ошибки. Эквивалентная ручная отправка: ```bash 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** и отфильтруйте: ```text service.name = han-chat-otlp-smoke service.namespace = han-chat ``` Проверьте, что timestamps свежие. Затем изучите логи Collector: ```bash 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: ```bash 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`: ```bash 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: ```bash 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 временно отключите входящее правило: ```text source: приватный IP backend destination: 192.168.0.5 protocol/port: TCP 4317 ``` Не меняйте TCP 22, маршруты приватной сети и правила самого backend. Запишите UTC-время изменения. На backend проверьте, что блокировка действительно применилась: ```bash 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, чтобы не создавать бизнес-данные: ```bash export HAN_BASE_URL='https://' 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: ```bash 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 доступен' ``` Наблюдайте очередь до нуля: ```bash 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: ```bash docker compose logs --since=10m otel-collector | grep -Ei 'queue is full|dropp|refused|permanent error' || true ``` В SigNoz установите период от `outage_started` до `drain_finished` и найдите: ```text 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. Канонический перечень метрик, SLO, dashboards и alert policy находится в `modules/module-09-observability.md`. До прохождения end-to-end и outage/recovery acceptance не используйте отсутствие ошибок в SigNoz как доказательство здоровья HAN Chat.