doc: обновлена документация — дополнения к архитектуре, деплою, прогрессу

- architecture/overview.md: добавлена актуальная схема (SQS, managed PG)
- deployment.md: добавлена процедура деплоя в iot-naeel
- progress.md: добавлен блок 2026-04-12 (Kafka→SQS, деплой)
- api/endpoints.md: добавлен актуальный формат /iot-admin/stats
- thinking/2026-04-12.md: дописан ход реализации и деплоя
- copilot-instructions.md: добавлено правило «документацию не стирать»

Старая документация (Kafka, RabbitMQ) сохранена как база знаний.
This commit is contained in:
Naeel
2026-04-12 16:16:46 +03:00
parent 150146edba
commit d4f7c5abc8
6 changed files with 332 additions and 0 deletions
+13
View File
@@ -27,6 +27,19 @@
---
## 🚫 ДОКУМЕНТАЦИЮ НЕ СТИРАТЬ — ТОЛЬКО ДОПОЛНЯТЬ
**СТРОГОЕ ПРАВИЛО: любую документацию в `doc/` ЗАПРЕЩЕНО удалять или перезаписывать.**
- Старый текст — это база знаний. Даже если он устарел (Kafka, RabbitMQ, и т.д.) — он остаётся.
- Новую информацию **дописывать** в конец файла или добавлять новые секции с датой.
- Если архитектура изменилась — добавить секцию `## Актуальная архитектура (ГГГГ-ММ-ДД)`, НЕ удаляя старую.
- Если решение отменено — не стирать, а добавить пометку `> ⚠️ Отменено ГГГГ-ММ-ДД: причина`.
Это касается ВСЕХ файлов в `doc/`, включая `progress.md`, `decisions/`, `thinking/`, `api/`, `architecture/`.
---
## ⚠️ ВЫПОЛНЕНИЕ КОМАНД — ТОЛЬКО НА УДАЛЁННОЙ МАШИНЕ
- Все команды (git, go, docker, kubectl, make и т.д.) выполнять **ТОЛЬКО на удалённой машине** через SSH.
+22
View File
@@ -30,3 +30,25 @@
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/iot-admin/stats` | JSON статистика (PG, Kafka lag, pods) |
---
## Обновление 2026-04-12: admin stats → SQS
> Kafka lag заменён на SQS queue stats. Endpoint тот же, формат ответа изменён.
### `/iot-admin/stats` — актуальный формат ответа
```json
{
"sqs": {
"queue_url": "http://us-east-1.goaws.com:4100/t-96afe7e9f781f6ca/iot-telemetry",
"approximate_messages": "0",
"approximate_messages_not_visible": "0"
},
"sqs_consumer_pods": [...],
"pg_tenants": [...]
}
```
Вместо `kafka_lag` теперь `sqs.approximate_messages` — количество сообщений в очереди, ожидающих обработки.
+84
View File
@@ -64,3 +64,87 @@ REST API (iot-operator:9090)
- `naeel/iot-operator` (Docker Hub)
- Все 3 бинарника в одном образе
- `command: ["/iot-operator"]` или `["/mqtt-bridge"]` или `["/kafka-consumer"]`
---
## Актуальная архитектура (2026-04-12)
> Kafka заменён на shared-SQS. Postgres заменён на managed. Новый кластер iot-naeel.
### Текущая схема
```
IoT Device (MQTT CONNECT)
│ username="{ns}_{deviceId}", password=hex
EMQX 5.5.1 (sless/emqx)
│ POST /internal/mqtt/auth → iot-operator:9090
│ POST /internal/mqtt/acl → iot-operator:9090
│ MQTT PUBLISH → topic: "{ns}/telemetry/{deviceId}"
iot-mqtt-bridge (cmd/mqtt-bridge)
│ подписка на "+/telemetry/+"
│ → AWS SDK SQS SendMessage → shared-SQS (https://qu.kube5s.ru)
│ очередь: "iot-telemetry" (tenant: iot-service, id: t-96afe7e9f781f6ca)
shared-SQS (namespace shared-sqs)
│ AWS SQS-совместимый, multi-tenant
iot-sqs-consumer (cmd/sqs-consumer)
│ ReceiveMessage (WaitTimeSeconds=20, long polling)
│ DeleteMessage после успешной записи (at-least-once)
│ → per-tenant Postgres DB
Managed PostgreSQL 17 (namespace dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5)
│ Host: postgresqlk8s-master.dc5db45d-....svc.cluster.local
│ User: super, DB: sqsdb
│ Per-tenant DB: tenant_{namespace}, Table: iot_telemetry
REST API (iot-operator:9090)
│ GET /v1/namespaces/{ns}/iot/telemetry
Пользователь (Terraform / UI Console)
│ wss://iot.kube5s.ru/mqtt (WebSocket через Ingress)
│ https://iot.kube5s.ru/console (UI)
```
### Компоненты (актуальные)
| Компонент | Бинарник | Порт | Назначение |
|-----------|----------|------|------------|
| iot-operator | cmd/iot-operator | :9090 (API), :8080 (metrics), :8081 (health) | Controller-manager + REST API |
| mqtt-bridge | cmd/mqtt-bridge | — | MQTT → shared-SQS bridge |
| sqs-consumer | cmd/sqs-consumer | — | shared-SQS → Postgres pipeline |
### Инфраструктура
| Сервис | Тип | Namespace | Описание |
|--------|-----|-----------|----------|
| EMQX 5.5.1 | В кластере | sless | MQTT-брокер, HTTP auth/acl → iot-operator |
| shared-SQS | В кластере | shared-sqs | AWS SQS-совместимая очередь, endpoint https://qu.kube5s.ru |
| PostgreSQL 17 | Managed (оператор) | dc5db45d-... | per-tenant DB, тот же инстанс что и SQS billing |
| cert-manager | В кластере | cert-manager | TLS сертификаты для iot.kube5s.ru |
| nginx-ingress | В кластере | ingress | WSS/HTTPS проксирование |
### Секреты в namespace sless
| Secret | Ключи | Источник |
|--------|-------|----------|
| iot-sqs-credentials | SQS_ENDPOINT, SQS_ACCESS_KEY, SQS_SECRET_KEY | shared-SQS tenant iot-service |
| iot-postgres-secret | POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, IOT_PG_DSN | Managed PG |
| iot-bridge-credentials | MQTT_USERNAME, MQTT_PASSWORD | Для mqtt-bridge → EMQX |
### Docker образ (актуальный)
- `naeel/iot-operator:v0.2.0` (Docker Hub)
- Все 3 бинарника в одном образе
- `command: ["/iot-operator"]` или `["/mqtt-bridge"]` или `["/sqs-consumer"]`
- Базовый: `gcr.io/distroless/static:nonroot`
### Кластер
- Имя: `iot-naeel`
- API: `https://185.247.187.149:6443`
- Ingress IP: `185.247.187.151`
- DNS: `iot.kube5s.ru → 185.247.187.147`
+98
View File
@@ -304,3 +304,101 @@ node {
|--------|------|-----------|
| 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) |
+50
View File
@@ -35,3 +35,53 @@
- [ ] Деплой в кластер
- [ ] E2E тест: создание устройства → MQTT → Kafka → Postgres → API
- [ ] Убрать IoT-код из sless (опционально, после подтверждения что всё работает)
---
## 2026-04-12: Замена Kafka → shared-SQS + деплой в новый кластер
### Выполнено
- [x] Создана ветка `feature/replace-kafka-with-sqs`
- [x] Документировано решение: `doc/decisions/2026-04-12-replace-kafka-with-sqs.md`
- [x] **mqtt-bridge** (`cmd/mqtt-bridge/main.go`): Kafka Writer → AWS SDK SQS SendMessage
- [x] **sqs-consumer** (`cmd/sqs-consumer/main.go`): новый — заменяет kafka-consumer, SQS ReceiveMessage → Postgres
- [x] **kafka-consumer** (`cmd/kafka-consumer/`): удалён из кода
- [x] **admin stats** (`internal/api/handler/iot_admin_stats_handler.go`): Kafka lag → SQS GetQueueAttributes
- [x] **handler.go**: убрано поле KafkaBrokers
- [x] **iot-operator/main.go**: убрана передача KAFKA_BROKERS
- [x] go.mod: убран `segmentio/kafka-go`, добавлен `aws-sdk-go-v2` (v1.41.5 + sqs v1.42.25)
- [x] Dockerfile: kafka-consumer → sqs-consumer
- [x] Makefile: build-consumer → sqs-consumer
- [x] .gitignore: исправлен баг (паттерны без `/` игнорировали `cmd/` директории)
- [x] `go build ./...` проходит на ВМ
- [x] Коммит и пуш в ветку
### Деплой в кластер iot-naeel (новый кластер)
- [x] shared-SQS: создан tenant `iot-service` (t-96afe7e9f781f6ca), очередь `iot-telemetry`
- [x] Namespace `sless` создан
- [x] Secret `iot-sqs-credentials` создан
- [x] Secret `iot-bridge-credentials` создан
- [x] Postgres: переключён на managed PG17 (тот же что SQS billing)
- [x] Secret `iot-postgres-secret` создан (DSN → managed PG)
- [x] Docker: собран и запушен `naeel/iot-operator:v0.2.0` в Docker Hub
- [x] Deployment YAML: обновлены image на Docker Hub, убраны imagePullSecrets
- [x] EMQX: задеплоен (emqx.yaml + emqx-ws-ingress.yaml, адаптирован из sless)
- [x] iot-operator: задеплоен (новый iot-operator.yaml с RBAC)
- [x] CRD `iotdevices.iot.kube5s.ru` установлен
- [x] iot-mqtt-bridge: задеплоен, Running
- [x] iot-sqs-consumer: задеплоен, Running — подключился к PG и SQS
- [x] cert-manager: выпускает TLS для iot.kube5s.ru
- [x] Коммит и пуш
### Что работает
- Все 4 пода Running 1/1
- iot-operator: controller стартовал, MQTT auth/acl обрабатывает запросы от EMQX
- sqs-consumer: подключился к Postgres и SQS, polling loop запущен
- mqtt-bridge: подключён к EMQX и SQS, SQS queue resolved
- EMQX: MQTT :1883, WS :8083, dashboard :18083
### Следующие шаги
- [ ] E2E тест: создание устройства → MQTT → SQS → Postgres → API
- [ ] Нагрузочный тест
- [ ] Мерж ветки в master
- [ ] Убрать IoT-код из sless (опционально)
+65
View File
@@ -57,3 +57,68 @@
**Плюс:** AWS SDK for Go — стандартная библиотека, код станет проще. shared-SQS уже живой.
**Минус:** polling latency (ReceiveMessage WaitTimeSeconds до 20s) vs Kafka push. Для IoT телеметрии — приемлемо.
---
## Продолжение — GitHub Copilot (Claude Opus 4.6)
### Реализация замены Kafka → SQS
**Что сделано:**
1. **mqtt-bridge** — полностью переписан:
- Убран `segmentio/kafka-go`, добавлен `aws-sdk-go-v2` (sqs, config, credentials)
- `kafka.Writer``sqs.Client.SendMessage`
- При старте: `GetQueueUrl` для резолва URL очереди "iot-telemetry"
- Env vars: `SQS_ENDPOINT`, `SQS_ACCESS_KEY`, `SQS_SECRET_KEY`, `SQS_QUEUE_NAME`, `SQS_REGION`
- Формат сообщения (MessageBody JSON) не изменился: `{namespace, device_id, topic, payload, received_at}`
2. **sqs-consumer** — создан с нуля (заменяет kafka-consumer):
- Long polling: `ReceiveMessage(WaitTimeSeconds=20)` — минимизирует запросы при пустой очереди
- At-least-once: `DeleteMessage` только после успешной записи в Postgres
- Использует `iotpg.IoTPostgresStore` — тот же механизм per-tenant DB что и kafka-consumer
3. **admin stats handler** — переписан:
- Вместо Kafka consumer lag → `GetQueueAttributes(ApproximateNumberOfMessages, ApproximateNumberOfMessagesNotVisible)`
- Pod labels для consumer: `iot-kafka-consumer``iot-sqs-consumer`
4. **Баг .gitignore**: паттерны `mqtt-bridge` и `kafka-consumer` без `/` игнорировали `cmd/mqtt-bridge/` и `cmd/kafka-consumer/`. Исправлено добавлением `/` префикса.
5. **Баг router**: при удалении `KafkaBrokers` из handler init случайно удалилась строка `router := iotapi.NewRouter(h, log)`. Восстановлена.
### Деплой в новый кластер iot-naeel
**Обнаружения при деплое:**
1. Кластер **полностью новый** — namespace `sless` не существовал, ничего не задеплоено.
2. **Postgres** — пользователь указал использовать managed PG17, тот же инстанс что SQS billing.
Credentials в `/home/naeel/terra/SQS-service/secrets/iot_pg.md`.
Самодеплоенный postgres:16-alpine из YAML заменён на DSN к managed PG.
3. **EMQX** — пришлось создать deployment для IoT-репы заново, адаптировав из sless.
Ключевое изменение: auth URL `sless-operator.sless.svc:9090``iot-operator.sless.svc:9090`.
4. **iot-operator deployment** — его не было в IoT-репе! Создан новый:
- ServiceAccount + ClusterRole (iotdevices CRD, secrets, events, namespaces, leases)
- ClusterRoleBinding
- Deployment + Service :9090
5. **kubectl токен** истекал за 24 часа — пользователь обновлял вручную.
6. **Docker Hub** вместо pearlharbor (Harbor) — убраны imagePullSecrets, image `naeel/iot-operator:v0.2.0`.
7. **shared-SQS tenant** создан через API:
- Tenant: `iot-service`, ID: `t-96afe7e9f781f6ca`
- Queue: `iot-telemetry`
- Admin token из Secret `shared-sqs-admin` в namespace `shared-sqs`
### Результат
Все 4 пода Running 1/1:
- `iot-operator` — controller работает, MQTT auth/acl обрабатывает запросы
- `emqx` — MQTT брокер, подключает IoT устройства
- `iot-mqtt-bridge` — подписан на EMQX, SQS queue resolved
- `iot-sqs-consumer` — подключён к PG и SQS, polling loop активен
TLS сертификат для `iot.kube5s.ru` выпускается cert-manager.