Проект разделен на два репозитория
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# SigNoz для HAN Chat
|
||||
|
||||
Одноузловой self-hosted SigNoz на Ubuntu 24.04 через официальный
|
||||
`foundryctl` и Docker Compose.
|
||||
|
||||
Сетевая модель:
|
||||
|
||||
- VM SigNoz: `192.168.0.5`;
|
||||
- OTLP/gRPC: `192.168.0.5:4317`, только приватная сеть;
|
||||
- OTLP/HTTP: `192.168.0.5:4318`, только приватная сеть;
|
||||
- UI/API: `127.0.0.1:8080`, доступ только через SSH-туннель;
|
||||
- ClickHouse, PostgreSQL и ClickHouse Keeper наружу не публикуются.
|
||||
|
||||
## Требования
|
||||
|
||||
- минимум 4 GiB RAM (для эксплуатации лучше 8 GiB);
|
||||
- минимум 30 GiB свободного диска для старта;
|
||||
- временный исходящий доступ в интернет на время установки и обновлений;
|
||||
- рабочий приватный интерфейс с адресом `192.168.0.5`;
|
||||
- доступ к ВМ по SSH через приватную сеть до отключения внешнего IP.
|
||||
|
||||
## Установка
|
||||
|
||||
Скопируйте эту папку на ВМ, например в `/opt/signoz`:
|
||||
|
||||
```bash
|
||||
rsync -rltD --no-perms --no-owner --no-group -ivc --delete \
|
||||
--exclude='.env' \
|
||||
--exclude='dist/' \
|
||||
--exclude='secrets/' \
|
||||
--exclude='pours/' \
|
||||
--exclude='casting.yaml.lock' \
|
||||
-e "ssh -i ~/.ssh/hansel-private" \
|
||||
/mnt/c/Users/MI/Documents/Assistent/HAN_chat_specification/codebase/Signoz/ \
|
||||
root@135.106.166.7:/opt/signoz/
|
||||
```
|
||||
|
||||
Исключения `pours/` и `casting.yaml.lock` обязательны при повторной
|
||||
синхронизации: Foundry создаёт их непосредственно на VM. Без исключений
|
||||
`rsync --delete` удалит runtime-конфигурацию, которой нет локально.
|
||||
|
||||
На ВМ:
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sed -i 's/\r$//' scripts/*.sh
|
||||
chmod +x scripts/*.sh
|
||||
|
||||
sudo ./scripts/05-configure-private-network.sh
|
||||
sudo PRIVATE_IP=192.168.0.5 ./scripts/00-check-vm.sh
|
||||
sudo ./scripts/10-install-docker.sh
|
||||
sudo PRIVATE_IP=192.168.0.5 ./scripts/20-deploy-signoz.sh
|
||||
```
|
||||
|
||||
После запуска создайте первого администратора и организацию SigNoz. До этого
|
||||
OpAMP не выдаст ingester рабочую OTLP-конфигурацию, хотя контейнер будет
|
||||
выглядеть запущенным.
|
||||
|
||||
Адреса `127.0.0.1` всегда означают loopback той машины, на которой
|
||||
интерпретируются. HAN_CHAT имеет приватный адрес `192.168.0.1`, а SigNoz —
|
||||
`192.168.0.5`. UI слушает `127.0.0.1:8080` именно на VM SigNoz.
|
||||
|
||||
Для постоянного доступа после удаления публичного IP SigNoz добавьте в
|
||||
`C:\Users\MI\.ssh\config`:
|
||||
|
||||
```sshconfig
|
||||
Host han-jump
|
||||
HostName 135.106.164.58
|
||||
User root
|
||||
IdentityFile C:\Users\MI\.ssh\hansel
|
||||
|
||||
Host signoz-private
|
||||
HostName 192.168.0.5
|
||||
User root
|
||||
IdentityFile C:\Users\MI\.ssh\hansel-private
|
||||
ProxyJump han-jump
|
||||
```
|
||||
Затем ssh signoz-ui -N
|
||||
|
||||
(устаревшее:
|
||||
На рабочем компьютере откройте туннель до SigNoz через HAN_CHAT:
|
||||
|
||||
```powershell
|
||||
ssh -L 8080:127.0.0.1:8080 signoz-private -N
|
||||
```
|
||||
)
|
||||
Маршрут SSH: Windows → публичный адрес HAN_CHAT → `192.168.0.5:22`.
|
||||
Назначение `127.0.0.1:8080` в `-L` открывает конечная VM SigNoz, а не
|
||||
HAN_CHAT.
|
||||
|
||||
Откройте `http://127.0.0.1:8080`, создайте администратора/организацию, затем
|
||||
на VM выполните:
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sudo docker compose -f pours/deployment/compose.yaml restart ingester
|
||||
sudo ./scripts/30-verify-signoz.sh
|
||||
```
|
||||
|
||||
После успешной проверки настройте host firewall. Подставьте реальный приватный
|
||||
IP backend:
|
||||
|
||||
```bash
|
||||
sudo OTLP_SOURCE=192.168.0.1 \
|
||||
ADMIN_CIDR=192.168.0.0/24 \
|
||||
PRIVATE_IP=192.168.0.5 \
|
||||
./scripts/40-configure-firewall.sh
|
||||
```
|
||||
|
||||
Затем выполните чек-лист из [docs/NETWORK.md](docs/NETWORK.md), подключите
|
||||
backend по [docs/BACKEND_OTLP.md](docs/BACKEND_OTLP.md) и только после
|
||||
успешной end-to-end проверки удалите внешний IP.
|
||||
|
||||
## Проверка и управление
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sudo ./scripts/30-verify-signoz.sh
|
||||
sudo docker compose -f pours/deployment/compose.yaml ps
|
||||
sudo docker compose -f pours/deployment/compose.yaml logs --tail=200
|
||||
sudo docker compose -f pours/deployment/compose.yaml restart
|
||||
```
|
||||
|
||||
Не редактируйте `pours/` вручную: Foundry перегенерирует эту папку.
|
||||
Постоянные изменения вносятся в `casting.yaml`, после чего снова запускается
|
||||
`20-deploy-signoz.sh`.
|
||||
|
||||
Обновление требует временного доступа к Docker Hub, GitHub и SigNoz:
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sudo ./scripts/20-deploy-signoz.sh
|
||||
```
|
||||
|
||||
Перед обновлением сделайте snapshot диска ВМ. Данные находятся в Docker
|
||||
volumes ClickHouse и PostgreSQL; `docker compose down -v` удалит их и поэтому
|
||||
для штатного обслуживания запрещён.
|
||||
|
||||
## Документация
|
||||
|
||||
- [NETWORK.md](docs/NETWORK.md) — сеть, группа безопасности, SSH-туннель;
|
||||
- [BACKEND_OTLP.md](docs/BACKEND_OTLP.md) — передача телеметрии HAN Chat;
|
||||
- [SIGNOZ_RUNBOOK.md](docs/SIGNOZ_RUNBOOK.md) — что смотреть в SigNoz;
|
||||
- [MVP_DASHBOARDS_ALERTS.md](docs/MVP_DASHBOARDS_ALERTS.md) — versioned
|
||||
спецификация первых dashboards и alerts.
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1alpha1
|
||||
kind: Installation
|
||||
metadata:
|
||||
name: signoz
|
||||
spec:
|
||||
deployment:
|
||||
flavor: compose
|
||||
mode: docker
|
||||
patches:
|
||||
- target: deployment/compose.yaml
|
||||
operations:
|
||||
- op: replace
|
||||
path: /services/ingester/ports
|
||||
value:
|
||||
- "192.168.0.5:4317:4317"
|
||||
- "192.168.0.5:4318:4318"
|
||||
- op: replace
|
||||
path: /services/signoz-signoz-0/ports
|
||||
value:
|
||||
- "127.0.0.1:8080:8080"
|
||||
@@ -0,0 +1,368 @@
|
||||
# Подключение 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.
|
||||
|
||||
Канонический контракт 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.
|
||||
@@ -0,0 +1,89 @@
|
||||
# SigNoz MVP: dashboards и alerts
|
||||
|
||||
Документ фиксирует versioned-спецификацию первых панелей. Создавайте их через
|
||||
SigNoz Query Builder и экспортируйте полученный Dashboard V2 JSON обратно в
|
||||
репозиторий после проверки реальных имён attributes на production-like данных.
|
||||
|
||||
Обязательные фильтры каждой панели:
|
||||
|
||||
```text
|
||||
service.namespace = han-chat
|
||||
deployment.environment = production-like
|
||||
```
|
||||
|
||||
Добавьте переменные `deployment.environment`, `service.name` и
|
||||
`service.version`. Идентификаторы пользователей, запросов, SMS и сессий
|
||||
переменными dashboard не являются.
|
||||
|
||||
Для Prometheus-scraped SMS/Keycloak metrics имя источника находится в bounded
|
||||
datapoint label `service_name`; для OTLP signals используется resource
|
||||
attribute `service.name`.
|
||||
|
||||
## Dashboard: HAN API RED
|
||||
|
||||
1. **Request rate** — rate/sum `han_http_requests_total`, group by
|
||||
`http.route`, `http.request.method`.
|
||||
2. **Error rate** — доля `han_http_requests_total` с
|
||||
`http.response.status_class=5xx`.
|
||||
3. **Latency p50/p95/p99** — `han_http_request_duration_seconds`, group by
|
||||
route template.
|
||||
4. **Rate-limit decisions** — rate `han_rate_limit_decisions_total`, group by
|
||||
`scope`, `outcome`.
|
||||
5. **Bootstrap outcomes** — rate `han_auth_bootstrap_total`, group by
|
||||
`outcome`.
|
||||
6. **Slow/error traces** — link в Traces с `service.name=api-backend`.
|
||||
|
||||
## Dashboard: HAN SMS
|
||||
|
||||
1. **Send outcomes** — rate `sms_send_total`, group by `provider`,
|
||||
`send_status`.
|
||||
2. **Provider p95 latency** — `sms_provider_request_duration_seconds`.
|
||||
3. **Uncertain outcomes** — rate `sms_uncertain_total`.
|
||||
4. **Callback result/lag** — `sms_callback_total`,
|
||||
`sms_callback_lag_seconds`.
|
||||
5. **Oldest pending** — `sms_pending_oldest_age_seconds`.
|
||||
6. **Journal rows/settings** — `sms_journal_rows`, `sms_settings_valid`.
|
||||
7. **Worker traces** — `service.name=sms-worker`, span `sms.process`.
|
||||
|
||||
## Dashboard: Collector health
|
||||
|
||||
Используйте autocomplete Metrics Explorer для фактических `otelcol_*` имён
|
||||
версии Collector `0.117.0`:
|
||||
|
||||
1. accepted и refused spans/metric points/log records;
|
||||
2. exporter sent/failed;
|
||||
3. sending queue capacity/size;
|
||||
4. tail-sampling sampled/dropped/late spans;
|
||||
5. process RSS/CPU;
|
||||
6. Prometheus scrape failures для `sms-service`, `sms-worker`, `keycloak`;
|
||||
7. отсутствие данных по каждому обязательному `service.name`.
|
||||
|
||||
## Первые alerts
|
||||
|
||||
Alerts создаются после 24 часов baseline. Все правила получают owner, severity
|
||||
и ссылку на `SIGNOZ_RUNBOOK.md`.
|
||||
|
||||
- **API 5xx:** доля 5xx > 5% в течение 5 минут.
|
||||
- **API latency:** p95 выше 750 ms 10 минут; до разделения route-классов это
|
||||
warning, не page.
|
||||
- **SMS uncertain:** рост `sms_uncertain_total` дольше 5 минут.
|
||||
- **SMS pending stale:** `sms_pending_oldest_age_seconds > 300` 5 минут.
|
||||
- **SMS settings invalid:** `sms_settings_valid < 1` 5 минут.
|
||||
- **Collector refused/dropped:** значение > 0 дольше 5 минут.
|
||||
- **Collector queue:** заполнение > 80% 10 минут.
|
||||
- **No telemetry:** обязательный production-like сервис отсутствует 10 минут
|
||||
при ожидаемом трафике.
|
||||
|
||||
Не создавайте SLO по sampled traces. Availability/error budget рассчитываются
|
||||
по metrics. Multi-window burn alerts добавляются после двух недель baseline.
|
||||
|
||||
## Acceptance
|
||||
|
||||
Dashboard считается введённым в эксплуатацию, когда:
|
||||
|
||||
1. панели показывают свежие данные после synthetic request;
|
||||
2. фильтр release отделяет текущий rollout от предыдущего;
|
||||
3. route содержит template, а не UUID/raw URI;
|
||||
4. ни одна metric series не содержит user/session/request/trace identifiers;
|
||||
5. alert проверен контролируемым synthetic failure;
|
||||
6. экспортированный Dashboard V2 JSON сохранён рядом с этим документом.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Сеть и доступ
|
||||
|
||||
## Сбор диагностики
|
||||
|
||||
До изменения Netplan выполните и сохраните вывод:
|
||||
|
||||
```bash
|
||||
ip -br link
|
||||
ip -br -4 address
|
||||
ip -4 route
|
||||
ip -4 rule
|
||||
sudo netplan get
|
||||
sudo netplan status --all 2>&1 || true
|
||||
sudo ls -la /etc/netplan
|
||||
sudo sh -c 'for f in /etc/netplan/*.yaml; do echo "--- $f"; cat "$f"; done'
|
||||
systemctl is-active systemd-networkd NetworkManager
|
||||
networkctl list 2>&1 || true
|
||||
networkctl status 2>&1 || true
|
||||
grep -RniE 'network:|network-config|disable_network_config' \
|
||||
/etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.d 2>/dev/null || true
|
||||
```
|
||||
|
||||
Диагностика этой VM показала:
|
||||
|
||||
- публичный интерфейс `eth0`, `135.106.166.7/24`, default gateway
|
||||
`135.106.166.1`;
|
||||
- приватный интерфейс `eth1`, MAC `fa:16:3e:b6:c6:70`, изначально
|
||||
`DOWN/unmanaged`;
|
||||
- приватный адрес VM — `192.168.0.5/24`.
|
||||
|
||||
Для `eth1` не нужен gateway: узлы `192.168.0.0/24` доступны connected route.
|
||||
Default route должен остаться только на `eth0`.
|
||||
|
||||
## Настройка приватного интерфейса
|
||||
|
||||
Используйте подготовленный скрипт:
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sudo ./scripts/05-configure-private-network.sh
|
||||
```
|
||||
|
||||
Он создаёт отдельный `/etc/netplan/60-signoz-private.yaml`, проверяет MAC,
|
||||
выполняет `netplan generate` и запускает `netplan try --timeout 120`. Файл
|
||||
`50-cloud-init.yaml` не изменяется: cloud-init продолжает управлять публичным
|
||||
`eth0`, а отдельный файл сохраняет конфигурацию `eth1`.
|
||||
|
||||
Пока `netplan try` ожидает подтверждения:
|
||||
|
||||
```bash
|
||||
ip -br -4 address show eth1
|
||||
ip route get 192.168.0.1
|
||||
ping -c 3 192.168.0.1
|
||||
```
|
||||
|
||||
С другой VM приватной сети проверьте:
|
||||
|
||||
```bash
|
||||
ping -c 3 192.168.0.5
|
||||
ssh -i ~/.ssh/hansel-private root@192.168.0.5
|
||||
```
|
||||
|
||||
Если новая SSH-сессия работает, вернитесь в первую и подтвердите Netplan
|
||||
клавишей Enter. Если нет — не подтверждайте: через 120 секунд произойдёт откат.
|
||||
Отсутствие ответа на ping само по себе может означать запрет ICMP; SSH является
|
||||
основной проверкой.
|
||||
|
||||
## Группа безопасности
|
||||
|
||||
Минимальные входящие правила для VM SigNoz:
|
||||
|
||||
- TCP 22 от административного узла/подсети приватной сети;
|
||||
- TCP 4317 от security groups/private IP ВМ1 и ВМ2;
|
||||
- TCP 4318 от ВМ1/ВМ2 только если планируется OTLP/HTTP;
|
||||
- никаких входящих правил для 8080, 5432, 8123, 9000, 9181.
|
||||
|
||||
Для текущего backend используется OTLP/gRPC, поэтому после проверки 4318 можно
|
||||
закрыть. Исходящий доступ к приватной сети оставьте. Для обновления временно
|
||||
разрешайте HTTPS/DNS наружу или используйте внутренний registry/proxy.
|
||||
|
||||
## Доступ к UI
|
||||
|
||||
UI слушает только `127.0.0.1:8080` на VM SigNoz.
|
||||
|
||||
Через доступный jump host:
|
||||
|
||||
```powershell
|
||||
ssh -i C:\Users\MI\.ssh\hansel `
|
||||
-J root@<JUMP_PUBLIC_IP> `
|
||||
-L 8080:127.0.0.1:8080 `
|
||||
root@192.168.0.5 -N
|
||||
```
|
||||
|
||||
Если ключи jump host и SigNoz различаются, удобнее добавить оба узла в
|
||||
`~/.ssh/config`. После запуска туннеля откройте
|
||||
`http://127.0.0.1:8080`.
|
||||
|
||||
## Проверки перед удалением внешнего IP
|
||||
|
||||
1. Новая SSH-сессия к `192.168.0.5` через jump host открывается.
|
||||
2. Туннель показывает UI SigNoz.
|
||||
3. `scripts/30-verify-signoz.sh` проходит без ошибок.
|
||||
4. С ВМ1 и ВМ2 доступны `192.168.0.5:4317` и при необходимости `:4318`.
|
||||
5. В SigNoz появился свежий trace сервиса HAN Chat.
|
||||
6. Все контейнеры имеют статус `running`, healthcheck — `healthy`.
|
||||
7. Создан snapshot диска ВМ.
|
||||
8. В облачной группе безопасности нет публичного доступа к служебным портам.
|
||||
@@ -0,0 +1,144 @@
|
||||
# Работа с SigNoz для HAN Chat
|
||||
|
||||
## Базовые фильтры
|
||||
|
||||
Во всех разделах начинайте с:
|
||||
|
||||
```text
|
||||
service.namespace = han-chat
|
||||
deployment.environment = production-like
|
||||
```
|
||||
|
||||
Для сравнения релизов используйте `service.version`. Не смешивайте production
|
||||
и smoke/local данные в одном запросе без фильтра окружения.
|
||||
|
||||
MVP service names:
|
||||
|
||||
```text
|
||||
api-backend
|
||||
sms-service
|
||||
sms-worker
|
||||
```
|
||||
|
||||
Для worker ищите ручные spans `sms.claim`, `sms.process`, `sms.provider`,
|
||||
`sms.save_result`. Health routes исключены до tail sampling.
|
||||
|
||||
## Ежедневная проверка
|
||||
|
||||
1. **Services** — появились ли ожидаемые сервисы, нет ли резкого роста error
|
||||
rate или p95 latency.
|
||||
2. **Traces** — последние ошибки и медленные запросы, путь от HTTP endpoint до
|
||||
БД/Redis/внешнего API.
|
||||
3. **Logs** — события рядом с trace по `trace_id`, ошибки и повторы workers.
|
||||
4. **Metrics** — насыщение ресурсов, очереди, SMS и фоновые задачи.
|
||||
5. **Alerts** — firing/acknowledged alerts и причины, а не только факт firing.
|
||||
|
||||
Пока приложения не инструментированы, эти представления будут неполными; см.
|
||||
ограничение в `BACKEND_OTLP.md`.
|
||||
|
||||
## Разбор инцидента
|
||||
|
||||
Начните с узкого временного диапазона вокруг события:
|
||||
|
||||
1. найдите пользовательский `request_id` из ответа API или журнала;
|
||||
2. в Logs ищите точное значение `request_id`;
|
||||
3. откройте связанный trace по `trace_id`;
|
||||
4. найдите первый ошибочный или самый долгий span, а не последний симптом;
|
||||
5. сравните с предыдущей версией по `service.version`;
|
||||
6. проверьте зависимости: PostgreSQL, Redis, Keycloak, S3, SMS provider,
|
||||
Bitrix;
|
||||
7. сохраните ссылку на запрос SigNoz и зафиксируйте UTC-интервал инцидента.
|
||||
|
||||
Не добавляйте в SigNoz телефон, email, токены, cookie, содержимое сообщений,
|
||||
полные SQL statements и query string. Collector удаляет известные
|
||||
чувствительные атрибуты, но новые поля должны проходить отдельную проверку.
|
||||
|
||||
## Рекомендуемые представления после инструментации
|
||||
|
||||
**API**
|
||||
|
||||
- request rate, error rate, p50/p95/p99 duration;
|
||||
- разрез по нормализованному `http.route`, не по сырому URL;
|
||||
- 401/403 отдельно от 5xx;
|
||||
- latency PostgreSQL, Redis, Keycloak и S3;
|
||||
- количество активных WebSocket/SSE соединений и reconnect.
|
||||
|
||||
**SMS**
|
||||
|
||||
- `sms_send_total` по результату/provider;
|
||||
- provider latency;
|
||||
- uncertain deliveries и callback lag;
|
||||
- возраст pending-заявок и размер journal;
|
||||
- валидность runtime settings.
|
||||
|
||||
**Workers**
|
||||
|
||||
- длительность и результат job;
|
||||
- глубина очереди, oldest item age;
|
||||
- retries/dead-letter/recovery;
|
||||
- lag cleanup/delivery/safety/notification jobs.
|
||||
|
||||
**Инфраструктура**
|
||||
|
||||
- CPU, RAM, filesystem usage VM SigNoz;
|
||||
- ClickHouse inserts/query latency, parts и disk usage;
|
||||
- dropped/refused spans, exporter failures и queue size Collector;
|
||||
- restarts и health контейнеров.
|
||||
|
||||
## Первые алерты
|
||||
|
||||
Точный состав панелей и acceptance-критерии зафиксированы в
|
||||
`MVP_DASHBOARDS_ALERTS.md`.
|
||||
|
||||
Создавайте алерты только после получения базовой линии, чтобы избежать шума:
|
||||
|
||||
- API 5xx rate выше согласованного порога 5–10 минут;
|
||||
- p95 latency выше SLO 10 минут;
|
||||
- нет телеметрии от production-сервиса 5–10 минут при ожидаемом трафике;
|
||||
- Collector exporter failures/dropped telemetry больше нуля;
|
||||
- очередь workers или oldest item age растёт;
|
||||
- SMS uncertain/callback lag превышает бизнес-порог;
|
||||
- диск VM SigNoz заполнен более чем на 75% и 85%;
|
||||
- любой критичный контейнер перезапускается или unhealthy.
|
||||
|
||||
Для каждого alert укажите severity, owner, runbook URL и минимальное время
|
||||
устойчивого нарушения. Не оповещайте по единичной ошибке.
|
||||
|
||||
## Sampling и интерпретация
|
||||
|
||||
Collector сейчас применяет probabilistic sampling 10% только к traces.
|
||||
Метрики и логи не sampled этим processor. Следствия:
|
||||
|
||||
- единичный запрос может не попасть в Traces;
|
||||
- абсолютное число traces нельзя считать числом запросов;
|
||||
- ошибки тоже могут быть отброшены, потому что tail/error-aware sampling пока
|
||||
не настроен;
|
||||
- SLO и alerting следует строить на метриках, а не на подсчёте sampled traces.
|
||||
|
||||
Для production рекомендуется перейти к tail sampling: сохранять 100% ошибок и
|
||||
медленных traces, а успешные быстрые запросы — выборочно.
|
||||
|
||||
## Обслуживание SigNoz
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
cd /opt/signoz
|
||||
sudo ./scripts/30-verify-signoz.sh
|
||||
sudo docker compose -f pours/deployment/compose.yaml logs --since=30m
|
||||
df -h
|
||||
docker system df
|
||||
```
|
||||
|
||||
Перед обновлением:
|
||||
|
||||
1. временно разрешить исходящий интернет;
|
||||
2. сделать snapshot диска;
|
||||
3. проверить свободное место;
|
||||
4. выполнить `scripts/20-deploy-signoz.sh`;
|
||||
5. повторить smoke-тест и проверить сохранность старых данных;
|
||||
6. отключить внешний доступ.
|
||||
|
||||
Не выполняйте `docker compose down -v`, `docker volume prune` или
|
||||
`docker system prune --volumes`: эти команды могут удалить телеметрию и
|
||||
метаданные SigNoz.
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PRIVATE_IP="${PRIVATE_IP:-192.168.0.5}"
|
||||
MIN_MEMORY_KIB=$((4 * 1024 * 1024))
|
||||
MIN_DISK_KIB=$((30 * 1024 * 1024))
|
||||
errors=0
|
||||
|
||||
ok() { printf 'OK %s\n' "$*"; }
|
||||
warn() { printf 'WARN %s\n' "$*" >&2; }
|
||||
fail() { printf 'FAIL %s\n' "$*" >&2; errors=$((errors + 1)); }
|
||||
|
||||
echo "=== ОС и ресурсы ==="
|
||||
. /etc/os-release
|
||||
[[ "${ID:-}" == ubuntu && "${VERSION_ID:-}" == "24.04" ]] \
|
||||
&& ok "Ubuntu ${VERSION_ID}" || warn "Проверено для Ubuntu 24.04; найдено ${PRETTY_NAME:-unknown}"
|
||||
|
||||
memory_kib="$(awk '/MemTotal/ {print $2}' /proc/meminfo)"
|
||||
((memory_kib >= MIN_MEMORY_KIB)) && ok "RAM не менее 4 GiB" || fail "SigNoz требует не менее 4 GiB RAM"
|
||||
|
||||
disk_kib="$(df -Pk /opt 2>/dev/null | awk 'NR==2 {print $4}')"
|
||||
((disk_kib >= MIN_DISK_KIB)) && ok "/opt: свободно не менее 30 GiB" \
|
||||
|| fail "Для начальной установки рекомендуется не менее 30 GiB свободно в /opt"
|
||||
|
||||
echo
|
||||
echo "=== Сеть ==="
|
||||
ip -br -4 address
|
||||
ip -4 route
|
||||
ip -4 addr show | grep -Fq "inet ${PRIVATE_IP}/" \
|
||||
&& ok "Приватный адрес ${PRIVATE_IP} назначен" \
|
||||
|| fail "Адрес ${PRIVATE_IP} не найден ни на одном интерфейсе"
|
||||
|
||||
if getent hosts archive.ubuntu.com registry-1.docker.io ghcr.io >/dev/null; then
|
||||
ok "DNS работает"
|
||||
else
|
||||
fail "DNS не разрешает адреса репозиториев"
|
||||
fi
|
||||
|
||||
for url in \
|
||||
https://archive.ubuntu.com \
|
||||
https://download.docker.com \
|
||||
https://registry-1.docker.io \
|
||||
https://github.com \
|
||||
https://signoz.io; do
|
||||
if curl -4fsSI --connect-timeout 8 --max-time 15 "$url" >/dev/null; then
|
||||
ok "доступен $url"
|
||||
else
|
||||
fail "нет доступа к $url"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=== Занятость портов ==="
|
||||
for port in 8080 4317 4318; do
|
||||
if ss -H -lnt "( sport = :$port )" | grep -q .; then
|
||||
fail "TCP-порт $port уже занят: $(ss -H -lntp "( sport = :$port )")"
|
||||
else
|
||||
ok "TCP-порт $port свободен"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
timedatectl show --property=NTPSynchronized --property=Timezone --no-pager
|
||||
if ((errors)); then
|
||||
printf '\nОбнаружено ошибок: %d\n' "$errors" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Проверка завершена успешно."
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PRIVATE_INTERFACE="${PRIVATE_INTERFACE:-eth1}"
|
||||
PRIVATE_MAC="${PRIVATE_MAC:-fa:16:3e:b6:c6:70}"
|
||||
PRIVATE_ADDRESS="${PRIVATE_ADDRESS:-192.168.0.5/24}"
|
||||
NETPLAN_FILE="/etc/netplan/60-signoz-private.yaml"
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Запустите через sudo: sudo $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
actual_mac="$(cat "/sys/class/net/${PRIVATE_INTERFACE}/address" 2>/dev/null || true)"
|
||||
if [[ "$actual_mac" != "$PRIVATE_MAC" ]]; then
|
||||
echo "Остановка: MAC ${PRIVATE_INTERFACE} равен '${actual_mac:-не найден}', ожидался ${PRIVATE_MAC}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -d -m 0700 /root/netplan-backup
|
||||
if [[ -f "$NETPLAN_FILE" ]]; then
|
||||
cp -a "$NETPLAN_FILE" "/root/netplan-backup/60-signoz-private.yaml.$(date +%Y%m%d-%H%M%S)"
|
||||
fi
|
||||
|
||||
cat >"$NETPLAN_FILE" <<EOF
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
${PRIVATE_INTERFACE}:
|
||||
match:
|
||||
macaddress: "${PRIVATE_MAC}"
|
||||
set-name: "${PRIVATE_INTERFACE}"
|
||||
addresses:
|
||||
- "${PRIVATE_ADDRESS}"
|
||||
mtu: 1500
|
||||
optional: true
|
||||
EOF
|
||||
chmod 0600 "$NETPLAN_FILE"
|
||||
|
||||
netplan generate
|
||||
echo "Создан ${NETPLAN_FILE}:"
|
||||
cat "$NETPLAN_FILE"
|
||||
echo
|
||||
echo "Netplan временно применит конфигурацию на 120 секунд."
|
||||
echo "Подтвердите её в приглашении только после проверки второй SSH-сессии."
|
||||
netplan try --timeout 120
|
||||
|
||||
echo
|
||||
ip -br -4 address show "$PRIVATE_INTERFACE"
|
||||
ip -4 route show dev "$PRIVATE_INTERFACE"
|
||||
ip route get 192.168.0.1
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Запустите через sudo: sudo $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl gnupg jq
|
||||
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
||||
-o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
. /etc/os-release
|
||||
cat >/etc/apt/sources.list.d/docker.sources <<EOF
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/ubuntu
|
||||
Suites: ${VERSION_CODENAME}
|
||||
Components: stable
|
||||
Architectures: $(dpkg --print-architecture)
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOF
|
||||
|
||||
apt-get update
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io \
|
||||
docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
install -d -m 0755 /etc/docker
|
||||
cat >/etc/docker/daemon.json <<'EOF'
|
||||
{
|
||||
"live-restore": true,
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "50m",
|
||||
"max-file": "5"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
systemctl enable --now docker
|
||||
systemctl restart docker
|
||||
docker version
|
||||
docker compose version
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PRIVATE_IP="${PRIVATE_IP:-192.168.0.5}"
|
||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CASTING="${ROOT_DIR}/casting.yaml"
|
||||
COMPOSE="${ROOT_DIR}/pours/deployment/compose.yaml"
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Запустите через sudo: sudo $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
command -v docker >/dev/null || { echo "Сначала установите Docker." >&2; exit 1; }
|
||||
ip -4 addr show | grep -Fq "inet ${PRIVATE_IP}/" || {
|
||||
echo "На ВМ отсутствует приватный адрес ${PRIVATE_IP}." >&2
|
||||
exit 1
|
||||
}
|
||||
grep -Fq "${PRIVATE_IP}:4317:4317" "$CASTING" || {
|
||||
echo "PRIVATE_IP не совпадает с адресом в casting.yaml." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ! command -v foundryctl >/dev/null; then
|
||||
installer="$(mktemp)"
|
||||
trap 'rm -f "$installer"' EXIT
|
||||
curl -fsSL https://signoz.io/foundry.sh -o "$installer"
|
||||
bash "$installer"
|
||||
if [[ -x /root/.local/bin/foundryctl ]]; then
|
||||
install -m 0755 /root/.local/bin/foundryctl /usr/local/bin/foundryctl
|
||||
elif [[ ! -x /usr/local/bin/foundryctl ]]; then
|
||||
echo "foundryctl не найден после установки." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
foundryctl gauge -f "$CASTING"
|
||||
foundryctl forge -f "$CASTING"
|
||||
|
||||
[[ -f "$COMPOSE" ]] || { echo "Foundry не создал $COMPOSE" >&2; exit 1; }
|
||||
docker compose -f "$COMPOSE" config --quiet
|
||||
|
||||
# Защита от случайной публикации UI или OTLP на всех интерфейсах.
|
||||
grep -Fq "${PRIVATE_IP}:4317:4317" "$COMPOSE"
|
||||
grep -Fq "${PRIVATE_IP}:4318:4318" "$COMPOSE"
|
||||
grep -Fq "127.0.0.1:8080:8080" "$COMPOSE"
|
||||
if grep -Eq '(^|[[:space:]"-])0\.0\.0\.0:(8080|4317|4318):' "$COMPOSE"; then
|
||||
echo "Остановка: обнаружена публикация служебного порта на 0.0.0.0." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker compose -f "$COMPOSE" pull
|
||||
docker compose -f "$COMPOSE" up -d --remove-orphans
|
||||
|
||||
echo "Ожидание SigNoz UI..."
|
||||
deadline=$((SECONDS + 300))
|
||||
until curl -fsS --max-time 5 http://127.0.0.1:8080/api/v1/health >/dev/null 2>&1; do
|
||||
if ((SECONDS >= deadline)); then
|
||||
echo "UI не стал готов за 5 минут." >&2
|
||||
docker compose -f "$COMPOSE" ps -a
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
docker compose -f "$COMPOSE" ps -a
|
||||
cat <<'EOF'
|
||||
|
||||
Базовый SigNoz запущен, но первичная настройка ещё не завершена.
|
||||
Откройте UI через SSH-туннель и создайте первого администратора/организацию.
|
||||
После этого перезапустите ingester и выполните scripts/30-verify-signoz.sh.
|
||||
EOF
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PRIVATE_IP="${PRIVATE_IP:-192.168.0.5}"
|
||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
COMPOSE="${ROOT_DIR}/pours/deployment/compose.yaml"
|
||||
errors=0
|
||||
|
||||
wait_for() {
|
||||
local label="$1"
|
||||
local timeout_seconds="$2"
|
||||
shift
|
||||
shift
|
||||
local deadline=$((SECONDS + timeout_seconds))
|
||||
printf '%-34s' "$label"
|
||||
until "$@" >/dev/null 2>&1; do
|
||||
if ((SECONDS >= deadline)); then
|
||||
echo "FAIL"
|
||||
errors=$((errors + 1))
|
||||
return 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
echo "OK"
|
||||
}
|
||||
|
||||
container_healthy() {
|
||||
[[ "$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$1" 2>/dev/null)" == healthy ]]
|
||||
}
|
||||
|
||||
one_shot_ok() {
|
||||
local state
|
||||
state="$(docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' "$1" 2>/dev/null)" || return 1
|
||||
[[ "$state" == "running 0" || "$state" == "exited 0" ]]
|
||||
}
|
||||
|
||||
[[ -f "$COMPOSE" ]] || { echo "Не найден $COMPOSE" >&2; exit 1; }
|
||||
echo "=== SigNoz: состояние ==="
|
||||
docker compose -f "$COMPOSE" ps
|
||||
echo
|
||||
|
||||
wait_for "PostgreSQL healthy" 300 container_healthy \
|
||||
signoz-metastore-postgres-0
|
||||
wait_for "ClickHouse Keeper healthy" 300 container_healthy \
|
||||
signoz-telemetrykeeper-clickhousekeeper-0
|
||||
wait_for "ClickHouse healthy" 300 container_healthy \
|
||||
signoz-telemetrystore-clickhouse-0-0
|
||||
wait_for "SigNoz healthy" 300 container_healthy \
|
||||
signoz-signoz-0
|
||||
wait_for "SigNoz API/UI" 300 curl -fsS --max-time 5 \
|
||||
http://127.0.0.1:8080/api/v1/health
|
||||
wait_for "OTLP gRPC TCP 4317" 300 timeout 3 bash -c \
|
||||
"exec 3<>/dev/tcp/${PRIVATE_IP}/4317"
|
||||
wait_for "OTLP HTTP 4318" 300 curl -fsS --max-time 5 \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"resourceSpans":[]}' \
|
||||
"http://${PRIVATE_IP}:4318/v1/traces"
|
||||
wait_for "User scripts completed" 300 one_shot_ok \
|
||||
signoz-telemetrystore-clickhouse-user-scripts
|
||||
wait_for "Migrations running/completed" 300 one_shot_ok \
|
||||
signoz-telemetrystore-migrator
|
||||
|
||||
echo
|
||||
docker compose -f "$COMPOSE" ps -a
|
||||
echo
|
||||
ss -lntp "( sport = :8080 or sport = :4317 or sport = :4318 )"
|
||||
if ((errors)); then
|
||||
echo "Проверка завершилась с ошибками: $errors" >&2
|
||||
echo "Логи: docker compose -f '$COMPOSE' logs --tail=200" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "SigNoz готов."
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PRIVATE_IP="${PRIVATE_IP:-192.168.0.5}"
|
||||
ADMIN_CIDR="${ADMIN_CIDR:-192.168.0.0/24}"
|
||||
OTLP_SOURCE="${OTLP_SOURCE:-}"
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Запустите через sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$OTLP_SOURCE" ]]; then
|
||||
echo "Укажите приватный IP backend, например:" >&2
|
||||
echo " sudo OTLP_SOURCE=192.168.0.1 $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
ip -4 addr show | grep -Fq "inet ${PRIVATE_IP}/" || {
|
||||
echo "Приватный адрес ${PRIVATE_IP} не найден." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "SSH будет разрешён только из ${ADMIN_CIDR}."
|
||||
echo "OTLP будет разрешён только от ${OTLP_SOURCE}."
|
||||
echo "До продолжения проверьте отдельную SSH-сессию через приватную сеть."
|
||||
read -r -p "Введите APPLY для применения правил: " answer
|
||||
[[ "$answer" == APPLY ]] || { echo "Отменено."; exit 1; }
|
||||
|
||||
apt-get update
|
||||
apt-get install -y ufw
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw logging medium
|
||||
ufw allow from "$ADMIN_CIDR" to "$PRIVATE_IP" port 22 proto tcp comment "SigNoz admin SSH"
|
||||
ufw allow from "$OTLP_SOURCE" to "$PRIVATE_IP" port 4317 proto tcp comment "HAN OTLP gRPC"
|
||||
ufw allow from "$OTLP_SOURCE" to "$PRIVATE_IP" port 4318 proto tcp comment "HAN OTLP HTTP"
|
||||
ufw --force enable
|
||||
ufw status verbose
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Важно: Docker-публикации могут обходить цепочку UFW INPUT. Основная фильтрация
|
||||
источника OTLP должна оставаться в облачной группе безопасности. casting.yaml
|
||||
дополнительно привязывает OTLP только к приватному IP, а UI — к loopback.
|
||||
EOF
|
||||
Reference in New Issue
Block a user