# Деплой 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