26 KiB
Журнал сессии — 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. - Проблемы ПЛАТФОРМЫ (не сервиса):
- Таймауты каждые 31–33с на внешнем пути (~5.5% запросов без ответа 30с). Доказано tcpdump; внутри кластера потерь нет (port-forward 24977 раундов, 0 сбоев).
- 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], AccessKeySSAK-+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, namespacesless, API185.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,/healthOK. - Технические факты платформы (из 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с (не режется).
- Контейнер = k8s deployment
- 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 нужен путь до образа).
Что сделано (все шаги):
- Создан каталог
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 (меняется envPORT), версия через 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.
.gitignore— добавленws-probe/ws-probe(сборочный бинарник).- Локальные проверки:
go vetOK; smoke:/health→{"status":"ok","version":"v0.1.0"}; WS-апгрейд →HTTP/1.1 101 Switching Protocols(Sec-WebSocket-Accept корректный). - Docker: логин в Docker Hub под
naeelактивен (проверенdocker info). - Образ собран и запушен (публичный):
naeel/iot-ws-probe:v0.1.0(и:latest),- digest
sha256:dc029be3e79fb37f15ce23477096e340ab181b96c0132dc2e443f341e36f0688, - размер 10.9MB.
- Коммиты:
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).
Результаты проверок (первые):
GET /health→{"status":"ok","version":"v0.1.0"}— под стянул наш образ.GET /→ HTML-страница ws-probe (200).- WebSocket upgrade через платформенный ingress →
HTTP/1.1 101 Switching Protocols,Sec-WebSocket-Acceptкорректный, HSTS на месте. (В выводе curl первая строкаHTTP/1.1 200 Connection established— локальный CONNECT-прокси машины, ответ 101 пришёл от платформы.) 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).
Следующие диагностические шаги (предложены, ждут «делай»):
- Повторить тест 3 раза — воспроизводимость и точное время обрыва.
- Логи пода пробника на платформе (
kubectl logsна ВМ) — что видит сервер в момент обрыва (read error / ping error). - Тест WS изнутри кластера на внутренний
containerk8s.<uuid>.svc.cluster.local:8080— если держится >10 мин: обрыв на внешнем шлюзе/ingress; если тоже рвётся: проблема ближе к поду. - Держать обычное HTTP-соединение через ingress >150с — сравнение с WS.
- По результатам — тикет в Nubes (лимит соединений на ingress/шлюзе).
9. Диагностика обрыва (16.08, команда «делай тесты»)
9.1 Инфраструктура пробника на платформе (через ВМ, kubeconfig живой)
- Namespace пробника:
2d9b96ff-0a50-41e5-a5d5-1cd9e5621555, deploymentcontainerk8s, подcontainerk8s-5dc7dcbfcf-d4ch6(IP 172.16.2.227), servicecontainerk8sClusterIP 10.106.45.135:8080. - Рядом стандартные: pod vmagent (метрики), ns shared-sqs
f1ffb134-...(2d9h), старый чужой nsdf36c8af-...(30d) — не трогали. - Ingress платформы: ns
ingress,shturval-ingress-controller-controller(2 пода: 172.16.0.73 / 172.16.3.149), LB 185.247.187.151 (kube-vip). DNStestiot.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).
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с (как прямой) |
Выводы:
- Под/сервис/nginx платформы — полностью здоровы (VM-ext 300с чисто, VM-int 600с чисто; nginx request_time = полное время жизни, ошибок нет).
- Обрыв воспроизводится ТОЛЬКО с рабочей станции, причём и напрямую, и через локальный прокси (172.17.192.1:10808) — 5/5 обрывов, точка сбоя ~150с.
- Публичный WS-echo (echo.websocket.org) с той же станции — ЧИСТО. Значит сеть станции в целом здорова; страдает именно маршрут станция → Nubes (185.247.187.151).
- ВМ (другой маршрут до платформы, p50 6–8мс по данным shared-sqs) — чисто.
- ⚠️ Нюанс для вывода «шлюз Nubes»: станция и ВМ ходят разными маршрутами. Если ВМ обходит внешний шлюз Nubes (короткий путь/пиринг) — то «таймер ~150с на внешнем шлюзе Nubes» подтверждается. Если ВМ проходит тот же шлюз — тогда проблема в маршруте станции (ISP/прокси-сеть). Точно различить может только тест с третьей независимой точки входа (другой ISP, мобильная сеть).
- Взаимосвязь с shared-sqs: таймауты 31–33с (HTTP) и MSS 1448 наблюдались с той же станции — вероятно, тот же проблемный маршрут/шлюз. Наши данные добавляют к картине «~150с на долгоживущие соединения».
9.8 Следующие шаги (предложены, ждут «делай»)
- Тест wss с третьей независимой точки (другой ISP/мобильная сеть) — решает вопрос «шлюз Nubes vs ISP станции».
- HTTP-цикл с той же станции на /health (запрос каждые 10с, 5 мин) — связать с таймаутами 31–33с shared-sqs.
- Тикет в Nubes: wss-соединения с интернета рвутся на ~150с (детерминированно),
- таймауты 31–33с и MSS 1448 (данные shared-sqs).
- При проектировании 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 20–25с). Доказано: станция 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: таймауты 31–33с (~5.5% HTTP-запросов), MSS=1448 при MTU 1400 — вероятно, тот же внешний шлюз.