docs: IoT telemetry storage architecture decisions and thinking log
This commit is contained in:
@@ -1,32 +1,74 @@
|
|||||||
# Архитектура системы
|
# Архитектура системы
|
||||||
|
|
||||||
Последнее обновление: 2026-03-18 (v0.1.34 + funcs-service v0.2.0)
|
Последнее обновление: 2026-04-04 (IoT telemetry storage architecture decision)
|
||||||
|
|
||||||
## Общее описание
|
## Общее описание
|
||||||
|
|
||||||
Managed Serverless Functions Service для облачного провайдера nubes.ru.
|
Managed Serverless Functions Service + IoT Platform для облачного провайдера nubes.ru.
|
||||||
|
Два независимых компонента: sless (serverless) и iot (IoT), каждый со своим оператором.
|
||||||
Пользователь загружает код через Terraform, сервис его собирает (kaniko) и запускает
|
Пользователь загружает код через Terraform, сервис его собирает (kaniko) и запускает
|
||||||
по HTTP-триггеру, расписанию (cron) или вручную через one-shot Job.
|
по HTTP-триггеру, расписанию (cron), вручную или по событию от IoT устройства.
|
||||||
|
|
||||||
|
## Namespace Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
namespace: sless — платформа serverless
|
||||||
|
(sless-operator, event-dispatcher, RabbitMQ, Postgres invocations)
|
||||||
|
namespace: sless-{hash} — tenant функции (function pods каждого клиента)
|
||||||
|
namespace: iot — платформа IoT
|
||||||
|
(iot-operator, EMQX, Postgres telemetry)
|
||||||
|
namespace: iot-{hash} — tenant IoT ресурсы (IoTDevice CRDs)
|
||||||
|
```
|
||||||
|
|
||||||
## Стек
|
## Стек
|
||||||
|
|
||||||
| Компонент | Технология | Где запущен |
|
| Компонент | Технология | Где запущен |
|
||||||
|-----------|-----------|-------------|
|
|-----------|-----------|-------------|
|
||||||
| Operator (API + Controllers) | Go (controller-runtime) | Kubernetes, namespace `sless` |
|
| sless-operator (API + Controllers) | Go (controller-runtime) | namespace `sless` |
|
||||||
| funcs-service (глобальная консоль) | Go (net/http) | Kubernetes, namespace `sless` |
|
| iot-operator (API + Controllers) | Go (controller-runtime) | namespace `iot` |
|
||||||
| PostgreSQL | PostgreSQL 16 | Kubernetes, namespace `sless` |
|
| PostgreSQL (invocations) | PostgreSQL 16 | namespace `sless` |
|
||||||
|
| PostgreSQL (telemetry) | PostgreSQL 16 | namespace `iot` |
|
||||||
|
| EMQX | EMQX 5.5.1 | namespace `iot` |
|
||||||
|
| RabbitMQ | RabbitMQ 3 | namespace `sless` |
|
||||||
|
| event-dispatcher | Go | namespace `sless` |
|
||||||
|
| iot-mqtt-bridge | Go | namespace `iot` |
|
||||||
| S3 | Ceph (облачный) | `s3.msk-1.ngcloud.ru` |
|
| S3 | Ceph (облачный) | `s3.msk-1.ngcloud.ru` |
|
||||||
| Container Registry | DockerHub (`naeel/`) | внешний |
|
| Container Registry | PearlHarbor (Nubes) | внешний |
|
||||||
| Builder | kaniko (k8s Job) | namespace пользователя |
|
| Builder | kaniko (k8s Job) | namespace пользователя |
|
||||||
| Функции (HTTP) | k8s Deployment + Service | namespace пользователя |
|
| Функции (HTTP) | k8s Deployment + Service | namespace sless-{hash} |
|
||||||
| Функции (one-shot) | k8s Job | namespace пользователя |
|
| Функции (one-shot) | k8s Job | namespace sless-{hash} |
|
||||||
| Функции (cron) | k8s CronJob | namespace пользователя |
|
| Функции (cron) | k8s CronJob | namespace sless-{hash} |
|
||||||
| Terraform Provider | Go (plugin framework v6) | localhost/CI |
|
| Terraform Provider | Go (plugin framework v6) | localhost/CI |
|
||||||
| nubes API | REST (облако) | `deck-api.ngcloud.ru` |
|
| nubes API | REST (облако) | `deck-api.ngcloud.ru` |
|
||||||
|
|
||||||
> Redis и RabbitMQ — отложены до v2.
|
## IoT Data Flow
|
||||||
|
|
||||||
## Компонент: funcs-service
|
```
|
||||||
|
IoT устройство
|
||||||
|
↓ ws://iot.kube5s.ru:80/mqtt (WebSocket, пока 1883 закрыт)
|
||||||
|
EMQX (namespace iot)
|
||||||
|
↓ ACL: каждое устройство видит только свои топики {ns}/{deviceId}/#
|
||||||
|
iot-mqtt-bridge
|
||||||
|
↓
|
||||||
|
RabbitMQ (namespace sless)
|
||||||
|
↓
|
||||||
|
event-dispatcher
|
||||||
|
↓ параллельно:
|
||||||
|
1. INSERT INTO tenant_{ns}.iot_telemetry ← автоматически
|
||||||
|
2. Вызов serverless function (если настроена)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Изоляция данных
|
||||||
|
|
||||||
|
- MQTT: ACL по username → топики только своего устройства
|
||||||
|
- Postgres: отдельная DATABASE per tenant, разные credentials
|
||||||
|
- k8s: отдельный namespace per tenant
|
||||||
|
|
||||||
|
## Связь sless ↔ iot
|
||||||
|
|
||||||
|
- Общий идентификатор tenant: `{hash}` в именах namespace
|
||||||
|
- Коммуникация через RabbitMQ endpoint (не через Go пакеты)
|
||||||
|
- Loose coupling — могут быть в разных кластерах
|
||||||
|
|
||||||
Глобальный HTTP сервис — **одна копия** на весь кластер, для всех пользователей.
|
Глобальный HTTP сервис — **одна копия** на весь кластер, для всех пользователей.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
# Решение: IoT Telemetry Storage Architecture
|
||||||
|
# Дата: 2026-04-04
|
||||||
|
# Агент: GitHub Copilot (Claude Sonnet 4.6)
|
||||||
|
# Статус: ПРИНЯТО
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
IoT платформа принимает данные с датчиков через MQTT. Данные проходят:
|
||||||
|
EMQX → iot-mqtt-bridge → RabbitMQ → event-dispatcher → function pod.
|
||||||
|
|
||||||
|
Проблема: данные не сохраняются. Функция получает событие и забывает его.
|
||||||
|
Для клиентов (мониторинг объектов, счётчики, производство) нужно:
|
||||||
|
- Автоматическое хранение всей телеметрии
|
||||||
|
- Доступ к историческим данным
|
||||||
|
- Низкий порог входа — не требовать от клиента настройки БД
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Решения
|
||||||
|
|
||||||
|
### 1. Хранилище — Postgres, отдельная DATABASE per tenant
|
||||||
|
|
||||||
|
**Выбрано**: один Postgres инстанс, отдельная DATABASE на каждого клиента.
|
||||||
|
|
||||||
|
**Отклонено**: одна таблица с tenant_id колонкой.
|
||||||
|
- Причина: изоляция только программная. Ошибка в WHERE → утечка чужих данных.
|
||||||
|
|
||||||
|
**Структура**:
|
||||||
|
```
|
||||||
|
Postgres (StatefulSet в namespace iot)
|
||||||
|
├── sless_platform — системные данные платформы (tenants, etc)
|
||||||
|
├── tenant_{hash} — данные клиента A (полная изоляция)
|
||||||
|
└── tenant_{hash} — данные клиента B (полная изоляция)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Безопасность**:
|
||||||
|
- Каждый tenant имеет свой Postgres USER с уникальным паролем (UUID)
|
||||||
|
- Пароль генерируется при создании tenant, хранится в k8s Secret
|
||||||
|
- Клиент B физически не может подключиться к DATABASE клиента A
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Доступ клиента — только через REST API
|
||||||
|
|
||||||
|
**Выбрано**: клиент читает телеметрию через REST API платформы.
|
||||||
|
|
||||||
|
**Отклонено**: прямой доступ к Postgres через connection string.
|
||||||
|
- Причина: Postgres внутри кластера, не должен торчать наружу. Security.
|
||||||
|
|
||||||
|
**API**:
|
||||||
|
```
|
||||||
|
GET /v1/namespaces/{ns}/iot/telemetry
|
||||||
|
?device={device_id}
|
||||||
|
&from={RFC3339}
|
||||||
|
&to={RFC3339}
|
||||||
|
&limit={int}
|
||||||
|
|
||||||
|
GET /v1/namespaces/{ns}/iot/devices/{id}/last
|
||||||
|
```
|
||||||
|
|
||||||
|
Авторизация — Bearer токен (тот же механизм что и для functions).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Схема таблицы telemetry
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE iot_telemetry (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
device_id TEXT NOT NULL,
|
||||||
|
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
payload JSONB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_iot_telemetry_device_ts
|
||||||
|
ON iot_telemetry (device_id, ts DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Почему JSONB**: у каждого клиента разные наборы данных:
|
||||||
|
- датчик температуры: `{"temp": 22.5, "humidity": 60}`
|
||||||
|
- GPS трекер: `{"lat": 55.75, "lon": 37.61, "speed": 60}`
|
||||||
|
- счётчик воды: `{"liters": 1234.5, "flow": 0.3}`
|
||||||
|
|
||||||
|
Фиксированная схема невозможна. JSONB + индекс по (device_id, ts) даёт
|
||||||
|
достаточную производительность для малого и среднего бизнеса.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. schema.sql при деплое функции
|
||||||
|
|
||||||
|
Клиент может положить `schema.sql` рядом с функцией:
|
||||||
|
```
|
||||||
|
my-function/
|
||||||
|
├── handler.py
|
||||||
|
├── schema.sql ← CREATE TABLE IF NOT EXISTS my_alerts (...)
|
||||||
|
└── requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
При деплое оператор выполняет `schema.sql` в БД tenant'а.
|
||||||
|
Это позволяет клиентам без знания Python настраивать дополнительные таблицы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. DB_DSN в функцию
|
||||||
|
|
||||||
|
При запуске function pod оператор прокидывает `DB_DSN` из Secret в env var:
|
||||||
|
```
|
||||||
|
DB_DSN=postgresql://tenant_abc:password@iot-postgres.iot.svc:5432/tenant_abc
|
||||||
|
```
|
||||||
|
|
||||||
|
Функция использует стандартный драйвер, не знает о деталях платформы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Разделение sless и iot операторов
|
||||||
|
|
||||||
|
**Решение**: sless-operator и iot-operator — ОТДЕЛЬНЫЕ компоненты с раздельными namespace.
|
||||||
|
|
||||||
|
**Мотивация**:
|
||||||
|
- В будущем могут быть в разных кластерах
|
||||||
|
- Независимый деплой и масштабирование
|
||||||
|
- Разные команды могут владеть компонентами
|
||||||
|
- Нет cross-dependency в коде (loose coupling)
|
||||||
|
|
||||||
|
**Namespace layout**:
|
||||||
|
```
|
||||||
|
namespace: sless — платформа serverless
|
||||||
|
(sless-operator, event-dispatcher, RabbitMQ, Postgres invocations)
|
||||||
|
namespace: sless-{hash} — tenant функции (function pods каждого клиента)
|
||||||
|
|
||||||
|
namespace: iot — платформа IoT
|
||||||
|
(iot-operator, EMQX, Postgres telemetry)
|
||||||
|
namespace: iot-{hash} — tenant IoT ресурсы (IoTDevice CRDs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Связь между sless и iot**:
|
||||||
|
- Общий идентификатор tenant: `{hash}` одинаковый в sless-{hash} и iot-{hash}
|
||||||
|
- MQTT событие → RabbitMQ в namespace sless → function pod в sless-{hash}
|
||||||
|
- iot-operator НЕ импортирует Go пакеты sless-operator
|
||||||
|
- Общение только через k8s API и RabbitMQ endpoints
|
||||||
|
|
||||||
|
**Postgres**:
|
||||||
|
- sless: отдельный Postgres для invocations логов
|
||||||
|
- iot: отдельный Postgres для telemetry per-tenant
|
||||||
|
- Разные StatefulSet, разные PVC, разные credentials
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Postgres инстанс для IoT
|
||||||
|
|
||||||
|
**Выбрано**: `postgres:16-alpine` StatefulSet в namespace `iot`.
|
||||||
|
|
||||||
|
**Причина**: простота для разработки. При передаче в production девопсы
|
||||||
|
заменят на Managed Postgres от Nubes — connection string поменяется, код не меняется.
|
||||||
|
|
||||||
|
**Ресурсы**:
|
||||||
|
- PVC: 10Gi (начальный размер, увеличивается по мере роста)
|
||||||
|
- Memory limit: 512Mi
|
||||||
|
- CPU: 0.5 cores
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## План реализации
|
||||||
|
|
||||||
|
1. StatefulSet Postgres в namespace `iot`
|
||||||
|
2. iot-operator: provisioning при создании IoTDevice namespace
|
||||||
|
- CREATE USER tenant_{ns} PASSWORD '{uuid}'
|
||||||
|
- CREATE DATABASE tenant_{ns} OWNER tenant_{ns}
|
||||||
|
- CREATE TABLE iot_telemetry + индекс
|
||||||
|
3. iot-mqtt-bridge: INSERT telemetry при получении MQTT сообщения
|
||||||
|
4. iot-operator: REST API `/v1/namespaces/{ns}/iot/telemetry`
|
||||||
|
5. sless-operator: при деплое function → прокинуть DB_DSN + выполнить schema.sql
|
||||||
+47
-1
@@ -1597,5 +1597,51 @@ G15 перезапущен → **21/21 PASS ✅**
|
|||||||
| 4 | nginx `client_max_body_size` ограничивает upload → 413 (не настроено явно) | G13F-4 NOTE |
|
| 4 | nginx `client_max_body_size` ограничивает upload → 413 (не настроено явно) | G13F-4 NOTE |
|
||||||
|
|
||||||
### Версия оператора
|
### Версия оператора
|
||||||
`v0.1.51` — задеплоен, работает
|
`v0.1.52` — задеплоен, работает
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-04-04 — IoT WebSocket workaround + MQTT ACL + Архитектура телеметрии
|
||||||
|
|
||||||
|
### Выполнено
|
||||||
|
|
||||||
|
#### MQTT WebSocket workaround (порт 1883 закрыт NSX-T)
|
||||||
|
- Создан Ingress `emqx-mqtt-websocket`: `iot.kube5s.ru/mqtt → EMQX:8083`
|
||||||
|
- DNS A-запись `iot.kube5s.ru → 185.247.187.147` создана пользователем
|
||||||
|
- Отлажена цепочка: убран `configuration-snippet` (заблокирован в nginx v1.12.6),
|
||||||
|
добавлен `ssl-redirect: false`, `pathType: Exact`
|
||||||
|
- Тест: `Connected rc=0` через paho-mqtt WebSocket ✅
|
||||||
|
- Коммит: `6e3e473` (ветка Ioter)
|
||||||
|
|
||||||
|
#### MQTT ACL изоляция топиков
|
||||||
|
- Найдена уязвимость: `authorization { no_match = allow }` — любой клиент мог
|
||||||
|
читать топики других клиентов после успешного CONNECT
|
||||||
|
- EMQX 5.x: ACL в ответе auth игнорируется (это EMQX 4.x фича)
|
||||||
|
- Добавлен endpoint `POST /internal/mqtt/acl` в sless-operator
|
||||||
|
- Обновлён `emqx.conf`: HTTP authorization backend, `no_match = deny`
|
||||||
|
- Тест: `sless/iot-bridge/#` → ALLOWED, `sless/other-device/telemetry` → DENIED + disconnect
|
||||||
|
- Лог EMQX: `authorization_permission_denied` ✅
|
||||||
|
- Оператор v0.1.52 задеплоен
|
||||||
|
- Коммит: `b23ae40` (ветка Ioter)
|
||||||
|
|
||||||
|
### Архитектурные решения (обсуждение, не реализовано)
|
||||||
|
|
||||||
|
Принято решение о хранении IoT телеметрии:
|
||||||
|
- Отдельная DATABASE per tenant в одном Postgres инстансе
|
||||||
|
- REST API для доступа (не прямой доступ к Postgres)
|
||||||
|
- JSONB payload (разные данные у разных клиентов)
|
||||||
|
- Отдельный iot-operator независимо от sless-operator
|
||||||
|
- schema.sql при деплое функции
|
||||||
|
- DB_DSN в env var функции
|
||||||
|
|
||||||
|
Подробно: `doc/decisions/iot-telemetry-storage-2026-04-04.md`
|
||||||
|
|
||||||
|
### Следующий этап (ветка iot-pg-telemetry)
|
||||||
|
|
||||||
|
- [ ] Postgres StatefulSet в namespace `iot`
|
||||||
|
- [ ] Provisioning БД при создании tenant
|
||||||
|
- [ ] INSERT telemetry из iot-mqtt-bridge
|
||||||
|
- [ ] REST API чтения телеметрии
|
||||||
|
- [ ] DB_DSN в function pod env
|
||||||
|
- [ ] schema.sql при деплое функции
|
||||||
|
|
||||||
|
|||||||
@@ -102,3 +102,114 @@ listeners.ws.default {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Ждём подтверждения от пользователя перед реализацией.
|
Ждём подтверждения от пользователя перед реализацией.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектурная дискуссия — IoT телеметрия и хранение данных
|
||||||
|
|
||||||
|
### Контекст разговора
|
||||||
|
|
||||||
|
Пользователь задал вопрос: "куда пишутся данные с IoT датчиков?"
|
||||||
|
|
||||||
|
Выяснилось что сейчас данные теряются — function pod получает событие но никуда не сохраняет. Это нормально для serverless (пользователь сам решает), но для IoT платформы нужно автоматическое хранение.
|
||||||
|
|
||||||
|
### Анализ сценариев использования
|
||||||
|
|
||||||
|
Реалистичные клиенты для Nubes (облачный провайдер СНГ, малый/средний бизнес):
|
||||||
|
1. Мониторинг объектов (склады, серверные, торговые точки) — температура, влажность, протечка
|
||||||
|
2. Умные счётчики / ЖКХ — снятие показаний без выезда
|
||||||
|
3. Небольшое производство / агро — теплицы, мини-заводы
|
||||||
|
|
||||||
|
Общий паттерн для всех: датчик → данные в БД → алерт если порог → график
|
||||||
|
|
||||||
|
### Решение по хранению данных
|
||||||
|
|
||||||
|
**Вопрос**: один большой Postgres или отдельный на каждого?
|
||||||
|
**Ответ**: один Postgres инстанс, но отдельная DATABASE на каждого tenant.
|
||||||
|
|
||||||
|
Причины:
|
||||||
|
- Вариант со одной таблицей + tenant_id — изоляция программная, ошибка в коде = утечка
|
||||||
|
- Отдельная DATABASE — физическая изоляция, разные connection string, разные пароли
|
||||||
|
- Клиент B не может подключиться к DATABASE клиента A даже при баге в коде платформы
|
||||||
|
|
||||||
|
Структура:
|
||||||
|
```
|
||||||
|
Postgres инстанс
|
||||||
|
├── sless_platform DB — системные таблицы (tenants, invocations)
|
||||||
|
├── tenant_abc DB — только данные клиента A
|
||||||
|
└── tenant_def DB — только данные клиента B
|
||||||
|
```
|
||||||
|
|
||||||
|
### Решение по доступу клиента
|
||||||
|
|
||||||
|
**Вопрос**: давать клиенту прямой доступ к Postgres?
|
||||||
|
**Ответ**: нет. Только через REST API платформы.
|
||||||
|
|
||||||
|
Причины:
|
||||||
|
- Postgres внутри кластера, снаружи не торчит (security)
|
||||||
|
- Единый endpoint `iot.kube5s.ru`
|
||||||
|
- Легко добавить rate limit, биллинг, кеш
|
||||||
|
- Клиент не зависит от деталей реализации хранилища
|
||||||
|
|
||||||
|
API:
|
||||||
|
```
|
||||||
|
GET /v1/namespaces/{ns}/iot/telemetry?device=X&from=T&to=T
|
||||||
|
GET /v1/namespaces/{ns}/iot/devices/{id}/last
|
||||||
|
```
|
||||||
|
|
||||||
|
### Решение по schema.sql
|
||||||
|
|
||||||
|
Клиент может положить `schema.sql` рядом с функцией. При деплое платформа выполняет его в БД tenant'а.
|
||||||
|
Это даёт низкий порог входа — клиент не шарит в Python, но может написать SQL по шаблону.
|
||||||
|
|
||||||
|
### Ключевое архитектурное решение — разделение операторов
|
||||||
|
|
||||||
|
**Решение**: sless-operator и iot-operator — ОТДЕЛЬНЫЕ компоненты.
|
||||||
|
Пока в одном кластере, но сделать так чтобы могли быть в разных.
|
||||||
|
|
||||||
|
**Namespace layout:**
|
||||||
|
```
|
||||||
|
namespace: sless — платформа sless (operator, event-dispatcher, RabbitMQ, Postgres invocations)
|
||||||
|
namespace: sless-{hash} — tenant функции (function pods)
|
||||||
|
namespace: iot — платформа IoT (iot-operator, EMQX, Postgres telemetry)
|
||||||
|
namespace: iot-{hash} — tenant IoT (IoTDevice CRDs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Связь**:
|
||||||
|
- Общий идентификатор tenant: `{hash}` одинаковый в обоих namespace
|
||||||
|
- MQTT событие → RabbitMQ в sless → function pod в sless-{hash}
|
||||||
|
- IoT operator НЕ импортирует пакеты sless-operator (loose coupling)
|
||||||
|
- Общение только через k8s API и RabbitMQ
|
||||||
|
|
||||||
|
**Postgres**:
|
||||||
|
- sless имеет свой Postgres (invocations)
|
||||||
|
- iot имеет свой Postgres (telemetry per tenant)
|
||||||
|
- Разные StatefulSet, разные PVC
|
||||||
|
|
||||||
|
### Что делает пользователь
|
||||||
|
|
||||||
|
Клиент:
|
||||||
|
1. Подключает устройство → данные автоматически пишутся в его `iot_telemetry`
|
||||||
|
2. Пишет функцию которая реагирует на события
|
||||||
|
3. Функция получает `DB_DSN` в env var (автоматически из Secret)
|
||||||
|
4. Может делать SELECT/INSERT в свою БД через обычный SQL в коде функции
|
||||||
|
5. Может читать телеметрию через REST API
|
||||||
|
|
||||||
|
### Plan — следующие шаги (этап IoT Postgres)
|
||||||
|
|
||||||
|
1. Поднять Postgres StatefulSet в namespace `iot`
|
||||||
|
2. В iot-operator при создании IoTDevice namespace → `CREATE USER`, `CREATE DATABASE`, `CREATE TABLE iot_telemetry`, `CREATE TABLE iot_devices`
|
||||||
|
3. Credentials → k8s Secret `iot-tenant-{ns}-pg`
|
||||||
|
4. В iot-mqtt-bridge при получении MQTT сообщения → INSERT в tenant БД
|
||||||
|
5. REST API endpoint для чтения телеметрии
|
||||||
|
6. При деплое function → прокинуть `DB_DSN` в env var из Secret
|
||||||
|
7. При деплое function → если есть `schema.sql` → выполнить в tenant БД
|
||||||
|
|
||||||
|
### Технические решения
|
||||||
|
|
||||||
|
- Postgres: `postgres:16-alpine` StatefulSet с PVC 10Gi в namespace `iot`
|
||||||
|
- Connection pool: pgxpool (pgx v5) per-tenant, lazy init, max 5 conn per tenant
|
||||||
|
- Таблица telemetry: `(id bigserial, device_id text, ts timestamptz default now(), payload jsonb)`
|
||||||
|
- Индекс: `(device_id, ts DESC)` для быстрых запросов по устройству за период
|
||||||
|
- Retention: пока без TTL, добавить позже через pg_partman или cron job
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user