From 4c8bb39354874c7ca054b570d415a96647f64a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 16 Aug 2026 16:20:07 +0400 Subject: [PATCH] docs: no-kubectl deployment procedure (tail 4) + HISTORY 28-29 --- HISTORY/2026-08-16-session-log.md | 35 ++++++ doc/deployment-nubes-production.md | 164 +++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 doc/deployment-nubes-production.md diff --git a/HISTORY/2026-08-16-session-log.md b/HISTORY/2026-08-16-session-log.md index 20f7222..a0ec46a 100644 --- a/HISTORY/2026-08-16-session-log.md +++ b/HISTORY/2026-08-16-session-log.md @@ -1006,3 +1006,38 @@ OSS-образ emqx/emqx:5.5.1 СОДЕРЖИТ postgres authn/authz - Во время окна недоступности бридж завис в одной попытке дозвона (тишина в логах) → вылечено rollout restart монолита. Для будущего: в paho выставить SetConnectTimeout явно (кандидат на hardening). + +--- + +## 28. Tail 4 выполнен: процедура деплоя без kubectl (15:35 GMT+03) + +- Создан `doc/deployment-nubes-production.md` — полная процедура деплоя IoT + на чужом кластере Nubes БЕЗ kubectl: + 1. PG: роль iot_service (LOGIN, CREATEROLE, CREATEDB), БД iotdb owner-ом, + SSL обязателен (sslmode=require / EMQX ssl.enable). + 2. EMQX ПЕРВЫМ (naeel/iot-emqx:v0.2.4): env только PG + (EMQX_AUTHENTICATION__1__*/EMQX_AUTHORIZATION__SOURCES__1__*), + CPU ≥1000m; предупреждение про запрещённые URL-env и ${VAR}. + 3. iot-service ВТОРЫМ (naeel/iot-service:v0.1.2): MQTT_HOST= + containerk8s..svc.cluster.local (UUID из консоли deck), + DSN sslmode=require, SQS-креды, MQTT_USERNAME/PASSWORD. + 4. Устройства: регистрация через API (JWT структурный), username + _, wss на домен EMQX, топик /telemetry/; + негативные auth-тесты (CONNACK rc=4/5) — чек-лист. + 5. Обновления образов без kubectl — только пересоздание контейнера + (путь к образу фиксируется при создании, latest кэшируется зеркалом). + 6. Диагностика и известные ограничения платформы (wss ~150с, SQS + внешний путь, фиксированные env). +- Версии зафиксированы в шапке документа. + +## 29. Итог сессии (хвосты 1–4 закрыты) + +| Хвост | Результат | +|---|---| +| 1. AUTH_TEST_MODE=false | Выполнено (kubectl set env, 11:11). | +| 2. EMQX без dashboard + CPU | v0.2.1 (dashboard off), CPU 2/1Gi patch. | +| 2а. Баг resubscribe бриджа | Найден и исправлен (v0.1.1, OnConnectHandler). | +| 3. PG-auth в EMQX | v0.2.4 (postgresql authn+authz) + EnsureBridgeDevice v0.1.2. | +| 4. Процедура деплоя | doc/deployment-nubes-production.md. | + +Версии: iot-service v0.1.2, iot-emqx v0.2.4 (Docker Hub, latest обновлены). diff --git a/doc/deployment-nubes-production.md b/doc/deployment-nubes-production.md new file mode 100644 index 0000000..59c575e --- /dev/null +++ b/doc/deployment-nubes-production.md @@ -0,0 +1,164 @@ +# Деплой IoT на платформе Nubes без kubectl (чужой кластер) + +**Версии на 2026-08-16:** `naeel/iot-emqx:v0.2.4`, `naeel/iot-service:v0.1.2`. +Образы публичные на Docker Hub, каждый имеет РОВНО один EXPOSE +(EMQX — 8083, iot-service — 9090). Поле порта в deck отсутствует — порт +берётся из EXPOSE образа. + +Процедура не требует kubectl: вся конфигурация задаётся env в deck-UI при +создании контейнеров. EMQX авторизует устройства напрямую в PostgreSQL +(таблица `iot_devices`) — адрес монолита ему НЕ нужен (в этом суть PG-auth, +см. HISTORY секцию 27). + +--- + +## 0. Предусловия + +1. **PostgreSQL managed** (инстанс платформы): + - роль `iot_service` (LOGIN) с правами **CREATEROLE + CREATEDB** + (нужны iotpg для per-tenant БД); + - база `iotdb` (OWNER iot_service); + - **SSL обязателен** (pg_hba платформы: без шифрования соединение + отклоняется) — в DSN `sslmode=require`, в EMQX `ssl.enable=true`. +2. **shared-SQS**: тенант с креды (AccessKey/SecretKey) и очередь + `iot-telemetry`. Внутренний endpoint тенанта вшит дефолтом в образ + iot-service (см. config/defaults.go) — для чужого кластера уточнить + внутренний адрес shared-sqs и задать через `SQS_ENDPOINT`. +3. **Имена доменов** выбрать заранее (задаются в deck при создании + контейнера), например: `iot.containerk8s.dev.nubes.ru` (API) и + `exqx.containerk8s.dev.nubes.ru` (MQTT wss). + +## 1. PostgreSQL (разово) + +Подключаться к master инстанса (`postgresqlk8s-master.. +svc.cluster.local:5432`) из консоли/порт-форварда: + +```sql +CREATE ROLE iot_service LOGIN PASSWORD '<сгенерировать>'; +ALTER ROLE iot_service CREATEROLE CREATEDB; +-- БД создаётся с owner iot_service (ddl_user не может SET ROLE напрямую): +GRANT iot_service TO <текущая роль>; +CREATE DATABASE iotdb OWNER iot_service; +REVOKE iot_service FROM <текущая роль>; +``` + +Таблицы (`iot_devices`, per-tenant БД телеметрии) создаются кодом +автоматически при старте iot-service. + +## 2. EMQX (создаётся ПЕРВЫМ) + +Тип «Простой HTTP контейнер», образ `naeel/iot-emqx:v0.2.4` +(или `latest` — но см. раздел 5 про обновления). CPU **≥ 1000m** +(на квоте 500m загрузка 5–10 мин, dashboard падает с таймаутами). + +Env (ВСЕ при создании — после создания env не меняются): + +| Переменная | Значение | +|---|---| +| `EMQX_AUTHENTICATION__1__SERVER` | `postgresqlk8s-master..svc.cluster.local:5432` | +| `EMQX_AUTHENTICATION__1__DATABASE` | `iotdb` | +| `EMQX_AUTHENTICATION__1__USERNAME` | `iot_service` | +| `EMQX_AUTHENTICATION__1__PASSWORD` | пароль роли | +| `EMQX_AUTHORIZATION__SOURCES__1__SERVER` | как AUTHENTICATION | +| `EMQX_AUTHORIZATION__SOURCES__1__DATABASE` | `iotdb` | +| `EMQX_AUTHORIZATION__SOURCES__1__USERNAME` | `iot_service` | +| `EMQX_AUTHORIZATION__SOURCES__1__PASSWORD` | пароль роли | +| `EMQX_LOG__CONSOLE_HANDLER__LEVEL` | `warning` | + +⚠ НЕ задавать `EMQX_AUTHENTICATION__1__URL` / `EMQX_AUTHORIZATION__SOURCES__1__URL` +(это HTTP-auth старой схемы — уронит под с `unknown_fields "url"`). +⚠ `${VAR}` в конфиге не интерпретируется; только официальные env-переопределения. + +Загрузка на CPU ≥1000m ~2 мин. Проверка: `curl https:///mqtt` +должен вернуть `400` (Cowboy без subprotocol — норма); wss-рука через +клиент с subprotocol `mqtt`. + +После создания получить из консоли deck **UUID namespace EMQX** — он нужен +на шаге 3. + +## 3. iot-service (создаётся ВТОРЫМ) + +Образ `naeel/iot-service:v0.1.2`, CPU ≥ 500m, память ≥ 256Mi. + +Env: + +| Переменная | Значение | Обязательна | +|---|---|---| +| `IOT_PG_DSN` | `postgresql://iot_service:<пароль>@postgresqlk8s-master..svc.cluster.local:5432/iotdb?sslmode=require` | да | +| `SQS_ACCESS_KEY` | AccessKey тенанта shared-sqs | да | +| `SQS_SECRET_KEY` | SecretKey тенанта shared-sqs | да | +| `MQTT_USERNAME` | `iot-bridge` | да | +| `MQTT_PASSWORD` | пароль бриджа (тот же, что в строке `__bridge` в iot_devices) | да | +| `MQTT_HOST` | `containerk8s..svc.cluster.local` | да (иначе дефолт `emqx` не резолвится) | +| `ADMIN_STATS_TOKEN` | токен для `/iot-admin/stats` | нет | +| `AUTH_TEST_MODE` | `false` | нет (дефолт false) | + +Остальные дефолты вшиты в образ (API_PORT=9090, SQS_ENDPOINT, SQS_QUEUE_NAME, +SQS_REGION, long-poll 20с, visibility 30с, MQTT_PORT=8083, MQTT_WS_PATH=/mqtt, +MQTT_CLIENT_ID=iot-bridge, LOG_LEVEL=info). + +Строку бриджа (`namespace='__bridge'`, `device_id=iot-bridge`, +`mqtt_password=MQTT_PASSWORD`) монолит создаёт сам при старте +(EnsureBridgeDevice) — вручную ничего не создавать. + +Проверка: `GET https:///health` → `{"status":"ok","version":"..."}`. +Бридж в логах: `bridge: MQTT connected` + `bridge: subscribed`. + +## 4. Устройства (регистрация) + +Устройство создаётся через API (JWT Bearer; структурная проверка sub+exp, +подпись на этом уровне не проверяется — периметр обеспечивает платформа): + +```bash +# токен для API (sub+exp): +TOKEN=$(python3 -c "import base64,json,time;h=base64.urlsafe_b64encode(b'{\"alg\":\"none\",\"typ\":\"JWT\"}').rstrip(b'=').decode();p=base64.urlsafe_b64encode(json.dumps({'sub':'admin','exp':int(time.time())+86400}).encode()).rstrip(b'=').decode();print(h+'.'+p+'.sig')") +curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ + -d '{"name":"dev1","device_id":"dev-001"}' \ + https:///v1/namespaces//iot/devices +``` + +Ответ содержит `mqtt_username` = `_` и `mqtt_password`. +Повторный просмотр пароля: `GET /v1/namespaces//iot/devices/`. + +Устройство подключается: +- URL: `wss:///mqtt`, подпротокол `mqtt`; +- username `_`, password из ответа API; +- публикация: топик `/telemetry/`; +- подписка разрешена только на свой топик (ACL). + +Негативные auth-тесты (должны быть отказ): неверный пароль → CONNACK rc=4; +неизвестный username → rc=5. Проверять через on_connect rc (rc из +`paho.connect()` всегда 0 даже при отказе!). + +Телеметрия: `GET /v1/namespaces//iot/telemetry?limit=100` +(тоже JWT Bearer). + +## 5. Обновления образов + +Платформа кэширует `latest` зеркалом, а путь к образу фиксируется при +создании контейнера. Без kubectl обновление = **пересоздание контейнера в +deck с конкретным тегом** (`naeel/iot-emqx:v0.2.4`, `naeel/iot-service:v0.1.2`). +В своём кластере (есть kubectl): `kubectl -n set image +deployment/containerk8s app=:` — зеркало подтянет уникальный тег. + +## 6. Диагностика (кратко) + +- EMQX не поднялся: `kubectl logs` (если доступен) или `curl` домена — + 503 = под лежит; schema-ошибки видны в логах запуска + (`failed_to_check_schema`, `unknown_fields`). +- Устройство не авторизуется: включить `EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug`, + смотреть `authenticator_result`/`authentication_result` и CONNACK ReasonCode. +- Бридж отваливается: в логах монолита `bridge: ...`; после рестарта EMQX + бридж переподписывается автоматически (фикс resubscribe в OnConnectHandler, + HISTORY секция 26). +- Телеметрия не доходит: цепочка устройство → EMQX → бридж → SQS + `iot-telemetry` → consumer → PG; сверять счётчики по HISTORY секции 26.4. + +## 7. Известные ограничения платформы + +- Edge-шлюз платформы рвёт wss ~150с (тикет Nubes подготовлен) — внешние + устройства должны переподключаться; внутри кластера (бридж) проблем нет. +- Внешний путь к SQS — таймауты ~31–33с (~5.5% запросов) и MSS-проблема; + монолит использует внутренний endpoint shared-sqs. +- env фиксируются при создании контейнера; ошиблись — пересоздать. +EOF