165 lines
9.6 KiB
Markdown
165 lines
9.6 KiB
Markdown
# Деплой 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`) из консоли/порт-форварда:
|
||
|
||
```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.<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,
|
||
подпись на этом уровне не проверяется — периметр обеспечивает платформа):
|
||
|
||
```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://<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
|