9.6 KiB
Деплой 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. Предусловия
- PostgreSQL managed (инстанс платформы):
- роль
iot_service(LOGIN) с правами CREATEROLE + CREATEDB (нужны iotpg для per-tenant БД); - база
iotdb(OWNER iot_service); - SSL обязателен (pg_hba платформы: без шифрования соединение
отклоняется) — в DSN
sslmode=require, в EMQXssl.enable=true.
- роль
- shared-SQS: тенант с креды (AccessKey/SecretKey) и очередь
iot-telemetry. Внутренний endpoint тенанта вшит дефолтом в образ iot-service (см. config/defaults.go) — для чужого кластера уточнить внутренний адрес shared-sqs и задать черезSQS_ENDPOINT. - Имена доменов выбрать заранее (задаются в 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