Compare commits

6 Commits
Author SHA1 Message Date
naeel 8f9b6a9611 Уменьшено поле вывода картинки 2026-07-22 10:46:26 +04:00
naeel 54537a43cf Добавлена схема архитектуры (demo.jpg) 2026-07-22 10:39:18 +04:00
naeel 182f670b34 README: детальная схема пайплайна, описание каждого сервиса 2026-07-22 10:26:24 +04:00
naeel ce7d031068 README: убрал лишнее, прямые ссылки на репо приложений 2026-07-22 10:23:32 +04:00
naeel 5511546534 IoT Demo: Terraform-конфиг для 6 сервисов Nubes Cloud
Producer -> RabbitMQ -> Consumer -> Redis+MongoDB -> Dashboard

Подробная инструкция в README.md:
- Что заменить в locals.tf (домены, git_path, ресурсы)
- Как заполнить terraform.tfvars
- Как форкнуть репо приложений
2026-07-22 10:19:24 +04:00
naeel f86b189183 fix: remove terraform destroy — not supported for this resource 2026-07-21 10:11:01 +04:00
10 changed files with 474 additions and 1 deletions
+1 -1
View File
@@ -42,5 +42,5 @@ terraform plan # покажет что будет создано, без р
terraform apply # создать кластер (подтвердить: yes)
```
После `apply` кластер появится в ЛК → Штурвал. Удаление: `terraform destroy`.
После `apply` кластер появится в ЛК → Штурвал.
+94
View File
@@ -0,0 +1,94 @@
# IoT Demo — Nubes Cloud
Демо-стенд «Умный дом»: 6 сервисов Nubes Cloud, развёртывание через Terraform.
## Пайплайн данных
Эмуляция IoT-датчиков «Умного дома»: температура, влажность, энергопотребление.
<img src="demo.jpg" alt="Архитектура IoT Demo" width="700">
| # | Сервис | Тип Nubes | Что делает |
|---|---|---|---|
| 1 | **Producer** | Node.js (22) | Генерирует случайные события от 8 датчиков (3 типа: температура °C, влажность %, энергопотребление kW). Раз в 3 секунды отправляет JSON-событие в очередь RabbitMQ `iot-events`. |
| 2 | **RabbitMQ** | RabbitMQ | Очередь сообщений AMQP. Держит события, пока Consumer не заберёт. `durable: true` — сообщения переживают рестарт брокера. |
| 3 | **Consumer** | Node.js (22) | Читает очередь RabbitMQ. Каждое событие пишет параллельно в Redis (оперативный кэш: последние значения + счётчики + лента) и MongoDB (постоянный архив с TTL 7 дней). |
| 4 | **Redis** | Redis | Кэш для Dashboard: `iot:latest` (hash — актуальные значения датчиков), `iot:counters` (hash — счётчики по типам), `iot:recent` (zset — лента 1000 событий). |
| 5 | **MongoDB** | MongoDB | Архив всех событий в коллекции `iot_events`. TTL-индекс на 7 дней — автоочистка. |
| 6 | **Dashboard** | Node.js (22) | Веб-интерфейс. Читает только Redis, отдаёт JSON-API и HTML с графиками Chart.js (bar chart значений, doughnut счётчиков, таблица событий). Автообновление каждые 3 сек. |
Все соединения между сервисами — внутренние, через K8s Service (`.svc.cluster.local`). Наружу торчат только HTTP-эндпоинты Node.js-приложений.
## Быстрый старт
### 1. Скопировать и заполнить `terraform.tfvars`
```bash
cp terraform.tfvars.example terraform.tfvars
```
```hcl
api_token = "xxx" # API-токен из ЛК Nubes
realm = "k8s-4-sandbox-nubes-ru" # Ваш K8s-кластер
api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
```
### 2. Заменить обязательные параметры в `locals.tf`
| Параметр | Что писать |
|---|---|
| `prod_git_path` | URL вашего форка producer в Gitea |
| `cons_git_path` | URL вашего форка consumer в Gitea |
| `dash_git_path` | URL вашего форка dashboard в Gitea |
| `prod_domain` | **Уникальный** поддомен для producer |
| `cons_domain` | **Уникальный** поддомен для consumer |
| `dash_domain` | **Уникальный** поддомен для dashboard |
| `prod_resource_name` | Имя инстанса producer в кабинете Nubes |
| `cons_resource_name` | Имя инстанса consumer |
| `dash_resource_name` | Имя инстанса dashboard |
> ⚠️ Домены (поддомены `.nodejsk8s.dev.nubes.ru`) должны быть **уникальны** в рамках кластера. Занятый домен — ошибка при деплое.
### 3. Деплой
```bash
terraform init
terraform apply
```
### 4. Проверка
```bash
curl https://<prod_domain>.nodejsk8s.dev.nubes.ru # → {"events_sent": N, "errors": 0}
curl https://<cons_domain>.nodejsk8s.dev.nubes.ru # → {"consumed": N, "errors": 0}
# Dashboard: открыть https://<dash_domain>.nodejsk8s.dev.nubes.ru в браузере
```
## Репозитории приложений
Код каждого приложения — отдельный репо. Склонируйте/форкните к себе в Gitea.
| Приложение | Репо | Стек |
|---|---|---|
| Producer | [`Nail/tf-iot-producer`](https://gitea.services.ngcloud.ru/Nail/tf-iot-producer) | Node.js + amqplib |
| Consumer | [`Nail/tf-iot-consumer`](https://gitea.services.ngcloud.ru/Nail/tf-iot-consumer) | Node.js + amqplib + redis + mongodb |
| Dashboard | [`Nail/tf-iot-dashboard`](https://gitea.services.ngcloud.ru/Nail/tf-iot-dashboard) | Node.js + express + redis + Chart.js |
## Файлы
| Файл | Назначение |
|---|---|
| `main.tf` | Провайдер, переменные |
| `locals.tf` | **Все настройки** — менять здесь |
| `infra.tf` | RabbitMQ + Redis + MongoDB |
| `producer.tf` | Node.js producer |
| `consumer.tf` | Node.js consumer |
| `dashboard.tf` | Node.js dashboard |
| `terraform.tfvars.example` | Шаблон секретов |
## Примечания
- **MongoDB без пароля** — для демо. В проде настроить аутентификацию.
- **`adopt_existing_on_create = true`** — повторный `apply` подхватывает существующие инстансы.
- **`suspend_on_destroy = true`** — `destroy` не удаляет, а засыпает (suspend).
- Все внутренние соединения — K8s Service (`.svc.cluster.local`).
+65
View File
@@ -0,0 +1,65 @@
# =============================================================================
# Consumer (Node.js) — читает RabbitMQ, сохраняет в Redis + MongoDB
# Использует amqplib для AMQP-подключения к RabbitMQ
# =============================================================================
locals {
# Подключение к RabbitMQ (AMQP, порт 5672)
cons_rmq_host = nubes_rabbitmq.main.state_out_flat["internalConnect"] # K8s-хост
cons_rmq_user = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminUser"]) # логин
cons_rmq_pass = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminPass"]) # пароль
# Подключение к Redis (порт 6379)
cons_rds_host = nubes_redis.main.state_out_flat["internalConnect.master"] # K8s-хост
cons_rds_pass = nonsensitive(nubes_redis.main.vault_secrets["adminPass"]) # пароль
# Подключение к MongoDB (порт 27017) — без аутентификации (vault пустой)
cons_mgo_host = nubes_mongodb.main.state_out_flat["internalConnect"] # K8s-хост
cons_mgo_user = "admin" # дефолтный пользователь
cons_mgo_pass = "" # без пароля
}
resource "nubes_nodejs" "consumer" {
resource_name = local.cons_resource_name # имя в личном кабинете
startup_configuration = {
resource_realm = var.realm # K8s-кластер
}
cluster_configuration = {
cpu = local.cons_cpu # CPU millicores
memory = local.cons_memory # память MB
replicas = local.cons_replicas # количество подов
}
access_configuration = {
domain = local.cons_domain # поддомен для HTTP
}
app_configuration = {
version = "22" # версия Node.js
git_path = local.cons_git_path # Git-репо с кодом
health_path = "/" # health-check эндпоинт
}
git_revision = local.cons_git_revision # коммит для деплоя
operation_timeout = local.cons_timeout # таймаут
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
# Переменные окружения для K8s пода
json_env = jsonencode({
RMQ_HOST = local.cons_rmq_host # хост RabbitMQ
RMQ_PORT = "5672" # порт AMQP
RMQ_USER = local.cons_rmq_user # логин RabbitMQ
RMQ_PASS = local.cons_rmq_pass # пароль RabbitMQ
RMQ_QUEUE = local.rmq_queue # имя очереди
REDIS_HOST = local.cons_rds_host # хост Redis
REDIS_PORT = "6379" # порт Redis
REDIS_PASS = local.cons_rds_pass # пароль Redis
MONGO_URI = "mongodb://${local.cons_mgo_user}:${local.cons_mgo_pass}@${local.cons_mgo_host}:27017/iot?authSource=admin"
MONGO_DB = "iot" # имя базы MongoDB
})
# Ждать создания всех трёх сервисов
depends_on = [nubes_rabbitmq.main, nubes_redis.main, nubes_mongodb.main]
}
+47
View File
@@ -0,0 +1,47 @@
# =============================================================================
# Dashboard (Node.js + Chart.js) — читает Redis, показывает графики
# API: /api/latest, /api/counters, /api/recent
# =============================================================================
locals {
# Подключение к Redis (только чтение)
dash_rds_host = nubes_redis.main.state_out_flat["internalConnect.master"] # K8s-хост Redis
dash_rds_pass = nonsensitive(nubes_redis.main.vault_secrets["adminPass"]) # пароль Redis
}
resource "nubes_nodejs" "dashboard" {
resource_name = local.dash_resource_name # имя в личном кабинете
startup_configuration = {
resource_realm = var.realm # K8s-кластер
}
cluster_configuration = {
cpu = local.dash_cpu # CPU millicores
memory = local.dash_memory # память MB
replicas = local.dash_replicas # количество подов
}
access_configuration = {
domain = local.dash_domain # поддомен -> iotdash22.nodejsk8s.dev.nubes.ru
}
app_configuration = {
version = "22" # версия Node.js
git_path = local.dash_git_path # Git-репо с кодом
health_path = "/" # health-check
}
git_revision = local.dash_git_revision # коммит для деплоя
operation_timeout = local.dash_timeout # таймаут
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
# Переменные окружения
json_env = jsonencode({
REDIS_HOST = local.dash_rds_host # хост Redis
REDIS_PORT = "6379" # порт Redis
REDIS_PASS = local.dash_rds_pass # пароль Redis
})
depends_on = [nubes_redis.main] # ждать создания Redis
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

+84
View File
@@ -0,0 +1,84 @@
# =============================================================================
# Инфраструктурные сервисы (создаются первым прогоном apply)
# RabbitMQ (очередь) + Redis (кэш) + MongoDB (архив)
# =============================================================================
# --- RabbitMQ: очередь сообщений (сервис 93) ---
# state_out_flat["internalConnect"] = K8s-хост (service.cluster.local)
# vault_secrets["adminUser"] / ["adminPass"] = логин/пароль (прямые строки, не JSON)
resource "nubes_rabbitmq" "main" {
resource_name = local.rmq_resource_name # имя в личном кабинете
startup_configuration = { # на каком K8s-кластере развернуть
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.rmq_cpu # CPU millicores
memory = local.rmq_memory # память MB
disk = local.rmq_disk # диск GB
replicas = local.rmq_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # внутренняя сеть K8s
}
operation_timeout = local.rmq_timeout # макс. время ожидания
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
# --- Redis: кэш дашборда (сервис 91) ---
# state_out_flat["internalConnect.master"] = хост
# vault_secrets["adminPass"] = пароль (прямая строка, не JSON)
resource "nubes_redis" "main" {
resource_name = local.rds_resource_name # имя в личном кабинете (rds = ReDiS)
startup_configuration = { # на каком K8s-кластере
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.rds_cpu # CPU millicores
memory = local.rds_memory # память MB
disk = local.rds_disk # диск GB
replicas = local.rds_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # разрешить всю внутреннюю сеть K8s
slave_ip_space = "no-needed" # slave-нода не нужна
slave_access_list = jsonencode([]) # slave-нода не нужна
}
operation_timeout = local.rds_timeout # макс. время ожидания операций
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
# --- MongoDB: архив событий (сервис 92) ---
# state_out_flat["internalConnect"] = хост
# vault_secrets — пустой (нет дефолтных пользователей), подключаемся без пароля
resource "nubes_mongodb" "main" {
resource_name = local.mgo_resource_name # имя в личном кабинете (mgo = MonGO)
startup_configuration = { # на каком K8s-кластере
resource_realm = var.realm
}
cluster_configuration = { # ресурсы пода
cpu = local.mgo_cpu # CPU millicores
memory = local.mgo_memory # память MB
disk = local.mgo_disk # диск GB
replicas = local.mgo_replicas # количество нод
}
access_configuration = { # сетевой доступ
master_ip_space = "no-needed" # не резервировать внешний IP
master_access_list = jsonencode(["10.0.0.0/8"]) # разрешить всю внутреннюю сеть K8s
}
operation_timeout = local.mgo_timeout # макс. время ожидания
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
}
+61
View File
@@ -0,0 +1,61 @@
# =============================================================================
# Общие переменные для всех ресурсов демо-стенда IoT
# Пайплайн: Producer -> RabbitMQ -> Consumer -> Redis+MongoDB -> Dashboard
# Все значения вынесены сюда — в ресурсах хардкода нет
# =============================================================================
locals {
# --- RabbitMQ: очередь сообщений (сервис 93) ---
rmq_resource_name = "iot-rmq" # имя инстанса в личном кабинете Nubes
rmq_cpu = 300 # CPU в millicores (1000 = 1 ядро)
rmq_memory = 512 # память в MB
rmq_disk = 5 # диск в GB
rmq_replicas = 1 # количество реплик (нод)
rmq_timeout = "11m" # таймаут ожидания операций create/modify
rmq_queue = "iot-events" # имя очереди: producer кладёт, consumer забирает
# --- Redis: кэш дашборда (сервис 91) ---
rds_resource_name = "iot-rds" # имя инстанса (rds = ReDiS)
rds_cpu = 300 # CPU millicores
rds_memory = 512 # память MB
rds_disk = 5 # диск GB
rds_replicas = 1 # реплик (нод)
rds_timeout = "11m" # таймаут операций
# --- MongoDB: архив всех событий (сервис 92) ---
mgo_resource_name = "iot-mgo" # имя инстанса (mgo = MonGO)
mgo_cpu = 300 # CPU millicores
mgo_memory = 512 # память MB
mgo_disk = 5 # диск GB
mgo_replicas = 1 # реплик (нод)
mgo_timeout = "11m" # таймаут операций
# --- Producer (Node.js): генерирует IoT-события -> RabbitMQ ---
prod_resource_name = "iot-prod" # имя инстанса в личном кабинете
prod_domain = "iot-prod" # поддомен (станет <domain>.nodejsk8s.dev.nubes.ru)
prod_git_path = "https://gitea.services.ngcloud.ru/ВАШ_ЛОГИН/tf-iot-producer.git" # ⚠️ замените на свой форк
prod_git_revision = "main" # ветка или хеш коммита для деплоя
prod_cpu = 300 # CPU millicores
prod_memory = 256 # память MB
prod_replicas = 1 # количество подов
prod_timeout = "11m" # таймаут операций
# --- Consumer (Node.js): RabbitMQ -> Redis + MongoDB ---
cons_resource_name = "iot-cons" # имя инстанса в личном кабинете
cons_domain = "iot-cons" # поддомен (станет <domain>.nodejsk8s.dev.nubes.ru)
cons_git_path = "https://gitea.services.ngcloud.ru/ВАШ_ЛОГИН/tf-iot-consumer.git" # ⚠️ замените на свой форк
cons_git_revision = "main" # ветка или хеш коммита для деплоя
cons_cpu = 300 # CPU millicores
cons_memory = 256 # память MB
cons_replicas = 1 # количество подов
cons_timeout = "11m" # таймаут операций
# --- Dashboard (Node.js + Chart.js): Redis -> графики ---
dash_resource_name = "iot-dash" # имя инстанса в личном кабинете
dash_domain = "iot-dash" # поддомен (станет <domain>.nodejsk8s.dev.nubes.ru)
dash_git_path = "https://gitea.services.ngcloud.ru/ВАШ_ЛОГИН/tf-iot-dashboard.git" # ⚠️ замените на свой форк
dash_git_revision = "main" # ветка или хеш коммита для деплоя
dash_cpu = 300 # CPU millicores
dash_memory = 256 # память MB
dash_replicas = 1 # количество подов
dash_timeout = "11m" # таймаут операций
}
+52
View File
@@ -0,0 +1,52 @@
# =============================================================================
# Terraform-конфиг: IoT Demo на Nubes Cloud
# =============================================================================
# 6 сервисов: RabbitMQ, Redis, MongoDB, 3× Node.js
# Пайплайн: Producer → RabbitMQ → Consumer → Redis + MongoDB → Dashboard
#
# Требования:
# - Nubes Cloud CLI (провайдер registry.kube5s.ru)
# - API-токен из личного кабинета
# - Заполненный terraform.tfvars (см. terraform.tfvars.example)
#
# Быстрый старт:
# 1. Скопировать terraform.tfvars.example → terraform.tfvars
# 2. Заполнить api_token, realm, api_endpoint
# 3. Форкнуть репозитории приложений в свой Gitea (см. README.md)
# 4. Обновить git_path и git_revision в locals.tf
# 5. terraform init && terraform apply
# =============================================================================
terraform {
required_providers {
nubes = {
source = "registry.kube5s.ru/nubes-test/nubes" # реестр провайдера Nubes
version = "5.1.16" # версия провайдера
}
}
}
# API-токен для доступа к Nubes Cloud (из личного кабинета)
variable "api_token" {
type = string
sensitive = true # не показывать в логах/плане
}
# K8s-кластер (realm) для развёртывания (например: k8s-4-sandbox-nubes-ru)
variable "realm" {
type = string
}
# API-эндпоинт Nubes Cloud:
# Тестовый: https://lk-api-gateway-test.ngcloud.ru/api/v1/svc
# Боевой: https://lk-api-gateway.ngcloud.ru/api/v1/svc
variable "api_endpoint" {
type = string
default = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
}
# Конфигурация провайдера Nubes
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
+52
View File
@@ -0,0 +1,52 @@
# =============================================================================
# Producer (Node.js) — генерирует IoT-события и шлёт в RabbitMQ
# Каждые 3 сек создаёт случайное событие от одного из 8 датчиков
# Переменные окружения попадают в K8s под через json_env
# =============================================================================
locals {
# Достаём реальные значения из созданного RabbitMQ (state_out_flat + vault_secrets)
prod_rmq_host = nubes_rabbitmq.main.state_out_flat["internalConnect"] # K8s-хост RabbitMQ
prod_rmq_user = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminUser"]) # логин RabbitMQ
prod_rmq_pass = nonsensitive(nubes_rabbitmq.main.vault_secrets["adminPass"]) # пароль RabbitMQ
}
resource "nubes_nodejs" "producer" {
resource_name = local.prod_resource_name # имя в личном кабинете
startup_configuration = {
resource_realm = var.realm # K8s-кластер
}
cluster_configuration = {
cpu = local.prod_cpu # CPU millicores
memory = local.prod_memory # память MB
replicas = local.prod_replicas # количество подов
}
access_configuration = {
domain = local.prod_domain # поддомен для HTTP-доступа
}
app_configuration = {
version = "22" # версия Node.js
git_path = local.prod_git_path # Git-репо с кодом
health_path = "/" # эндпоинт для health-check'а
}
git_revision = local.prod_git_revision # коммит для деплоя
operation_timeout = local.prod_timeout # таймаут
adopt_existing_on_create = true # подхватить suspended инстанс при повторном apply
# Переменные окружения в K8s поде (доступны через process.env)
json_env = jsonencode({
RABBIT_HOST = local.prod_rmq_host # хост RabbitMQ
RABBIT_PORT = "5672" # порт AMQP
RABBIT_USER = local.prod_rmq_user # логин
RABBIT_PASS = local.prod_rmq_pass # пароль
RABBIT_QUEUE = local.rmq_queue # имя очереди
PRODUCE_INTERVAL = "3" # интервал между событиями (сек)
})
depends_on = [nubes_rabbitmq.main] # ждать создания RabbitMQ
}
+18
View File
@@ -0,0 +1,18 @@
# =============================================================================
# terraform.tfvars — переменные для IoT Demo
# Скопируйте этот файл: cp terraform.tfvars.example terraform.tfvars
# и заполните значения ниже.
# =============================================================================
# API-токен из личного кабинета Nubes Cloud
# (Настройки → API-ключи → Создать)
api_token = "ВАШ_API_ТОКЕН"
# K8s-кластер для развёртывания
# (например: k8s-4-sandbox-nubes-ru — тестовый кластер)
realm = "ВАШ_REALM"
# API-эндпоинт (раскомментируйте нужный):
# Тест: https://lk-api-gateway-test.ngcloud.ru/api/v1/svc
# Прод: https://lk-api-gateway.ngcloud.ru/api/v1/svc
# api_endpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"