diff --git a/README.md b/README.md index 9e8a9c1..acb7a7f 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,91 @@ -# Введение -Данная практика посвещена базовым принципам Docker. Для выполнения практики необходимо использовать 3 виртуальные машины Linux. - -# Задание - -Необходимо развернуть следующую систему: - -![image](assets/images/DockerTask.png) - -Симуляторы сенсоров, развернутые в докер контейнерах, публикуют сообщения на Mqtt брокер. Сервис Consumer подписывается на все сообщение, -опубликованные в брокере, и заносит их в базу данных временных рядов. Сервис dashboard отображает графики полученных данных от сервисов. - -Для выполнения данного задания необходимо: -1. Использовать три виртуальные машины из предыдущей практики. Установить на каждой docker и docker-compose. - -2. Linux A. - 1. Разработать скрипты - симулятор технологических данных. Создать класс Sensor, а также наследников этого класса (указать 4 реально существующих типа датчиков, например, температура, давление, потребляемый ток). - 2. В основной программе реализовать клиента, который подключается к mqtt брокеру и публикует сообщения. - 3. Конфигурацию типов датчиков вывести через переменные среды - 4. Запуск реализовать с помощью докер контейнеров. Для запуска нескольких типов использовать docker-compose. - 5. В итоге сделать 4 типа датчиков, запустить 6 контейнеров разных типов, где задается: - * тип датчика - * название датчика - * дискретность публикации сообщений - * (ДОП ЗАДАЧА, НЕОБЯЗАТЕЛЬНО) тип формулы (подумайте как можно через конфигурацию повлиять на характер генерации нового числа) - * в формуле предусмотрите в качестве операнда дату вашего рождения (например умножение на год рождения или месяц) - * (ДОП ЗАДАЧА, НЕОБЯЗАТЕЛЬНО) тип топика (json формат (topic - /sensor/type, value {"name":"sensor1", "value": 22.4} ) или значение в конкретный топик (topic - /sensor/type/value, value - 22.4)) - 6. Залить на свою страницу в докерхаб созданный image контейнера в публичный репозиторий. - -3. Linux B - 1. Развернуть mosquitto брокер с помощью docker. Настроить проброс портов, необходимый для работы приложения. - 2. Настроить конфигурационный файл для брокера. Использовать volume для подгрузки файла в докер. - 3. В файрволе открыть порт, необходимый для работы брокера. -4. Linux C - 1. Развернуть influxdb (1.8 версия), telegraf, grafana с помощью docker-compose - 2. Настроить подписку телеграфа на mqtt топики брокера - 3. Сохранять все сообщения в influxdb - 4. В grafana вывести дашборды с текущими значениями всех датчиков и агрегированными показаниями (среднее значение по всем датчикам) - сделать скриншот - 5. Необходимо создать volume для каждого контейнера с конфигурационными файлами. (ДОП ЗАДАЧА, НЕОБЯЗАТЕЛЬНО) При пересоздании контейнеров конфигурация дашбордов должна сохранится (без самих данных). - 6. В конфигурационных файлах ссылаться по alias контейнеров (не по ip адресам) -5. При загрузки задания в гит репозиторий создать две ветки develop и master. В master загрузить только readme - задание. - В ветку develop - загружаем сделанное задание. Как только отправляете мне на проверку задание, создаете пул реквест в ветку мастер и назначаете меня ревьювером. Таким образом я смогу комментировать ваш репозиторий. -6. Оформить инструкцию в виде Markdown файла. Приложить конфигурационные файлы в репозиторий. - Критерием успеха выполнения задания будет являтся также написанная инструкция по запуску ваших контейнеров. То есть, если я загружу с докерхаба ваш image, запущу docker-compose файлы на своих виртуальных машинах, и при этом смогу зайти в интерфейс и посмотреть дашборд с потоком данных, то задание выполнено. - -Репозиторий оформить следующим образом: -- root - - assets - - images - картинки для отчета - - vms - - client - - simulator - код симулятора и докерфайл - - gateway - - mosquitto - папка с конфигом для mosquitto + sh скрипт по запуску докера - - server - - grafana - конфиги для тома графаны - - influxdb - конфиги для тома инфлукса - - telegraf - конфиги для тома телеграфа - - docker-compose.yml - - - report.md - Отчет markdown - -P.S. опираться можно на данную [статью](https://coderlessons.com/articles/programmirovanie/raspberry-pi-iot-datchiki-influxdb-mqtt-i-grafana) +## Настройка vmA (client) - Система сенсоров +1. Создаём папку для приложения (`/sensor-sim`). +2. Далее подготавливаем файлы самого приложения: + 1. `requirements.txt` в одну строку: `paho-mqtt`. + 2. `Dockerfile` для сборки образа и установки зависимости. + 3. Сам скрипт `sensor.py`, в котором описан базовый класс **Sensor** и его наследники. Чтение конфигурации происходит из **env-переменных**. + +3. Далее необходимо собрать образ и опубликовать его на Docker Hub: +```shell +docker login +docker build -t aleksandr/sensor-sim:latest . +docker push aleksandr/sensor-sim:latest +``` + +4. После этого создаём `docker-compose.yml` для запуска контейнеров из опубликованного образа.
+Каждый контейнер имеет свои env-переменные: SENSOR_TYPE, SENSOR_NAME и т.д.
+Получаем такую картину: +![lA](https://github.com/AlexKud2002/DockerPractice/blob/master/sensors.jpg) + +## Настройка vmB (gateway) - MQTT Broker +1. Создаём папку для MQTT Broker (`/mosquitto`). +2. Далее подготавливаем конфиг и `docker-compose.yml` +3. Также запускаем контейнер и проверяем открытость порта 1883: + +![lA](https://github.com/AlexKud2002/DockerPractice/blob/master/mqtt_status.jpg) + +В MQTT Explorer можно увидеть, что значения с сенсоров поступают корректно: + +![lA](https://github.com/AlexKud2002/DockerPractice/blob/master/mqtt_explorer.jpg) + +## Настройка vmC (server) - Мониторинг + +1. Создаём подобную структуру папок: +```shell +server/ +├── docker-compose.yml +├── influxdb/ +│ ├── influxdb.conf +│ ├── data/ +│ ├── meta/ +│ └── wal/ +├── telegraf/ +│ └── telegraf.conf +├── grafana/ +│ ├── provisioning/ +│ │ ├── datasources/ +│ │ │ └── default.yaml +│ │ └── dashboards/ +│ │ ├── default.yaml +│ │ └── dashboard.json +``` + +- После запуска контейнера `influxdb` создаем базу данных вручную при помощи: +```shell +CREATE database sensors +USE sensors +CREATE USER telegraf WITH PASSWORD 'telegraf' WITH ALL PRIVILEGES +``` +- И конфигурируем `telegraf.conf`, изменив: +```shell +[[outputs.influxdb]] + urls = ["http://influxdb:8086"] # здесь уже указан alias + database = "sensors" + username = "telegraf" + password = "telegraf" + +[[inputs.mqtt_consumer]] + servers = ["tcp://192.168.0.150:1883"] # IP ВМ с MQTT + topics = ["/sensor/+"] + data_format = "value" + data_type = "float" +``` + +2. В `docker-compose.yml` файле указываем одну сеть для всех контейнеров и указываем отдельно именнованые тома для **Grafana** и **InfluxDB**, также монтируем для обоих контейнеров ранее написанные конфиги. +3. После `docker-compose up -d` необходимо зайти на `http://192.168.0.149:3000` (где уже ожидает графический интерфейс **Grafana**) и сконфигурировать вручную дашборд, предварительно добавив источник данных из **InfluxDB**. +4. JSON созданного дашборда экспортируем и сохраняем в `.../grafana/provisioning/dashboards/dashboard.json`. +5. Перезапускаем контейнер с **Grafana**. + +![lA](https://github.com/AlexKud2002/DockerPractice/blob/master/server.jpg) + +## Тестирование + +1. Данные генерируются на vmA (работы сенсоров), поступают на vmB (**MQTT Broker**), а далее поступают на vmC (считываются **Telegraf** и сохраняются в **InfluxDB**), отображение же уже происходит в **Grafana** на заранее сконфигурированном дашборде. +2. У всех контейнеров также реализовано сохранение настроек при помощи `restart: unless-stopped` в `docker-compose.yml`. + +Финальные результаты работы: + +![lA](https://github.com/AlexKud2002/DockerPractice/blob/master/grafana.jpg) + +Представленные скриншоты подтверждают работоспособность системы. +Все необходимые файлы приложены в репозитории. diff --git a/grafana.jpg b/grafana.jpg new file mode 100644 index 0000000..59262af Binary files /dev/null and b/grafana.jpg differ diff --git a/mqtt_explorer.jpg b/mqtt_explorer.jpg new file mode 100644 index 0000000..bb922ca Binary files /dev/null and b/mqtt_explorer.jpg differ diff --git a/mqtt_status.jpg b/mqtt_status.jpg new file mode 100644 index 0000000..511ee16 Binary files /dev/null and b/mqtt_status.jpg differ diff --git a/photo b/photo new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/photo @@ -0,0 +1 @@ + diff --git a/sensors.jpg b/sensors.jpg new file mode 100644 index 0000000..451507d Binary files /dev/null and b/sensors.jpg differ diff --git a/server.jpg b/server.jpg new file mode 100644 index 0000000..b3a2f01 Binary files /dev/null and b/server.jpg differ diff --git a/vms/client/docker-compose.yml b/vms/client/docker-compose.yml new file mode 100644 index 0000000..c8ab021 --- /dev/null +++ b/vms/client/docker-compose.yml @@ -0,0 +1,67 @@ +version: '3.3' + +services: + sensor1: + build: . + environment: + SENSOR_TYPE: temperature + SENSOR_NAME: temp_1 + INTERVAL: 3 + BROKER_HOST: 192.168.0.150 + networks: + - default + + sensor2: + build: . + environment: + SENSOR_TYPE: pressure + SENSOR_NAME: pressure_1 + INTERVAL: 4 + BROKER_HOST: 192.168.0.150 + networks: + - default + + sensor3: + build: . + environment: + SENSOR_TYPE: humidity + SENSOR_NAME: hum_1 + INTERVAL: 5 + BROKER_HOST: 192.168.0.150 + networks: + - default + + sensor4: + build: . + environment: + SENSOR_TYPE: current + SENSOR_NAME: curr_1 + INTERVAL: 2 + BROKER_HOST: 192.168.0.150 + networks: + - default + + sensor5: + build: . + environment: + SENSOR_TYPE: temperature + SENSOR_NAME: temp_2 + INTERVAL: 3 + BROKER_HOST: 192.168.0.150 + networks: + - default + + sensor6: + build: . + environment: + SENSOR_TYPE: pressure + SENSOR_NAME: pressure_2 + INTERVAL: 6 + BROKER_HOST: 192.168.0.150 + networks: + - default + +networks: + default: + external: + name: iot_net diff --git a/vms/client/entity/sensor.py b/vms/client/entity/sensor.py new file mode 100644 index 0000000..12e45f6 --- /dev/null +++ b/vms/client/entity/sensor.py @@ -0,0 +1,66 @@ +import os +import time +import random +import json +import paho.mqtt.client as mqtt +from datetime import datetime + +class Sensor: + def __init__(self, name, interval): + self.name = name + self.interval = interval + + def generate(self): + return round(random.uniform(10.0, 30.0), 2) + + def read(self): + return { + "name": self.name, + "value": self.generate(), + "timestamp": datetime.utcnow().isoformat() + "Z" + } + +class TemperatureSensor(Sensor): + def generate(self): + return round(random.uniform(20.0, 40.0), 2) + +class PressureSensor(Sensor): + def generate(self): + return round(1000 + random.random() * 50, 2) + +class CurrentSensor(Sensor): + def generate(self): + return round(random.uniform(0.1, 10.0), 2) + +class HumiditySensor(Sensor): + def generate(self): + return round(random.uniform(30.0, 90.0), 2) + +SENSOR_TYPES = { + "temperature": TemperatureSensor, + "pressure": PressureSensor, + "current": CurrentSensor, + "humidity": HumiditySensor, +} + +# Параметры из переменных окружения +sensor_type = os.getenv("SENSOR_TYPE", "temperature") +sensor_name = os.getenv("SENSOR_NAME", "sensor1") +interval = int(os.getenv("INTERVAL", 5)) +broker_host = os.getenv("BROKER_HOST", "192.168.0.150") +broker_port = int(os.getenv("BROKER_PORT", 1883)) + +SensorClass = SENSOR_TYPES.get(sensor_type, Sensor) +sensor = SensorClass(sensor_name, interval) + +client = mqtt.Client() +client.connect(broker_host, broker_port, 60) + +topic = f"/sensor/{sensor_type}" + +while True: + payload = json.dumps(sensor.read()) + client.publish(topic, payload) + print(json.dumps(sensor.read(), indent=2)) + print(f"[{sensor_name}] -> {topic}: {payload}") + time.sleep(interval) diff --git a/vms/client/requirements.txt b/vms/client/requirements.txt new file mode 100644 index 0000000..c7b9a31 --- /dev/null +++ b/vms/client/requirements.txt @@ -0,0 +1 @@ +paho_mqtt \ No newline at end of file diff --git a/vms/gateway/mosquitto/mosquitto.conf.txt b/vms/gateway/mosquitto/mosquitto.conf.txt new file mode 100644 index 0000000..d7ea974 --- /dev/null +++ b/vms/gateway/mosquitto/mosquitto.conf.txt @@ -0,0 +1,4 @@ +listener 1883 +allow_anonymous true +persistence false +log_dest stdout \ No newline at end of file diff --git a/vms/server/infra/influxdb/scripts/influxdb.conf.txt b/vms/server/infra/influxdb/scripts/influxdb.conf.txt new file mode 100644 index 0000000..7a6dbf4 --- /dev/null +++ b/vms/server/infra/influxdb/scripts/influxdb.conf.txt @@ -0,0 +1,23 @@ +reporting-disabled = true +bind-address = "127.0.0.1:8088" + +[meta] + dir = "/var/lib/influxdb/meta" + +[data] + dir = "/var/lib/influxdb/data" + wal-dir = "/var/lib/influxdb/wal" + trace-logging-enabled = false + +[http] + enabled = true + bind-address = ":8086" + auth-enabled = true + log-enabled = true + write-tracing = false + pprof-enabled = false + https-enabled = false + +[logging] + level = "info" + file = "stderr" \ No newline at end of file diff --git a/vms/server/infra/telegraf/docker-compose.yml.txt b/vms/server/infra/telegraf/docker-compose.yml.txt new file mode 100644 index 0000000..d34b98b --- /dev/null +++ b/vms/server/infra/telegraf/docker-compose.yml.txt @@ -0,0 +1,53 @@ +version: '3.3' + +services: + influxdb: + image: influxdb:1.8 + container_name: influxdb + ports: + - "8086:8086" + volumes: + - influxdb-data:/var/lib/influxdb + command: influxd -config /etc/influxdb/influxdb.conf + environment: + - INFLUXDB_DB=sensors + - INFLUXDB_ADMIN_ENABLED=true + - INFLUXDB_ADMIN_USER=admin + - INFLUXDB_ADMIN_PASSWORD=admin + - INFLUXDB_USER=telegraf + - INFLUXDB_USER_PASSWORD=telegraf + networks: + - default + restart: unless-stopped + + telegraf: + image: telegraf:latest + container_name: telegraf + depends_on: + - influxdb + volumes: + - ./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro + networks: + - default + restart: unless-stopped + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + networks: + - default + restart: unless-stopped + +volumes: + influxdb-data: + grafana-data: + +networks: + default: + external: + name: iot_net \ No newline at end of file diff --git a/vms/server/infra/telegraf/telegraf.conf.txt b/vms/server/infra/telegraf/telegraf.conf.txt new file mode 100644 index 0000000..a4f85dc --- /dev/null +++ b/vms/server/infra/telegraf/telegraf.conf.txt @@ -0,0 +1,23 @@ +[agent] + interval = "10s" + round_interval = true + metric_batch_size = 1000 + metric_buffer_limit = 10000 + collection_jitter = "0s" + flush_interval = "10s" + flush_jitter = "0s" + precision = "" + hostname = "telegraf" + omit_hostname = false + +[[outputs.influxdb]] + urls = ["http://influxdb:8086"] + database = "sensors" + username = "telegraf" + password = "telegraf" + +[[inputs.mqtt_consumer]] + servers = ["tcp://192.168.0.150:1883"] + topics = ["/sensor/+"] + data_format = "value" + data_type = "float" \ No newline at end of file