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

484 lines
37 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 1314.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:1008:29:03):
| t (с) | Событие |
|---|---|
| 0.4 | CONNECTED |
| 10120 | echo 1..12 — все возвращаются ✓ |
| 20.4140.4 | PING сервера каждые 20с — доходят ✓ |
| 50.5125.5 | PONG сервера на ping клиента каждые 25с — доходят ✓ |
| ~150 | ping клиента (25с-каденс) — БЕЗ pong; echo 1617 пропали; 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с — обрыв окончательно локализуется на
внешнем краевом шлюзе (вне кластера), как и таймауты 3133с shared-sqs.
### 9.6 Предварительный вывод
- Контейнер, под, сервис, nginx — здоровы. Детерминированный обрыв внешних
WS-соединений на ~150–173с происходит на краевом шлюзе Nubes.
- Для IoT это значит: wss-устройства будут переподключаться каждые ~2.5–3 мин,
пока шлюз не починят (тикет Nubes, объединить с таймаутами 31–33с и MSS).
### 9.7 Финальные результаты всех тестов
| Тест | Путь | Клиент | Результат |
|---|---|---|---|
| Тест 1 | станция → внешний wss | python | обрыв 173.1с |
| RUN1 | станция → внешний wss | python | обрыв **ровно 173.1с** |
| RUN2 | станция → внешний wss | python | обрыв 163.4с (stall на ~150с) |
| RUN3 | станция → внешний wss | python | обрыв 163.7с (stall на ~150с) |
| VM-ext | ВМ → внешний wss | node+ws | **чисто 300.2с, 29/29 echo** |
| VM-int | ВМ → port-forward → под | node+ws | **чисто 600.1с, 59/59 echo** |
| echo.org | станция → публичный WS-echo | python | **чисто 200с+ (тест 300с)** |
| proxy | станция → прокси 172.17.192.1:10808 → wss | python | обрыв 163.9с (как прямой) |
**Выводы:**
1. Под/сервис/nginx платформы — полностью здоровы (VM-ext 300с чисто,
VM-int 600с чисто; nginx request_time = полное время жизни, ошибок нет).
2. Обрыв воспроизводится ТОЛЬКО с рабочей станции, причём и напрямую, и через
локальный прокси (172.17.192.1:10808) — 5/5 обрывов, точка сбоя ~150с.
3. Публичный WS-echo (echo.websocket.org) с той же станции — ЧИСТО. Значит
сеть станции в целом здорова; страдает именно маршрут станция → Nubes
(185.247.187.151).
4. ВМ (другой маршрут до платформы, p50 6–8мс по данным shared-sqs) — чисто.
5. ⚠️ Нюанс для вывода «шлюз Nubes»: станция и ВМ ходят разными маршрутами.
Если ВМ обходит внешний шлюз Nubes (короткий путь/пиринг) — то «таймер
~150с на внешнем шлюзе Nubes» подтверждается. Если ВМ проходит тот же
шлюз — тогда проблема в маршруте станции (ISP/прокси-сеть). Точно
различить может только тест с третьей независимой точки входа
(другой ISP, мобильная сеть).
6. Взаимосвязь с shared-sqs: таймауты 3133с (HTTP) и MSS 1448 наблюдались
с той же станции — вероятно, тот же проблемный маршрут/шлюз. Наши данные
добавляют к картине «~150с на долгоживущие соединения».
### 9.8 Следующие шаги (предложены, ждут «делай»)
1. Тест wss с третьей независимой точки (другой ISP/мобильная сеть) — решает
вопрос «шлюз Nubes vs ISP станции».
2. HTTP-цикл с той же станции на /health (запрос каждые 10с, 5 мин) — связать
с таймаутами 3133с shared-sqs.
3. Тикет в Nubes: wss-соединения с интернета рвутся на ~150с (детерминированно),
+ таймауты 31–33с и MSS 1448 (данные shared-sqs).
4. При проектировании IoT: устройствам нужен MQTT keepalive ≤60с + автореконнект,
а до фикса шлюза — считать интернет-подключения устройств нестабильными
(~2.5–3 мин жизни).
### 9.9 Тест с третьей точки (Vultr, 95.179.252.111) — ГИПОТЕЗА ПОДТВЕРЖДЕНА
Доступ: `root@95.179.252.111`, ключ `~/.ssh/vultr_openssh` (на ВМ-машине).
Python 3.10, websocket-client 1.9.0 установлен. Тот же скрипт, 300с.
| Тест | Результат |
|---|---|
| Vultr → testiot wss | **обрыв 163.3с** (stall ~150с) — ИДЕНТИЧНО станции |
| Vultr → echo.websocket.org | чисто (175с+, pong 150.1с) |
**ИТОГОВЫЙ ВЫВОД (доказано с трёх точек):**
- Обрыв долгоживущих WS-соединений на ~150с — **на краевом шлюзе платформы
Nubes** (внешний вход 185.247.187.151), а не в поде/ingress и не в ISP клиента:
два независимых интернет-маршрута (станция, Vultr) дают идентичный обрыв,
контрольные публичные WS-echo с обеих точек чисты.
- ВМ 5.172.178.213 ходит коротким маршрутом (p50 6–8мс) и шлюза не касается —
поэтому чиста.
- Внутри платформы (port-forward) всё идеально (600с, 59/59).
- Для IoT: wss-устройства из интернета будут отваливаться каждые ~2.5–3 мин.
Это блокер для прода; тикет Nubes обязателен (объединить с таймаутами 31–33с
и MSS 1448 из shared-sqs — тот же шлюз).
### 9.10 Рекомендации для тикета Nubes (готовы данные)
- Симптом: WS-соединение (wss через edge) детерминированно рвётся на ~150с
при активном трафике (echo каждые 10с, ping/pong 2025с). Доказано:
станция 5/5, Vultr 1/1; nginx внутри видит полную жизнь соединения
(request_time 173.85с), под чист; изнутри кластера и с ВМ — чисто.
- Воспроизведение: `wscat -c wss://testiot.containerk8s.dev.nubes.ru/ws`
(или python websocket-client, ping 25с / echo 10с) — обрыв на 150–173с.
- Связь с shared-sqs: таймауты 3133с (~5.5% HTTP-запросов), MSS=1448 при
MTU 1400 — вероятно, тот же внешний шлюз.
---
## 10. Консультация Sonnet (промпт → ответ) — критическая оценка
**Что сделано:** составлен промпт для Sonnet (жёсткий список файлов: HISTORY
2026-08-16, migration-to-managed, sqs-integration, architecture/current-v0.2.3,
deployment-v0.2.3; 9 вопросов; формат «Ответ/Рекомендация/Риски», без кода).
Ответ получен, оценён ниже.
**Ключевые рекомендации Sonnet (приняты):**
- Компоновка: EMQX-контейнер + Go-монолит (API+bridge+consumer в одном бинарнике).
- EMQX оставить (не mochi-mqtt), если настроится на платформе.
- SQS в середине потока СОХРАНИТЬ (буфер при падении PG, admin-stats).
- Auth: перенос Secrets→таблица iot_devices ДО переключения; закрыть
authTestMode и ADMIN_STATS_TOKEN до прода.
- 3 фазы миграции; откат — DNS обратно; критерий готовности переключения
устройств: wss ≥600с с 3 независимых интернет-точек.
- Вопросы в техподдержку Nubes: кастомный домен, лимит ~150с, таймауты
3133с, MSS, subprotocol mqtt, multi-port, PVC, latest-pull, лимиты.
**ПОПРАВКИ к ответу Sonnet (важно):**
1. **PG НЕ меняется.** Managed PostgreSQL уже используется текущим consumer'ом
(IOT_PG_DSN → postgresqlk8s…svc.cluster.local). Утверждение Sonnet «новые
данные пишутся в новую PG» — ОШИБКА. Телеметрию переносить не нужно вовсе;
переносятся только CRD-объекты (устройства+пароли).
2. **Платформенные контейнеры живут в ТОМ ЖЕ кластере**, что и старый деплой
IoT: ноды `iot-naeel-*`, ingress `shturval` — один кластер. Значит старый
вход `iot.kube5s.ru` (185.247.187.147), скорее всего, идёт через ТОТ ЖЕ
краевой шлюз — вариант Sonnet «EMQX на старом кластере как точка входа»
проблему не обходит (и его сомнение в обратную сторону неверно).
Проверяемо тестом: держать wss к `wss://iot.kube5s.ru/mqtt` 5+ мин —
если stall ~150с есть и там, текущие устройства уже страдают.
3. **Multi-port** у Sonnet заявлен как факт в п.1 («1883 внутренний для bridge»),
но в п.9.6 сам же помечен НЕИЗВЕСТНО — противоречие. Вероятный реальный
сценарий: один порт на контейнер → EMQX наружу только 8083 (wss), bridge
к EMQX тоже по wss внутри платформы.
4. **Конфиг EMQX через env — не блокер.** EMQX 5 поддерживает env vars
(EMQX_*); плюс можно собрать свой образ на базе emqx/emqx с вшитым
emqx.conf (публичный Docker Hub) — снимает ограничение полностью.
5. **/metrics уже есть** в текущем операторе (порт 8080, controller-runtime)
— переносится в монолит, а не «отсутствует» (ошибка Sonnet).
6. **bcrypt для паролей** — ок для НОВЫХ устройств, но для переноса старых
нужен скрипт: прочитать все Secrets `iot-{deviceId}` в ns sless и вставить
в таблицу с сохранением работоспособности (хэшировать при вставке).
7. Дубли при at-least-once: нужен dedup-ключ в InsertTelemetry
(например уникальный индекс по (namespace, device_id, received_at) или
message-id) — сейчас отсутствует.
**Следующий шаг (предложен, ждёт «делай»):**
- Тест wss к СТАРОМУ прод-домену `wss://iot.kube5s.ru/mqtt` (5 мин, станция +
Vultr) — определить, страдают ли текущие устройства от того же шлюза.
---
## 11. Статус старого деплоя IoT — «зомби» (проверено 16.08)
> ⛔⛔⛔ ЛЕГАСИ — НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ при принятии решений. Раздел оставлен
> только как фиксация факта. Старый IoT для миграции значения не имеет.
**Проверка по вопросу «старый прод жив?» (факты):**
1. ns `sless` жив (125d). Все 4 deployment'а Running: `emqx`, `iot-mqtt-bridge`,
`iot-operator`, `iot-sqs-consumer` (поды 117–125 дней без рестартов).
2. **Внешний вход мёртв:** `iot.kube5s.ru` не резолвится ни с ВМ (getent пусто),
ни со станции (getent exit 2); curl → 000/exit 35. DNS-запись снята —
устройства через него не ходят.
3. **Конвейер телеметрии мёртв с 14.08:** секрет `iot-sqs-credentials` в sless
содержит старый `SQS_ENDPOINT=https://qu.kube5s.ru` (удалён 14.08 вместе со
старым shared-sqs). Логи consumer (16.08, прямо сейчас): каждые 5с
`ERROR SQS ReceiveMessage failed ... lookup qu.kube5s.ru: no such host`.
Bridge подключён к EMQX, но сообщений нет (нет устройств).
**Вывод:** старый IoT — «зомби»: поды формально Running, но внешнего входа нет
(DNS снят), а SQS-звено указывает на удалённый сервис. Работающих устройств на
старом кластере нет → риск миграции «не сломать прод-устройства» минимален,
переносить по сути нечего (только CRD-объекты/пароли при желании).
**Отмена теста:** wss-тест к старому домену бессмыслен — точки входа нет.
Старый деплой можно выключать после переключения (или по команде пользователя
— сейчас НЕ трогаем).
---
## 12. Пометка ЛЕГАСИ всего, что относится к старому IoT (16.08)
**Команда пользователя:** «УБЕРИ ВСЁ, что касается старого IoT; стирать пока
не надо; ЧЁТКО пропиши везде, что это ЛЕГАСИ и НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ».
**Сделано:**
1. В шапку каждого файла старого IoT добавлена пометка
«⛔⛔⛔ ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT ... НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ»:
- 15 документов в `doc/` (кроме `doc/sqs-integration.md` — актуален);
- `2026-08-13-migration-to-managed.md` (старый Node.js-план — отменён);
- все 8 манифестов `deployments/k8s/`;
- `config/crd/bases/iot.kube5s.ru_iotdevices.yaml`;
- `cmd/iot-operator/main.go`, `controllers/iotdevice_controller.go`,
`api/v1alpha1/device_types.go`, `api/v1alpha1/groupversion_info.go`;
- `Makefile` (старые k8s-цели);
- `examples/` (README.md, main.tf, handler.py).
2. Создан корневой `LEGACY.md` — реестр: что легаси, что актуально.
3. Раздел 11 этого журнала помечен «НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ».
4. Удалений НЕ производилось.
**Актуальные файлы (НЕ легаси):** `HISTORY/`, `doc/sqs-integration.md`,
`ws-probe/`, Go-код `cmd/mqtt-bridge/`, `cmd/sqs-consumer/`, `internal/`
(переиспользуется в монолите).
---
## 13. Аудит вмешательства старого IoT + план Фазы 1 (16.08)
**Команда пользователя:** «НЕ ДОПУСКАТЬ, чтобы старые данные и файлы от прежнего
IoT вмешивались в проект. ПРОВЕРЬ.»
**Результаты аудита (факты):**
1. **Код:** k8s-зависимости есть ТОЛЬКО в `internal/api/handler/`:
- `handler.go` — controller-runtime client, runtime;
- `iot_device_handler.go` — corev1, apimachinery, metav1, CRD-типы
`iotv1alpha1` (CRUD устройств через K8s + MQTT-auth через Secrets);
- `iot_admin_stats_handler.go` — corev1, controller-runtime.
Всё остальное ЧИСТО: `router.go`, `middleware/`, UI-embeds, `iotpg/`,
`cmd/mqtt-bridge/`, `cmd/sqs-consumer/`.
2. **go.mod:** тяжёлые k8s-депы (k8s.io/api, apimachinery, client-go v0.26.0,
controller-runtime v0.14.1 + ~40 indirect) — держатся только из-за этих
трёх файлов. После переписывания хендлеров — `go mod tidy` их уберёт.
3. **Dockerfile (корень)** — собирал 3 старых бинарника; НЕ был помечен.
→ помечен ЛЕГАСИ (новый монолит получит собственный Dockerfile).
4. **CI:** в `.github/` только правила (copilot-instructions, pravila) —
старых пайплайнов нет. `bin/` в git не отслеживается.
5. **Данные:** старый shared-sqs (qu.kube5s.ru) удалён полностью — старых
очередей в новом shared-sqs нет. В managed PG остались старые тестовые
tenant-DB (изолированы; очистка — только по отдельной команде). Старые
k8s Secrets новым кодом не читаются.
**Правило для монолита (зафиксировано):**
- Новый код НЕ импортирует `controllers/`, `api/v1alpha1/`, k8s-клиент.
- Хендлеры устройств переписываются на PG (`iot_devices`), MQTT-auth — SQL.
- `LEGACY.md` дополнен разделом аудита.
**План Фазы 1 (согласован в обсуждении, ждёт «делай»):**
1. Новый `cmd/iot-service/main.go` + структура по пакетам (config, api, bridge,
consumer, storage/iotpg) — много мелких файлов, без god-файлов.
2. Хендлеры устройств/админки — на PG вместо K8s; authTestMode=false.
3. Bridge+consumer — переиспользуются как есть (MQTT-подписка, SQS long-poll).
4. Новый Dockerfile (один бинарник), образ `naeel/iot-service` (имя — на
подтверждении пользователя), env-дефолты вшиты, секреты — в deck.
5. Сборка → пуш latest → пользователь создаёт контейнер в deck-UI.