docs: no-kubectl deployment procedure (tail 4) + HISTORY 28-29
This commit is contained in:
@@ -1006,3 +1006,38 @@ OSS-образ emqx/emqx:5.5.1 СОДЕРЖИТ postgres authn/authz
|
||||
- Во время окна недоступности бридж завис в одной попытке дозвона (тишина
|
||||
в логах) → вылечено rollout restart монолита. Для будущего: в paho
|
||||
выставить SetConnectTimeout явно (кандидат на hardening).
|
||||
|
||||
---
|
||||
|
||||
## 28. Tail 4 выполнен: процедура деплоя без kubectl (15:35 GMT+03)
|
||||
|
||||
- Создан `doc/deployment-nubes-production.md` — полная процедура деплоя IoT
|
||||
на чужом кластере Nubes БЕЗ kubectl:
|
||||
1. PG: роль iot_service (LOGIN, CREATEROLE, CREATEDB), БД iotdb owner-ом,
|
||||
SSL обязателен (sslmode=require / EMQX ssl.enable).
|
||||
2. EMQX ПЕРВЫМ (naeel/iot-emqx:v0.2.4): env только PG
|
||||
(EMQX_AUTHENTICATION__1__*/EMQX_AUTHORIZATION__SOURCES__1__*),
|
||||
CPU ≥1000m; предупреждение про запрещённые URL-env и ${VAR}.
|
||||
3. iot-service ВТОРЫМ (naeel/iot-service:v0.1.2): MQTT_HOST=
|
||||
containerk8s.<uuid-emqx>.svc.cluster.local (UUID из консоли deck),
|
||||
DSN sslmode=require, SQS-креды, MQTT_USERNAME/PASSWORD.
|
||||
4. Устройства: регистрация через API (JWT структурный), username
|
||||
<ns>_<device_id>, wss на домен EMQX, топик <ns>/telemetry/<device_id>;
|
||||
негативные auth-тесты (CONNACK rc=4/5) — чек-лист.
|
||||
5. Обновления образов без kubectl — только пересоздание контейнера
|
||||
(путь к образу фиксируется при создании, latest кэшируется зеркалом).
|
||||
6. Диагностика и известные ограничения платформы (wss ~150с, SQS
|
||||
внешний путь, фиксированные env).
|
||||
- Версии зафиксированы в шапке документа.
|
||||
|
||||
## 29. Итог сессии (хвосты 1–4 закрыты)
|
||||
|
||||
| Хвост | Результат |
|
||||
|---|---|
|
||||
| 1. AUTH_TEST_MODE=false | Выполнено (kubectl set env, 11:11). |
|
||||
| 2. EMQX без dashboard + CPU | v0.2.1 (dashboard off), CPU 2/1Gi patch. |
|
||||
| 2а. Баг resubscribe бриджа | Найден и исправлен (v0.1.1, OnConnectHandler). |
|
||||
| 3. PG-auth в EMQX | v0.2.4 (postgresql authn+authz) + EnsureBridgeDevice v0.1.2. |
|
||||
| 4. Процедура деплоя | doc/deployment-nubes-production.md. |
|
||||
|
||||
Версии: iot-service v0.1.2, iot-emqx v0.2.4 (Docker Hub, latest обновлены).
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# Деплой 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
|
||||
Reference in New Issue
Block a user