141 lines
11 KiB
Markdown
141 lines
11 KiB
Markdown
# Оценка готовности IoT к production (2026-10-05)
|
||
|
||
> Три взгляда на один репозиторий: анализ A, анализ B и моё мнение.
|
||
> Все три — только чтением кода и документации, без сборки и запуска тестов
|
||
> (правила репо: команды — только через SSH на ВМ; сборку/прогоны не выполняли).
|
||
|
||
---
|
||
|
||
## 1. Анализ A (первый) — итог 5/10
|
||
|
||
**Вердикт:** цельный MVP, но по коду не считать production-ready для
|
||
надёжной multi-tenant эксплуатации.
|
||
|
||
### Блокеры
|
||
- **Не проверяется принадлежность namespace пользователю.** Middleware
|
||
проверяет JWT, но не передаёт `sub` обработчикам для сравнения с namespace
|
||
из URL. CRUD и выдача пароля устройства берут namespace из запроса.
|
||
Валидный токен не ограничивает пользователя его tenant-ом:
|
||
`auth.go:20`, `router.go:54`, `devices.go:179`. `exp` фактически необязателен
|
||
(`auth.go:120`).
|
||
- **Путь потери уже принятых сообщений.** Flusher заменяет буфер на пустой
|
||
ДО записи в PG, при ошибке только логирует. Consumer при этом удаляет
|
||
сообщения из SQS после добавления в буфер: `iot_telemetry_store.go:129`,
|
||
`:135`, `consumer.go:81`.
|
||
- **Health зелёный при остановившемся pipeline.** Ошибки запуска bridge/consumer
|
||
только логируются; горутина завершается, а `/health` всегда отдаёт `ok`:
|
||
`main.go:106`, `router.go:74`.
|
||
- **Доставка допускает потери и дубли.** Переполненная RAM-очередь bridge
|
||
сбрасывает сообщения; исчерпание SQS-ретраев заканчивается drop. При
|
||
at-least-once нет идемпотентного ключа против дублей: `sender.go:70`, `:112`.
|
||
|
||
### Что хорошо
|
||
Компоненты сведены в понятный единый сервис; есть MQTT-reconnect с повторной
|
||
подпиской, SQS-ретраи, graceful shutdown, разделение хранения устройств и
|
||
телеметрии, широкий набор нагрузочных сценариев.
|
||
|
||
### Чего не хватает до уверенного релиза
|
||
- Автоматических Go-тестов (`*_test.go` не найдено) и CI.
|
||
- Согласованности доков с кодом: гайд деплоя — `iot-service:v0.1.2`, Makefile —
|
||
`v0.1.13`; loadtest README описывает уже исправленную блокировку
|
||
MQTT-обработчика и visibility 30с, код задаёт 120с:
|
||
`deployment-nubes-production.md:3`, `Makefile:4`, `README.md:85`, `config.go:114`.
|
||
|
||
---
|
||
|
||
## 2. Анализ B (второй) — итог ~60–65%
|
||
|
||
**Вердикт:** рабочий MVP / технический прототип, не продакшн-готовый сервис.
|
||
|
||
### Что хорошо
|
||
- Конвейер устройство → EMQX → мост → SQS → consumer → PG (per-tenant)
|
||
работает end-to-end; подтверждено HISTORY и нагрузочными прогонами.
|
||
- Прогоны: baseline 2390/2390 без потерь/дублей; ACL-нарушения отсекаются;
|
||
reconnect-storm прошёл чисто.
|
||
- Код небольшой (~2,4 тыс. строк в монолите), аккуратный: graceful shutdown,
|
||
асинхронный SQS-диспатчер с ретраями, batch-insert, backoff, constant-time
|
||
сравнение паролей.
|
||
- Документация подробная: деплой без kubectl, интеграция с SQS, лог ревью и
|
||
фиксов, 10 нагрузочных сценариев.
|
||
- Auth и ACL в EMQX работают напрямую через PostgreSQL, без зависимости от монолита.
|
||
|
||
### Что мешает назвать готовым
|
||
1. **Нет ни одного Go-теста и нет CI.** Только Python-нагрузочные скрипты;
|
||
в `.github/` одни правила.
|
||
2. **Изоляция тенантов слабая.** JWT проверяет только `sub` и `exp`; namespace
|
||
из URL не сверяется с токеном. Без `JWT_HMAC_SECRET` подпись не проверяется.
|
||
Любой «валидный» токен читает устройства и пароли любого namespace.
|
||
3. **Пароли устройств в БД открытым текстом** и отдаются через GET. EMQX —
|
||
`password_hash_algorithm = plain`.
|
||
4. **`AUTH_TEST_MODE=true`** (любой Bearer) был включён на проде для диагностики.
|
||
HISTORY §38 — «вернуть false»; закрытия не видно.
|
||
5. **`/internal/mqtt/auth` и `/internal/mqtt/acl`** открыты на публичном порту
|
||
без авторизации; дублируют PG-auth в EMQX и уже не нужны. ACL выдаёт права
|
||
моста любому клиенту с `clientid`, совпадающим с мостовым.
|
||
6. **Нерешённая платформенная проблема:** плавающий обрыв ответов >~15–64 КБ и
|
||
WebSocket-обрывы ~150с; стабильного воспроизведения нет, разбор с DevOps
|
||
идёт. В коде обходится пагинацией.
|
||
7. **Отложено:** whitelist тенантских БД (любой namespace создаёт базу), DLQ,
|
||
soak 24ч.
|
||
8. **Легаси не вычищено:** `cmd/iot-operator`, `controllers/`, `internal/api/`,
|
||
`cmd/mqtt-bridge`, `cmd/sqs-consumer`; в `go.mod` 12 строк k8s/controller-runtime;
|
||
бинарники в `bin/` и `ws-probe/ws-probe`. `LEGACY.md` обещает убрать после
|
||
переписывания хендлеров, а оно сделано.
|
||
9. **Дрейф конфигурации:** `cookie = "iot-emqx-cookie-mvp"` захардкожен в
|
||
`emqx.conf`; параметры EMQX задаются только через env в deck-UI.
|
||
10. **Нет TLS-верификации** к PG (`verify_none`). Нет rate limiting и метрик
|
||
(только `/health` и admin-статистика).
|
||
|
||
### Минимум для продакшна
|
||
- привязать namespace к JWT, сделать `JWT_HMAC_SECRET` обязательным;
|
||
- убедиться, что `AUTH_TEST_MODE` выключен;
|
||
- хешировать пароли устройств или не отдавать повторно;
|
||
- удалить `/internal/mqtt/*` и легаси;
|
||
- добавить тесты и CI;
|
||
- закрыть вопрос обрывов на платформе.
|
||
|
||
---
|
||
|
||
## 3. Моё мнение (перепроверено по коду)
|
||
|
||
Оба анализа компетентны и в главном совпадают. Расхождение 5/10 vs 60–65% —
|
||
не в фактах, а в весе, который они дают одним и тем же находкам. Спорные места
|
||
я перепроверил сам — оба правы по существу.
|
||
|
||
### Подтверждается кодом (не предположение)
|
||
1. **Изоляция тенантов слабая** — верно. `middleware/auth.go` проверяет JWT
|
||
только на `sub`+`exp`; `sub` не сверяется с `{namespace}` (claims не
|
||
передаются в обработчики, `router.go` не извлекает `sub`). `exp` необязателен
|
||
(`if claims.Exp > 0`), без `JWT_HMAC_SECRET` подпись не проверяется.
|
||
2. **Путь потери принятых сообщений** — верно. `processTelemetry` → `InsertTelemetry`
|
||
кладёт в in-memory буфер и возвращает nil, после чего consumer безусловно
|
||
удаляет сообщение из SQS. `flushAll` подменяет буфер на пустой ДО записи в PG
|
||
и при ошибке только логирует. Данные могут молча пропасть.
|
||
3. **`/health` зелёный при упавшем pipeline** — верно. `main.go` ошибки
|
||
bridge/consumer только логирует; горутина завершается, `healthHandler` всегда
|
||
отдаёт `ok`.
|
||
4. **Нет Go-тестов и CI** — верно. `*_test.go` отсутствует, `.github/` — правила.
|
||
5. **`AUTH_TEST_MODE` и пароли plaintext** — верно и задокументировано
|
||
(`password_hash_algorithm=plain`, `AUTH_TEST_MODE=true` для DevOps, §38).
|
||
6. **Дрейф доков** — верно: гайд `v0.1.2` vs Makefile `v0.1.13`; loadtest README
|
||
30с vs код 120с. Анализ A зафиксировал точнее.
|
||
|
||
### Где анализ B точнее/дополняет
|
||
- Явно оговорил, что сборку/тесты не запускал (ограничение правил — SSH).
|
||
- Глубже разобрал открытые `/internal/mqtt/*`, TLS `verify_none`, захардкоженный
|
||
`cookie`, невычищенное легаси и k8s-зависимости в `go.mod`.
|
||
|
||
### Где я бы поправил обоих
|
||
- **Проценты условны.** Оба дают один вывод «MVP/прототип, не production-ready»;
|
||
цифры — риторика, а не измерение.
|
||
- **Приоритет блокеров:** главные технические — потеря сообщений и tenant-граница.
|
||
Plaintext-пароли и unauthed `/internal/mqtt/*` — важные, но второго эшелона
|
||
(периметр платформы + EMQX уже на PG-auth напрямую снижают остроту).
|
||
|
||
### Итог
|
||
**Рабочий MVP, пригодный для демо/пилота, но не production-ready.** Главные
|
||
блокеры: слабая tenant-изоляция, гарантированная потеря сообщений при сбое
|
||
flush, «слепой» health, отсутствие Go-тестов/CI и не закрытая платформенная
|
||
проблема wss ~150с / MTU. Моя оценка — между A и B, ближе к
|
||
«функционально готов, эксплуатационно — нет».
|