docs: no-kubectl deployment procedure (tail 4) + HISTORY 28-29

This commit is contained in:
“Naeel”
2026-08-16 16:20:07 +04:00
parent 4ddd95a441
commit 4c8bb39354
2 changed files with 199 additions and 0 deletions
+35
View File
@@ -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 обновлены).
+164
View File
@@ -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