Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added root/assets/images/A_sensors_ps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/B_mosquitto_ps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/C_grafana_dashboard_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/C_grafana_dashboard_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/C_stack_ps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/MQTT.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/linA_docker status.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/linB_docker status.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added root/assets/images/linC_docker status.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions root/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Отчет по практической работе: Развертывание системы мониторинга IoT с Docker

**Цель работы:** Развернуть и настроить распределенную систему для симуляции, сбора, хранения и визуализации данных от IoT-датчиков с использованием Docker и Docker Compose на трех виртуальных машинах Linux.

**Используемые инструменты:** VirtualBox, Ubuntu Server 24.04 LTS, Docker, Docker Compose, Python 3 (с библиотекой `paho-mqtt`), Mosquitto, InfluxDB 1.8.10, Telegraf, Grafana.

**Личные данные:**
* Фамилия: ismukhanova
* День рождения: 13
* Месяц рождения: 08

---

## 1. Подготовка Инфраструктуры и Установка Docker

* **Виртуальные машины:** Использованы три виртуальные машины (`Linux-A`, `Linux-B`, `Linux-C`), настроенные в предыдущей лабораторной работе.
* **Установка Docker и Docker Compose:** На каждой из трех виртуальных машин были выполнены шаги по установке Docker Engine и плагина Docker Compose из официальных репозиториев Docker. Членство текущего пользователя было добавлено в группу `docker` для выполнения команд без `sudo`.
---

## 2. Настройка Linux-B (ismukhanovagateway) - Mosquitto MQTT Broker

* **Цель:** Развернуть MQTT-брокер для обмена сообщениями между симуляторами и сборщиком данных.
* **Действия:**
1. На `Linux-B` создана директория `~/mosquitto_config`.
2. Внутри нее создан файл `mosquitto.conf` со следующей конфигурацией:
```conf
# Полный файл в репозитории: vms/gateway/mosquitto/mosquitto.conf
persistence true
persistence_location /mosquitto/data/
log_dest stdout
allow_anonymous true
listener 1883
```
3. Запущен контейнер Mosquitto с использованием официального образа `eclipse-mosquitto`:
```bash
# На Linux-B
sudo docker run -d \
-p 1883:1883 \
-v /home/ismukhanova_2/mosquitto_config/mosquitto.conf:/mosquitto/config/mosquitto.conf \
-v /home/ismukhanova_2/mosquitto_data:/mosquitto/data \
--name mqtt_broker \
--restart unless-stopped \
eclipse-mosquitto
```
4. Проверен статус контейнера `mqtt_broker` и его логи.
5. Настроен файрвол `ufw` на `Linux-B` для разрешения входящих соединений на порт `1883/tcp`.
## 3. Настройка Linux-A (ismukhanovaserver) - Симуляторы Сенсоров

![Mosquitto PS B](<./assets/images/B_mosquitto_ps.png>)


* **Цель:** Создать и запустить Docker-контейнеры, симулирующие отправку данных от различных датчиков на MQTT-брокер.
* **Действия:**
1. На `Linux-A` создана структура директорий `~/sensor_project/sensor_app/`.
2. Установлена библиотека `paho-mqtt` для Python.
3. Разработан скрипт `sensor_simulator.py` (класс `Sensor`, чтение конфигурации из переменных окружения, подключение к MQTT-брокеру на `Linux-B` (IP `192.168.13.1`), публикация JSON-данных).
4. Создан `Dockerfile` на базе `python:3.9-slim`.
5. Собран Docker-образ `iasiks/sensor-simulator:latest` (используя твой Docker Hub ID).
6. Образ опубликован на Docker Hub.
7. Создан `docker-compose.yml` для запуска 6 контейнеров-симуляторов (4 типа датчиков) с разными конфигурациями и опциями `tty: true`, `stdin_open: true`.
8. Контейнеры симуляторов запущены (`docker compose up -d`).

![Sensor Log A](<./assets/images/A_sensors_ps.png>)


## 4. Настройка Linux-C - Стек Мониторинга (InfluxDB, Telegraf, Grafana)

