6.9 KiB
Решение: 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
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
План реализации
- StatefulSet Postgres в namespace
iot - iot-operator: provisioning при создании IoTDevice namespace
- CREATE USER tenant_{ns} PASSWORD '{uuid}'
- CREATE DATABASE tenant_{ns} OWNER tenant_{ns}
- CREATE TABLE iot_telemetry + индекс
- iot-mqtt-bridge: INSERT telemetry при получении MQTT сообщения
- iot-operator: REST API
/v1/namespaces/{ns}/iot/telemetry - sless-operator: при деплое function → прокинуть DB_DSN + выполнить schema.sql