refactor: старые артефакты IoT перенесены в legacy/ (git mv, история сохранена); активное дерево очищено, go build OK

This commit is contained in:
“Naeel”
2026-08-16 09:48:34 +04:00
parent bf32394def
commit f0c36a11ee
30 changed files with 31 additions and 9 deletions
+406
View File
@@ -0,0 +1,406 @@
> ⛔⛔⛔ ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT (k8s-деплой). НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ. Актуальное: HISTORY/2026-08-16-session-log.md
# 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 не стартует):**
```hocon
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:**
```hocon
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`
**Логика:**
1. Подключиться к EMQX как MQTT клиент (credentials из Secret `iot-bridge-credentials`)
2. Подписаться на `+/telemetry/+` (все namespace, все устройства)
3. При получении: извлечь namespace из topic[0], publish в RabbitMQ `iot.{namespace}.telemetry`
4. Reconnect loop при обрыве соединения
**Envelope в RabbitMQ:**
```json
{
"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`
```hcl
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)**
```bash
kubectl apply -f iot/config/crd/bases/iot.kube5s.ru_iotdevices.yaml
```
**2. Обновить RBAC (добавить права на iot.kube5s.ru)**
```bash
kubectl apply -f deployments/k8s/rbac.yaml
```
**3. Применить EMQX**
```bash
kubectl apply -f deployments/k8s/emqx.yaml
kubectl rollout status deployment/emqx -n sless
```
**4. Применить оператор (с IoT поддержкой)**
```bash
kubectl apply -f deployments/k8s/operator.yaml
kubectl rollout status deployment/sless-operator -n sless
```
**5. Bootstrap credentials для mqtt-bridge**
Создать системное IoTDevice устройство для bridge:
```bash
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**
```bash
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`:
```hocon
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` и применить:
```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 |
---
## Актуальный деплой (2026-04-12)
> Кластер: iot-naeel (новый). Kafka удалён. Postgres — managed. SQS вместо Kafka.
### Кластер iot-naeel
- API: `https://185.247.187.149:6443`
- Ingress IP: `185.247.187.151`
- DNS: `iot.kube5s.ru → 185.247.187.147`
- kubeconfig: `~/.kube/config` на ВМ (токен с TTL 24ч, обновляется через auth.k8s.ngcloud.ru)
### Namespace sless — содержимое
| Ресурс | Имя | Описание |
|--------|-----|----------|
| Deployment | emqx | MQTT-брокер EMQX 5.5.1 |
| Deployment | iot-operator | Controller-manager + REST API v0.2.0 |
| Deployment | iot-mqtt-bridge | MQTT → shared-SQS bridge v0.2.0 |
| Deployment | iot-sqs-consumer | shared-SQS → Postgres consumer v0.2.0 |
| Service | emqx | :1883 (MQTT), :8083 (WS), :18083 (dashboard) |
| Service | emqx-ws | :8083 (для Ingress) |
| Service | iot-operator | :9090 (REST API) |
| Ingress | emqx-mqtt-websocket | iot.kube5s.ru → /mqtt (WS), /console (UI) |
| Secret | iot-sqs-credentials | SQS_ENDPOINT, SQS_ACCESS_KEY, SQS_SECRET_KEY |
| Secret | iot-postgres-secret | POSTGRES_USER, POSTGRES_PASSWORD, IOT_PG_DSN |
| Secret | iot-bridge-credentials | MQTT_USERNAME, MQTT_PASSWORD |
| CRD | iotdevices.iot.kube5s.ru | IoTDevice v1alpha1 |
| ServiceAccount | iot-operator | + ClusterRole + ClusterRoleBinding |
### Процедура деплоя (актуальная)
```bash
# 0. SSH на ВМ (все команды оттуда)
ssh -i ~/.ssh/id_ed25519 naeel@5.172.178.213
# 1. Сборка и push образа
cd /home/naeel/terra/IoT
docker build -t naeel/iot-operator:v0.2.0 -t naeel/iot-operator:latest .
docker push naeel/iot-operator:v0.2.0
docker push naeel/iot-operator:latest
# 2. CRD (один раз)
kubectl apply -f config/crd/bases/iot.kube5s.ru_iotdevices.yaml
# 3. Postgres Secret (содержит DSN managed PG)
kubectl apply -f deployments/k8s/iot-postgres.yaml
# 4. EMQX
kubectl apply -f deployments/k8s/emqx.yaml
kubectl apply -f deployments/k8s/emqx-ws-ingress.yaml
# 5. Operator
kubectl apply -f deployments/k8s/iot-operator.yaml
# 6. SQS credentials (уже создан через kubectl create secret)
# kubectl get secret iot-sqs-credentials -n sless
# 7. MQTT bridge credentials (уже создан)
# kubectl get secret iot-bridge-credentials -n sless
# 8. Bridge + Consumer
kubectl apply -f deployments/k8s/iot-mqtt-bridge.yaml
kubectl apply -f deployments/k8s/iot-sqs-consumer.yaml
# 9. Проверка
kubectl get pods -n sless
kubectl logs -n sless deployment/iot-operator --tail=10
kubectl logs -n sless deployment/iot-mqtt-bridge --tail=10
kubectl logs -n sless deployment/iot-sqs-consumer --tail=10
```
### Managed PostgreSQL
- Namespace: `dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5`
- Pod: `postgresqlk8s-0`
- Host: `postgresqlk8s-master.dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5.svc.cluster.local`
- User: `super`, DB: `sqsdb`, PG 17
- Тот же инстанс что использует shared-SQS для billing
- Credentials: см. `/home/naeel/terra/SQS-service/secrets/iot_pg.md`
### shared-SQS (очередь вместо Kafka)
- Namespace: `shared-sqs`
- Endpoint: `https://qu.kube5s.ru`
- Tenant ID: `t-96afe7e9f781f6ca` (iot-service)
- Queue: `iot-telemetry`
- Access Key: хранится в Secret `iot-sqs-credentials` namespace `sless`
- Admin token: хранится в Secret `shared-sqs-admin` namespace `shared-sqs`
### Версии образов (актуальные)
| Версия | Дата | Registry | Изменения |
|--------|------|----------|-----------|
| v0.2.0 | 2026-04-12 | Docker Hub naeel/iot-operator | Kafka→SQS, managed PG, новый кластер |
| v0.1.50 | 2026-04-04 | pearlharbor (Harbor) | IoT controller + API + mqtt-bridge (Kafka) |