* **Цель:** Развернуть компоненты для сбора, хранения и визуализации данных.
* **Действия:**
1. На `Linux-C` создана структура директорий `~/iot_stack/` с поддиректориями для конфигураций Telegraf и Grafana Provisioning.
2. Подготовлен `telegraf.conf` с настройками `outputs.influxdb` (URL `http://influxdb_server:8086`, БД `sensors`, пользователь `telegraf`) и `inputs.mqtt_consumer` (сервер `<IP_Linux_B_в_clientnet>:1883`, топики `sensors/#`, `data_format = "json_v2"` с парсингом `measurement_name_path = "@topic[1]"`).
3. Подготовлены файлы Grafana Provisioning: `datasources/influxdb_datasource.yaml` и `dashboards/dashboards_provider.yaml`.
4. Создан `docker-compose.yml` для запуска `influxdb:1.8.10`, `telegraf`, `grafana/grafana` в общей сети `iot_monitoring_network` с именованными томами и монтированием конфигураций.
5. Стек запущен (`docker compose up -d`).
6. В CLI InfluxDB создана база данных `sensors` и пользователь `telegraf` с паролем и правами.
7. Проверен источник данных в Grafana и создан дашборд "Мониторинг Сенсоров".
8. JSON-модель дашборда экспортирована и добавлена в директорию для provisioning. Grafana перезапущена.

![Stack PS C](<./assets/images/C_stack_ps.png>)



## 5. Результаты и Визуализация

* **Поток данных:** Данные от симуляторов на `Linux-A` успешно передаются через MQTT-брокер на `Linux-B`, собираются Telegraf на `Linux-C`, сохраняются в InfluxDB и визуализируются в Grafana.
* **Дашборд Grafana:** Настроенный дашборд "Мониторинг Сенсоров" доступен через веб-интерфейс (`http://192.168.31.102:3000`) и корректно отображает показания всех 6 запущенных симуляторов сенсоров, а также агрегированные значения. Дашборд успешно загружается автоматически благодаря механизму provisioning.
* **Персистентность:** Данные InfluxDB и конфигурации/дашборды Grafana сохраняются между перезапусками контейнеров. Контейнеры с политикой `restart: unless-stopped` автоматически запускаются после перезагрузки ВМ.

![Final Grafana Dashboard 1](<./assets/images/C_grafana_dashboard_1.png>)

![Final Grafana Dashboard 2](<./assets/images/C_grafana_dashboard_2.png>)

---

## 6. Выводы

В ходе выполнения практической работы была успешно развернута и настроена распределенная система мониторинга IoT-данных с использованием Docker и Docker Compose. Продемонстрирована настройка взаимодействия между симуляторами сенсоров, MQTT-брокером, сборщиком метрик Telegraf, базой данных временных рядов InfluxDB и платформой визуализации Grafana, развернутыми на трех отдельных виртуальных машинах. Реализована автоматическая настройка компонентов Grafana через provisioning. Обеспечена персистентность данных. Все компоненты системы успешно взаимодействуют, данные корректно собираются, хранятся и визуализируются.

---


15 changes: 15 additions & 0 deletions root/vms/client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Используем легковесный базовый образ Python
FROM python:3.9-slim

# Устанавливаем рабочую директорию в контейнере
WORKDIR /app

# Копируем файл с зависимостями и устанавливаем их
COPY sensor_app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Копируем код приложения в рабочую директорию контейнера
COPY sensor_app/ .

# Команда, которая будет выполняться при запуске контейнера
CMD ["python", "sensor_simulator.py"]
90 changes: 90 additions & 0 deletions root/vms/client/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
services:
temperature_sensor_1:
image: iasiks/sensor-simulator:latest # Убедись, что это имя твоего локально собранного образа
container_name: temp_sensor_alpha
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=alpha_temp
- SENSOR_TYPE=temperature
- PUBLISH_INTERVAL=10
- BIRTH_DAY_OPERAND=13 # Твой день рождения
- TOPIC_STYLE=json

