Компоненты: - iot-operator: controller-manager (IoTDevice CRD) + REST API (порт 9090) - mqtt-bridge: MQTT (EMQX) → Kafka bridge - kafka-consumer: Kafka → Postgres pipeline Модуль: gitea.services.ngcloud.ru/Nail/IoT Все 3 бинарника собираются, import paths адаптированы.
10 KiB
IoT MVP — Инженерная документация деплоя
Создано: 2026-04-04
Ветка: Ioter
Автор: GitHub Copilot (Claude Sonnet 4.6)
Архитектура IoT стека
IoT Device (физическое)
│ MQTT CONNECT (username="{ns}_{deviceId}", password=hex)
▼
EMQX 5.5.1 (sless/emqx)
│ HTTP POST /internal/mqtt/auth → sless-operator:9090
│ (auth backend: проверяет Secret iot-{deviceId} в k8s)
▼
│ MQTT PUBLISH → topic: "{ns}/telemetry/{deviceId}"
▼
iot-mqtt-bridge (sless/iot-mqtt-bridge)
│ paho.mqtt.golang, подписка на "+/telemetry/+"
│ parse topic → namespace из первого сегмента
▼
RabbitMQ (sless/rabbitmq)
│ queue: "iot.{namespace}.telemetry"
▼
event-dispatcher (sless/event-dispatcher)
│ Trigger type=event, queue=iot.{namespace}.telemetry
▼
Serverless Function (пользовательский handler)
Компоненты
1. CRD IoTDevice
Расположение: iot/api/v1alpha1/device_types.go
API group: iot.kube5s.ru/v1alpha1
Манифест: iot/config/crd/bases/iot.kube5s.ru_iotdevices.yaml
Поля Spec:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
deviceId |
string | да | Идентификатор устройства. Pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$ |
enabled |
bool | нет | Активно ли устройство (default: true) |
metadata |
map[string]string | нет | Произвольные метаданные (модель, локация) |
Поля Status:
| Поле | Описание |
|---|---|
phase |
Active / Disabled / Pending / Error |
mqttUsername |
{namespace}_{deviceId} |
secretName |
Имя k8s Secret с credentials |
topicPrefix |
{namespace}/ |
message |
Сообщение об ошибке если phase=Error |
2. IoT Controller
Файл: iot/controllers/iotdevice_controller.go
Логика Reconcile:
IoTDevice CREATE/UPDATE
1. Добавить finalizer "iot.kube5s.ru/device-cleanup"
2. Если Secret iot-{deviceId} не существует:
- Сгенерировать пароль: crypto/rand 32 bytes → hex (64 символа)
- OwnerReference → Secret удаляется каскадно при удалении IoTDevice
- Secret keys: mqtt-username, mqtt-password, device-id
3. Обновить Status: phase=Active, mqttUsername, secretName, topicPrefix
4. Если enabled=false → phase=Disabled
IoTDevice DELETE
1. Проверить finalizer
2. Secret удаляется каскадно (OwnerReference)
3. Убрать finalizer → k8s завершает удаление
3. IoT REST API
Файл: internal/api/handler/iot_device_handler.go
| Endpoint | Auth | Описание |
|---|---|---|
POST /internal/mqtt/auth |
Нет (internal) | MQTT auth backend для EMQX |
POST /v1/namespaces/{ns}/iot/devices |
JWT | Создать IoTDevice |
GET /v1/namespaces/{ns}/iot/devices |
JWT | Список (без паролей) |
GET /v1/namespaces/{ns}/iot/devices/{name} |
JWT | Получить (включая mqtt_password из Secret) |
DELETE /v1/namespaces/{ns}/iot/devices/{name} |
JWT | Удалить |
PATCH /v1/namespaces/{ns}/iot/devices/{name} |
JWT | Обновить enabled |
MQTT Auth endpoint:
- Всегда HTTP 200 (EMQX игнорирует non-200)
- Парсит
username→{namespace}_{deviceId}(разделитель первый_) - Ищет k8s Secret
iot-{deviceId}в namespace crypto/subtle.ConstantTimeCompareдля защиты от timing attack
4. EMQX 5.5.1
Манифест: deployments/k8s/emqx.yaml
Конфиг: HOCON emqx.conf, монтируется как ConfigMap volume
Критически важные поля (без них EMQX 5.x не стартует):
node {
name = "emqx@127.0.0.1" # Обязательно для single-node
cookie = "..." # Erlang cluster cookie (любая строка для single-node)
data_dir = "/opt/emqx/data" # Директория данных Mnesia
}
⚠️ EMQX 5.x: поля
node.cookieиnode.data_dir— обязательные (mandatory), в отличие от 4.x где были значения по умолчанию. При обновлении ConfigMap нуженkubectl rollout restart— Deployment не перезапускается автоматически.
Auth backend:
authentication = [{
mechanism = password_based
backend = http
method = post
url = "http://sless-operator.sless.svc:9090/internal/mqtt/auth"
}]
5. iot-mqtt-bridge
Код: iot/cmd/mqtt-bridge/main.go
Манифест: deployments/k8s/iot-mqtt-bridge.yaml
Образ: тот же что и оператор (sless-operator:v0.1.50), бинарь /iot-mqtt-bridge
Логика:
- Подключиться к EMQX как MQTT клиент (credentials из Secret
iot-bridge-credentials) - Подписаться на
+/telemetry/+(все namespace, все устройства) - При получении: извлечь namespace из topic[0], publish в RabbitMQ
iot.{namespace}.telemetry - Reconnect loop при обрыве соединения
Envelope в RabbitMQ:
{
"namespace": "sless-user123",
"device_id": "sensor-01",
"topic": "sless-user123/telemetry/sensor-01",
"payload": "<base64 of raw MQTT payload>",
"received_at": "2026-04-04T07:19:30Z"
}
6. Terraform Provider
Файл: terraform/provider/internal/resources/iot_device_resource.go
Ресурс: sless_iot_device
Версия провайдера: 0.1.2
resource "sless_iot_device" "temperature_sensor" {
name = "temp-sensor-01"
device_id = "temp-sensor-01"
enabled = true
metadata = {
model = "DHT22"
location = "Warehouse A"
}
}
output "mqtt_password" {
value = sless_iot_device.temperature_sensor.mqtt_password
sensitive = true
}
Процедура первого деплоя
Предварительные условия
- Кластер с namespace
sless - sless-operator запущен (или будет запущен в шаге 3)
- RabbitMQ доступен в кластере
Шаги
1. Применить CRD (один раз, cluster-wide)
kubectl apply -f iot/config/crd/bases/iot.kube5s.ru_iotdevices.yaml
2. Обновить RBAC (добавить права на iot.kube5s.ru)
kubectl apply -f deployments/k8s/rbac.yaml
3. Применить EMQX
kubectl apply -f deployments/k8s/emqx.yaml
kubectl rollout status deployment/emqx -n sless
4. Применить оператор (с IoT поддержкой)
kubectl apply -f deployments/k8s/operator.yaml
kubectl rollout status deployment/sless-operator -n sless
5. Bootstrap credentials для mqtt-bridge
Создать системное IoTDevice устройство для bridge:
TOKEN=$(kubectl get secret sless-operator-secret -n sless \
-o jsonpath="{.data.SLESS_API_TOKEN}" | base64 -d)
# Создать IoTDevice
curl -X POST https://sless.kube5s.ru/v1/namespaces/sless/iot/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"iot-bridge","device_id":"iot-bridge","enabled":true}'
# Подождать 5с пока контроллер создаст Secret
sleep 5
# Получить credentials
CREDS=$(curl -s https://sless.kube5s.ru/v1/namespaces/sless/iot/devices/iot-bridge \
-H "Authorization: Bearer $TOKEN")
MQTT_USER=$(echo $CREDS | jq -r .mqtt_username)
MQTT_PASS=$(echo $CREDS | jq -r .mqtt_password)
# Создать Secret для bridge Deployment
kubectl create secret generic iot-bridge-credentials -n sless \
--from-literal=MQTT_USERNAME="$MQTT_USER" \
--from-literal=MQTT_PASSWORD="$MQTT_PASS"
6. Применить mqtt-bridge
kubectl apply -f deployments/k8s/iot-mqtt-bridge.yaml
kubectl rollout status deployment/iot-mqtt-bridge -n sless
Ожидаемый результат
emqx-xxx 1/1 Running
iot-mqtt-bridge-xxx 1/1 Running
sless-operator-xxx 1/1 Running
Известные ошибки и решения
EMQX CrashLoopBackOff: required_field node.cookie/node.data_dir
Симптом: escript: exception throw: {emqx_conf_schema, [{kind=>validation_error, path=>"node.cookie", reason=>required_field}]}
Причина: EMQX 5.x требует явного задания node { cookie, data_dir } в конфиге.
Решение: Добавить в emqx.conf:
node {
name = "emqx@127.0.0.1"
cookie = "your-cookie-string"
data_dir = "/opt/emqx/data"
}
После kubectl apply — сделать kubectl rollout restart deployment/emqx -n sless.
RBAC forbidden: iotdevices.iot.kube5s.ru
Симптом: {"error":"iotdevices.iot.kube5s.ru is forbidden: User \"system:serviceaccount:sless:sless-operator\" cannot create resource"}
Причина: ClusterRole sless-operator не включает API group iot.kube5s.ru.
Решение: Добавить в deployments/k8s/rbac.yaml и применить:
- apiGroups: ["iot.kube5s.ru"]
resources: ["iotdevices"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["iot.kube5s.ru"]
resources: ["iotdevices/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["iot.kube5s.ru"]
resources: ["iotdevices/finalizers"]
verbs: ["update"]
mqtt-bridge: multiple restarts при старте
Симптом: iot-mqtt-bridge RESTARTS=3
Причина: bridge пытается подключиться к EMQX который ещё не готов. Нормальное поведение.
Решение: Bridge имеет reconnect loop — после старта EMQX подключение восстанавливается автоматически. Ничего делать не нужно.
Версии образов
| Версия | Дата | Изменения |
|---|---|---|
| v0.1.50 | 2026-04-04 | IoT controller + IoT API + iot-mqtt-bridge бинарь |
| v0.1.49 | ранее | До IoT |