Files
IoT/doc/deployment-nubes-production.md
T

9.6 KiB
Raw Blame History

Деплой 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.<instance-uid>. svc.cluster.local:5432) из консоли/порт-форварда:

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.<uid>.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://<emqx-домен>/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.<uid>.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.<uuid-emqx>.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://<iot-домен>/health{"status":"ok","version":"..."}. Бридж в логах: bridge: MQTT connected + bridge: subscribed.

4. Устройства (регистрация)

Устройство создаётся через API (JWT Bearer; структурная проверка sub+exp, подпись на этом уровне не проверяется — периметр обеспечивает платформа):

# токен для 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://<iot-домен>/v1/namespaces/<ns>/iot/devices

Ответ содержит mqtt_username = <ns>_<device_id> и mqtt_password. Повторный просмотр пароля: GET /v1/namespaces/<ns>/iot/devices/<name>.

Устройство подключается:

  • URL: wss://<emqx-домен>/mqtt, подпротокол mqtt;
  • username <ns>_<device_id>, password из ответа API;
  • публикация: топик <ns>/telemetry/<device_id>;
  • подписка разрешена только на свой топик (ACL).

Негативные auth-тесты (должны быть отказ): неверный пароль → CONNACK rc=4; неизвестный username → rc=5. Проверять через on_connect rc (rc из paho.connect() всегда 0 даже при отказе!).

Телеметрия: GET /v1/namespaces/<ns>/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 <ns> set image deployment/containerk8s app=<image>:<tag> — зеркало подтянет уникальный тег.

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