pressure_sensor_1:
image: iasiks/sensor-simulator:latest
container_name: pressure_sensor_beta
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=beta_pressure
- SENSOR_TYPE=pressure
- PUBLISH_INTERVAL=12
- BIRTH_DAY_OPERAND=13
- TOPIC_STYLE=json

current_sensor_1:
image: iasiks/sensor-simulator:latest
container_name: current_sensor_gamma
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=gamma_current
- SENSOR_TYPE=current
- PUBLISH_INTERVAL=15
- BIRTH_DAY_OPERAND=13
- TOPIC_STYLE=json

humidity_sensor_1:
image: iasiks/sensor-simulator:latest
container_name: humidity_sensor_delta
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=delta_humidity
- SENSOR_TYPE=humidity
- PUBLISH_INTERVAL=18
- BIRTH_DAY_OPERAND=13
- TOPIC_STYLE=json

temperature_sensor_2:
image: iasiks/sensor-simulator:latest
container_name: temp_sensor_epsilon
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=epsilon_temp
- SENSOR_TYPE=temperature
- PUBLISH_INTERVAL=11
- BIRTH_DAY_OPERAND=13
- TOPIC_STYLE=json

pressure_sensor_2:
image: iasiks/sensor-simulator:latest
container_name: pressure_sensor_zeta
restart: unless-stopped
tty: true
stdin_open: true
environment:
- MQTT_BROKER_HOST=192.168.31.103
- MQTT_BROKER_PORT=1883
- SENSOR_NAME=zeta_pressure
- SENSOR_TYPE=pressure
- PUBLISH_INTERVAL=14
- BIRTH_DAY_OPERAND=13
- TOPIC_STYLE=json
1 change: 1 addition & 0 deletions root/vms/client/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
paho-mqtt
140 changes: 140 additions & 0 deletions root/vms/client/sensor_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import paho.mqtt.client as mqtt
import time
import random
import os
import json

# Базовый класс Sensor
class Sensor:
def __init__(self, sensor_name, sensor_type, birth_date_operand):
self.sensor_name = sensor_name
self.sensor_type = sensor_type
self.birth_date_operand = birth_date_operand
self.current_value = self._initialize_value()

def _initialize_value(self):
if self.sensor_type == "temperature":
return random.uniform(15.0, 25.0)
elif self.sensor_type == "pressure":
return random.uniform(980.0, 1050.0)
elif self.sensor_type == "current":
return random.uniform(0.5, 5.0)
elif self.sensor_type == "humidity":
return random.uniform(30.0, 70.0)
else:
return random.uniform(0, 100)

def generate_new_value(self):
change = random.uniform(-0.5, 0.5)
modifier_from_birth = 1.0
if self.birth_date_operand != 0:
modifier_from_birth = self.birth_date_operand / 10.0 if self.birth_date_operand >=1 else 1.0

if self.sensor_type == "temperature":
self.current_value += change * modifier_from_birth
self.current_value = max(0, min(50, self.current_value))
elif self.sensor_type == "pressure":
self.current_value += change * 5 * modifier_from_birth
self.current_value = max(900, min(1100, self.current_value))
elif self.sensor_type == "current":
self.current_value += change * 0.1 * modifier_from_birth
self.current_value = max(0, min(10, self.current_value))
elif self.sensor_type == "humidity":
self.current_value += change * 2 * modifier_from_birth
self.current_value = max(0, min(100, self.current_value))
else:
self.current_value += change
return round(self.current_value, 2)

def get_topic_and_payload_type(self, topic_style="json"):
value = self.generate_new_value()

topic_for_default_style = f"sensors/{self.sensor_type}"
payload = json.dumps({"name": self.sensor_name, "value": value})

if topic_style == "value_in_topic":
topic_for_value_style = f"sensors/{self.sensor_type}/{self.sensor_name}/value"
payload_for_value_style = str(value)
return topic_for_value_style, payload_for_value_style

return topic_for_default_style, payload


def on_connect(client, userdata, flags, rc):
if rc == 0:
print(f"INFO [{client._client_id.decode() if client._client_id else 'UnknownSensor'}]: Connected to MQTT Broker!")
else:
print(f"ERROR [{client._client_id.decode() if client._client_id else 'UnknownSensor'}]: Failed to connect to MQTT Broker, return code {rc}")

