368 lines
15 KiB
Markdown
368 lines
15 KiB
Markdown
# Подключение HAN Chat к SigNoz
|
||
|
||
## Схема
|
||
|
||
Приложения на ВМ1 и ВМ2 отправляют OTLP только своему локальному `otel-collector`:
|
||
|
||
```text
|
||
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
|
||
|
||
```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://<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:
|
||
|
||
```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.
|