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

165 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Деплой 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