def on_publish(client, userdata, mid):
pass

if __name__ == "__main__":
MQTT_BROKER_HOST = os.getenv("MQTT_BROKER_HOST", "localhost")
MQTT_BROKER_PORT_STR = os.getenv("MQTT_BROKER_PORT", "1883")
try:
MQTT_BROKER_PORT = int(MQTT_BROKER_PORT_STR)
except ValueError:
print(f"FATAL ERROR (Initial Env Parse): Invalid MQTT_BROKER_PORT: '{MQTT_BROKER_PORT_STR}'. Must be an integer.")
exit(1)

SENSOR_NAME = os.getenv("SENSOR_NAME", "default_sensor")
SENSOR_TYPE = os.getenv("SENSOR_TYPE", "temperature")
PUBLISH_INTERVAL_STR = os.getenv("PUBLISH_INTERVAL", "5")
try:
PUBLISH_INTERVAL = int(PUBLISH_INTERVAL_STR)
if PUBLISH_INTERVAL <= 0:
print(f"WARN [{SENSOR_NAME}]: PUBLISH_INTERVAL ('{PUBLISH_INTERVAL_STR}') is not positive, defaulting to 5 seconds.")
PUBLISH_INTERVAL = 5
except ValueError:
print(f"FATAL ERROR [{SENSOR_NAME}]: Invalid PUBLISH_INTERVAL: '{PUBLISH_INTERVAL_STR}'. Must be an integer.")
exit(1)

BIRTH_DAY_STR = os.getenv("BIRTH_DAY_OPERAND", "13")
try:
BIRTH_DAY = int(BIRTH_DAY_STR)
except ValueError:
print(f"FATAL ERROR [{SENSOR_NAME}]: Invalid BIRTH_DAY_OPERAND: '{BIRTH_DAY_STR}'. Must be an integer.")
exit(1)

TOPIC_STYLE = os.getenv("TOPIC_STYLE", "json")

print(f"\n--- Sensor Simulator Starting: {SENSOR_NAME} ({SENSOR_TYPE}) ---")
print(f"Target MQTT Broker: {MQTT_BROKER_HOST}:{MQTT_BROKER_PORT}")
print(f"Publish Interval: {PUBLISH_INTERVAL}s, Topic Style: {TOPIC_STYLE}")
print(f"-----------------------------------------------------\n")

client_id = f"sensor-{SENSOR_NAME}-{random.randint(0,10000)}"
client = mqtt.Client(client_id=client_id, callback_api_version=mqtt.CallbackAPIVersion.VERSION1)
client.user_data_set(SENSOR_NAME)
client.on_connect = on_connect
client.on_publish = on_publish

try:
client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT, 60)
except Exception as e:
print(f"FATAL ERROR [{SENSOR_NAME}]: Could not connect to MQTT Broker: {e}")
exit(1)

client.loop_start()
sensor = Sensor(SENSOR_NAME, SENSOR_TYPE, BIRTH_DAY)

try:
while True:
topic, payload = sensor.get_topic_and_payload_type(TOPIC_STYLE)
result = client.publish(topic, payload)
status = result[0]
if status == 0:
print(f"INFO [{SENSOR_NAME}]: Sent `{payload}` to topic `{topic}`")
else:
print(f"ERROR [{SENSOR_NAME}]: Failed to send message to topic {topic}, status: {status}")

time.sleep(PUBLISH_INTERVAL)
except KeyboardInterrupt:
print(f"INFO [{SENSOR_NAME}]: Simulation stopped by user (KeyboardInterrupt).")
except Exception as e:
print(f"FATAL ERROR [{SENSOR_NAME}]: An error occurred in the main loop: {e}")
finally:
print(f"INFO [{SENSOR_NAME}]: Stopping and disconnecting...")
client.loop_stop()
client.disconnect()
5 changes: 5 additions & 0 deletions root/vms/gateway/mosquitto/mosquitto.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
persistence true
persistence_location /mosquitto/data/
log_dest stdout
allow_anonymous true
listener 1883
Loading