Files
IoT/HISTORY/2026-08-16-session-log.md
T

262 lines
20 KiB
Markdown
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.
# Журнал сессии — 2026-08-16 (IoT: интеграция с shared-sqs, выбор пути миграции)
**Рабочая папка:** /home/naeel/nubes/IoT
> Правило этой сессии (задано пользователем): ВСЁ документировать в HISTORY,
> каждый шаг — сразу.
---
## 1. Контекст (состояние на входе)
- Пользователь завершил handoff по shared-sqs:
- `HANDOFF-NEW-CHAT.md` — в SQS-репозитории, закоммичен и запушен (`5fa34c2`).
- `doc/sqs-integration.md` — создан в IoT-репозитории, **не закоммичен** (untracked).
- `2026-08-13-migration-to-managed.md` — также untracked в IoT-репо.
- Git IoT: HEAD `368ff06` (feature/replace-kafka-with-sqs), origin отстаёт на 1 коммит
(HEAD не запушен: «fix(ingress): remove cert-manager + ssl-redirect»).
## 2. `doc/sqs-integration.md` — что важно для IoT (прочитан, суть)
- shared-sqs: прод v0.1.35, все тесты зелёные (api 22/22, sdk 15/15, FIFO/DLQ e2e),
нагрузка 168 591 операция — 0 сервисных ошибок.
- Внешний endpoint: `https://sqs.containerk8s.dev.nubes.ru` (регион us-east-1).
- Внутренний endpoint (поды того же кластера Nubes, realm iot-naeel):
`http://containerk8s.f1ffb134-7d16-45bd-8bef-69f6ec8ab33c.svc.cluster.local:4100`.
- Проблемы ПЛАТФОРМЫ (не сервиса):
1. Таймауты каждые 31–33с на внешнем пути (~5.5% запросов без ответа 30с).
Доказано tcpdump; внутри кластера потерь нет (port-forward 24977 раундов, 0 сбоев).
2. MSS=1448 при MTU 1400 → тела >~1400 байт виснут ~51с (PMTUD сломан).
Тикет Nubes подготовлен (`SQS-service/doc/thinking/nubes-ticket.md`).
- Рекомендации интеграции: AWS SDK + override endpoint; главная — внутренний адрес
(обходит шлюз полностью); ретраи ≥3, read_timeout ≥30с (для внешнего пути);
сообщения до ~1300 байт; VisibilityTimeout 30с+; FIFO требует MessageGroupId
(dedup 5 мин); DLQ через RedrivePolicy; лимит 50 очередей на тенанта.
- Креды детерминированы из email: tenantID `t-`+sha256(email)[:8],
AccessKey `SSAK-`+sha256(email)[:12], SecretKey sha256(email+":shared-sqs:secret-key:v1").
- Блокер прода — только тикет Nubes (для интернет-клиентов); для внутренней
интеграции блокеров нет.
## 3. Как сейчас развёртывается IoT (факты из репозитория)
- Код полностью на Go 1.25, модуль `gitea.services.ngcloud.ru/Nail/IoT`.
- Один образ Docker Hub `naeel/iot-operator:v0.2.6` (distroless), три бинарника:
`/iot-operator`, `/mqtt-bridge`, `/sqs-consumer`.
- Собственный k8s кластер `iot-naeel`, namespace `sless`, API `185.247.187.149:6443`.
- Деплой с ВМ: `ssh naeel@5.172.178.213`, каталог `/home/naeel/terra/IoT`,
`kubectl apply -f deployments/k8s/` (kubeconfig на ВМ, токен ~24ч).
- Компоненты:
- `iot-operator` — controller-manager (CRD IoTDevice) + REST API :9090;
- `iot-mqtt-bridge` — EMQX → SQS SendMessage;
- `iot-sqs-consumer` — SQS → Postgres (long poll 20с, delete после записи);
- EMQX 5.5.1 — MQTT-брокер (HTTP auth → operator).
- Секреты (ns sless): `iot-bridge-credentials`, `iot-sqs-credentials`
(`SQS_ENDPOINT=https://qu.kube5s.ru` — старый внешний адрес, устарел),
`iot-postgres-secret` (managed PG, `...svc.cluster.local:5432/sqsdb`).
- Ingress: устройства по `wss://iot.kube5s.ru/mqtt`.
- Миграция на managed Node.js (план от 13.08) — НЕ выполнена; Node.js-кода в репо нет.
## 4. Обсуждение: HTTP-контейнер vs Managed Node.js (вывод этой сессии)
- Вывод AI: HTTP-контейнер (как у shared-sqs) лучше: Go сохраняется целиком,
Dockerfile готов, схема доказана shared-sqs, внутренняя сеть без таймаутов.
Managed Node.js = полный rewrite (aedes, Express, @aws-sdk/client-sqs, pg) — запасной.
- Открытый риск контейнера: поддержка WebSocket upgrade на платформенном ingress
не проверена (устройства ходят по wss).
- Устройства в любом варианте ходят через внешний шлюз Nubes — проблемы шлюза
(5.5% таймаутов, MSS) остаются риском для wss в обоих вариантах.
## 5. Чтение SQS-репозитория (`/home/naeel/nubes/SQS-service`) — выяснено
- **План Node.js фактически отменён.** `doc/2026-08-13-migration-to-nubes.md`:
«Код не меняется вообще. Только инфраструктура».
- shared-sqs мигрировал в **«Простой HTTP контейнер» (nubes_http)** через deck-UI,
realm `iot-naeel`. Домен `sqs.containerk8s.dev.nubes.ru`, `/health` OK.
- Технические факты платформы (из HISTORY 13–14.08):
- Контейнер = k8s deployment `containerk8s`, namespace = UUID инстанса
(`f1ffb134-7d16-45bd-8bef-69f6ec8ab33c`), контейнер в поде называется `app`, порт 4100.
- Внутренний DNS: `containerk8s.<uuid-inst>.svc.cluster.local:4100`.
- Внешний: `*.containerk8s.dev.nubes.ru`, ingress/TLS встроены в контейнер.
- Образ — ТОЛЬКО публичный Docker Hub (Gitea/GHCR приватные платформа не тянет).
- Обновление образа — уникальным тегом (`latest` кэшируется зеркалом
`mirror.k8s.ngcloud.ru`); через `kubectl set image deployment/containerk8s app=...`
(правки через kubectl не откатываются — ownerReferences нет).
- Env vars — в deck-UI; Managed Redis — `redisk8s.<uuid>.svc.cluster.local:6379`.
- JWT-валидация: `https://lk-api-gateway.ngcloud.ru/api/v1/svc`
(deck-api*.ngcloud.ru — ЛЕГАСИ, не использовать).
- Long-running работает: SQS long-poll 20с; HTTP-соединение через ingress
держится ~150с (не режется).
- **WebSocket в SQS-истории не упоминается вообще** — поддержка wss на платформе
не проверялась. Единственный реальный риск контейнерного пути для IoT.
## 6. Вывод и следующий шаг
- Путь миграции IoT (кандидат): «Простой HTTP контейнер» в realm `iot-naeel`,
Go без переписывания; убрать operator/CRD → таблица `iot_devices` в Postgres;
bridge и consumer остаются Go.
- Первая проверка перед стартом: WebSocket upgrade (wss) через платформенный
ingress — тестовый контейнер с WS-эндпоинтом + `wscat`.
- ЖДЁМ решения пользователя по пути миграции.
---
## 7. Создан ws-probe — тестовый контейнер для проверки wss
**Команда пользователя:** «делай» (создать пробник + залить образ на Docker Hub;
для запуска в deck-UI нужен путь до образа).
**Что сделано** (все шаги):
1. Создан каталог `ws-probe/` в IoT-репозитории:
- `main.go` — Go echo-сервер на `gorilla/websocket`: `GET /health` (JSON
status+version), `GET /ws` (echo + ping каждые 20с, read-deadline 60с,
сбрасывается pong'ами — мёртвые соединения закрываются), `GET /` (HTML).
Без кредов, без внешних зависимостей.
- `Dockerfile` — multi-stage golang:1.25 → distroless/static:nonroot,
порт 8080 (меняется env `PORT`), версия через ldflags.
- `Makefile` — `docker-build`/`docker-push`, `VERSION=v0.1.0`, `IMAGE=naeel/iot-ws-probe`.
- `go.mod`/`go.sum` — отдельный модуль (gorilla/websocket v1.5.3), чтобы
не тащить зависимость в основной go.mod IoT.
2. `.gitignore` — добавлен `ws-probe/ws-probe` (сборочный бинарник).
3. Локальные проверки: `go vet` OK; smoke: `/health` → `{"status":"ok","version":"v0.1.0"}`;
WS-апгрейд → `HTTP/1.1 101 Switching Protocols` (Sec-WebSocket-Accept корректный).
4. Docker: логин в Docker Hub под `naeel` активен (проверен `docker info`).
5. Образ собран и запушен (публичный):
- **`naeel/iot-ws-probe:v0.1.0`** (и `:latest`),
- digest `sha256:dc029be3e79fb37f15ce23477096e340ab181b96c0132dc2e443f341e36f0688`,
- размер 10.9MB.
6. Коммиты: `27cb40e` (ws-probe), `2a7ecd2` (HISTORY). Пуш НЕ делался (не просили).
**Инструкция для deck-UI («Простой HTTP контейнер»):**
- Образ: **`naeel/iot-ws-probe:latest`** — стабильный путь БЕЗ уникального тега.
⛔ ПРАВИЛО (от пользователя): в deck-UI путь к образу фиксируется при создании
и НЕ редактируется — с тегом `:v0.1.0` мы не смогли бы поменять версию.
Обновление версии — новым пушем в `latest` + redeploy.
⚠️ Оговорка: зеркало платформы кэширует `latest` (случай shared-sqs) — если
redeploy подтянет старый digest, запасной путь: `kubectl set image
deployment/containerk8s app=naeel/iot-ws-probe:<новый-уникальный-тег>`.
- Env vars: не нужны (порт 8080 — дефолт; при необходимости `PORT`).
- Никаких кредов не требуется.
**Критерии проверки wss после деплоя** (будут применены):
- `curl -i -H "Connection: Upgrade" -H "Upgrade: websocket" ...` → 101;
- `wscat -c wss://<домен>/ws` → подключение, echo, соединение живёт 5–10 мин;
- переподключения стабильны; ping/pong проходят.
**Следующий шаг:** пользователь создаёт контейнер в deck-UI с этим образом —
далее тест wss по критериям выше.
---
## 8. Контейнер задеплоен в deck-UI — тест wss (16.08, ~07:26 GMT+03)
**Контейнер:** `https://testiot.containerk8s.dev.nubes.ru/` (создан пользователем
в deck-UI, образ `naeel/iot-ws-probe:latest`).
**Результаты проверок (первые):**
1. `GET /health` → `{"status":"ok","version":"v0.1.0"}` — под стянул наш образ.
2. `GET /` → HTML-страница ws-probe (200).
3. **WebSocket upgrade через платформенный ingress → `HTTP/1.1 101 Switching Protocols`**,
`Sec-WebSocket-Accept` корректный, HSTS на месте. (В выводе curl первая строка
`HTTP/1.1 200 Connection established` — локальный CONNECT-прокси машины,
ответ 101 пришёл от платформы.)
4. **`wscat -c wss://.../ws -x "probe-echo-1"` → echo вернулось, exit 0.**
Двусторонний обмен работает.
**КЛЮЧЕВОЙ ВЫВОД:** платформа Nubes («Простой HTTP контейнер») пропускает
WebSocket upgrade — wss для устройств возможен, путь контейнера подтверждён.
**Длительный тест стабильности** (запущен 08:26:10 по `date` = 07:26 GMT+03):
- скрипт `/tmp/ws-stability-test.py`: соединение 10 мин, echo каждые 10с,
ping/pong обе стороны (клиент ping_interval=25, сервер ping 20с),
лог с таймстемпами. Результат будет дописан ниже.
### РЕЗУЛЬТАТ длительного теста (16.08, обрыв на 173.1с) — ПРОБЛЕМА
Соединение оборвалось на **173.1с** (08:29:03 по `date` = 07:29 GMT+03).
Хронология (полный лог выше в терминале, 08:26:10–08:29:03):
| t (с) | Событие |
|---|---|
| 0.4 | CONNECTED |
| 10–120 | echo 1..12 — все возвращаются ✓ |
| 20.4–140.4 | PING сервера каждые 20с — доходят ✓ |
| 50.5–125.5 | PONG сервера на ping клиента каждые 25с — доходят ✓ |
| ~150 | ping клиента (25с-каденс) — БЕЗ pong; echo 16–17 пропали; PING сервера 160.4 не пришёл |
| 170.1 | клиент: ERROR ping/pong timed out |
| 173.1 | CLOSED; итог sent=17, echoed=15 |
**Анализ:** первые 140с — идеально (двусторонний обмен, ping/pong обе стороны).
Деградация началась ~145–155с, обрыв ~150–173с. Похоже на платформенный
**лимит жизни соединения на внешнем пути ~150с** (перекликается с квирком
shared-sqs: «GET /ui через ingress не закрывает соединение ~150с»).
**Что это значит:** если лимит подтвердится — wss-устройства будут отваливаться
каждые ~2.5–3 мин независимо от MQTT keepalive (обрыв при активном трафике,
не по idle). Миграция на контейнер НЕ отменяется, но перед стартом нужно
выяснить и снять лимит (тикет Nubes).
**Следующие диагностические шаги (предложены, ждут «делай»):**
1. Повторить тест 3 раза — воспроизводимость и точное время обрыва.
2. Логи пода пробника на платформе (`kubectl logs` на ВМ) — что видит сервер
в момент обрыва (read error / ping error).
3. Тест WS изнутри кластера на внутренний `containerk8s.<uuid>.svc.cluster.local:8080`
— если держится >10 мин: обрыв на внешнем шлюзе/ingress; если тоже рвётся:
проблема ближе к поду.
4. Держать обычное HTTP-соединение через ingress >150с — сравнение с WS.
5. По результатам — тикет в Nubes (лимит соединений на ingress/шлюзе).
---
## 9. Диагностика обрыва (16.08, команда «делай тесты»)
### 9.1 Инфраструктура пробника на платформе (через ВМ, kubeconfig живой)
- Namespace пробника: `2d9b96ff-0a50-41e5-a5d5-1cd9e5621555`, deployment
`containerk8s`, под `containerk8s-5dc7dcbfcf-d4ch6` (IP 172.16.2.227),
service `containerk8s` ClusterIP 10.106.45.135:8080.
- Рядом стандартные: pod vmagent (метрики), ns shared-sqs `f1ffb134-...` (2d9h),
старый чужой ns `df36c8af-...` (30d) — не трогали.
- Ingress платформы: ns `ingress`, `shturval-ingress-controller-controller`
(2 пода: 172.16.0.73 / 172.16.3.149), LB 185.247.187.151 (kube-vip).
DNS `testiot.containerk8s.dev.nubes.ru` → 185.247.187.151.
- В логах пробника источник WS-подключений = IP ingress-подов (172.16.0.73 /
172.16.3.149) — соединения терминируются на nginx.
### 9.2 Серверная сторона обрыва (логи пода)
- Тест 1: сервер получал echo каждые 10с до 04:28:40 UTC, затем ПАУЗА ~24с,
в 04:29:04 пришли СРАЗУ ДВА сообщения (echo-15 задержан на ~14с, echo-16 на
~4с) и сразу `ws read error: close 1000 (normal)` — закрыл КЛИЕНТ после
своего ping-timeout. Серверных ошибок нет.
- Вывод: сервер и под ни при чём — соединение застряло между клиентом и nginx.
### 9.3 Nginx (ingress) — чисто
- Access-логи обоих подов: `/ws` → 101, `request_time` = 173.851с / 173.850с
(полная жизнь соединения), статус 101, ошибок нет. Nginx НЕ закрывает
соединение и не видит аномалий.
- X-Forwarded-For показывает клиентов как 192.168.255.10 / 192.168.255.71 —
перед LB есть краевой шлюз/NAT (внешний путь Nubes).
- В error-логах за окно обрыва — только фоновые SSL-сканеры и посторонние
POST /graphql — к нашему обрыву отношения не имеют.
### 9.4 Воспроизводимость (внешний путь, 3 прогона)
- RUN1: обрыв **ровно на 173.1с** (ERROR ping timeout 170.1с, CLOSED 173.1с,
sent=17, echoed=15) — идентично тесту 1. Детерминированно.
- RUN2: запущен, ожидание (ожидаемый обрыв ~08:38:39 по `date`).
- Прогоны идут на РАЗНЫЕ ingress-поды — обрыв не зависит от пода.
### 9.5 Внутренний путь (port-forward с ВМ) — ИДЁТ
- `kubectl -n 2d9b96ff-... port-forward svc/containerk8s 18080:8080` (ВМ),
клиент node+ws (модуль `ws` установлен на ВМ в /tmp/wstest).
- Тест 600с, старт 07:34:55 MSK. На 155с — ЗДОРОВ: PING сервера каждые 20с,
PONG клиента каждые 25с (с 25-й секунды!), ECHO ok[12] на 120с.
- Различие с внешним путём видно уже по pong-каденсу: внутри pong приходит
с 25с, снаружи первый pong только на 50с — внешний шлюз «съедает» что-то
уже на 25-й секунде.
- Если внутренний тест доживёт 600с — обрыв окончательно локализуется на
внешнем краевом шлюзе (вне кластера), как и таймауты 31–33с shared-sqs.
### 9.6 Предварительный вывод
- Контейнер, под, сервис, nginx — здоровы. Детерминированный обрыв внешних
WS-соединений на ~150–173с происходит на краевом шлюзе Nubes.
- Для IoT это значит: wss-устройства будут переподключаться каждые ~2.5–3 мин,
пока шлюз не починят (тикет Nubes, объединить с таймаутами 31–33с и MSS).