Отже, продовження попереднього посту InfluxDB: знайомство і основні можливості.
Там познайомились з InfluxDB в цілому, тепер час будувати з ним реальні рішення.
Що будемо робити – запустимо InfluxDB на Debian, налаштуємо NGINX, імпортуємо дані з Google Sheets в .csv, а потім мігруємо їх до InfluxDB та підключимо Grafana. І додатково трохи пограємось з Python Falsk для створення веб-форми.
Зміст
Мій “self-monitoring” проект
Власне, для чого я все це роблю: я веду такий собі “self human monitoring” – кожного дня записую в Google Sheets різні показники – як добре спав, який був настрій, наскільки добре голова працювала і багато іншого, загалом там 23 метрики.
Далі це все прямо в Google Sheets виводиться в графіки, де я в будь-який момент можу глянути в який період яке в мене було самопочуття.
Система дуже класна, веду її вже два з половиною роки і активно користуюсь, але є проблема – це візуалізація даних, бо дефолтні графіки в сами гуглотаблицях дуже обмежені.
Минулого року для візуалізації підключав Google Looker Studio, який нативно вміє інтеграцію з Google Sheets – але з ним постійно виникали якісь проблеми, особливо якщо змінювався формат в таблиці типу перейменування колонок, тому згодом я Looker Studio закинув.
І врешті-решт прийшла ідея того, що, камон! Девопс я, ілі тварь дрожащая?
Чому б не використати мої знання в моніторингу інфраструктури в цій справі теж?
Тому вирішив побудувати власний стек моніторингу, де дані будуть зберігатись в InfluxDB.
Взагалі, InfluxDB вибрав, бо трохи погрався і сподобалось як там все з коробки є, але коли почав вже робити дашборди – то поняв, що вона все ж доволі обмежена, і мені не вистачає Grafana.
Тому поки що InfluxDB залишиться як база, а до неї додамо Grafana.
А вже пізніше, мабуть, все ж мігрую дані до VictoriaMetrics.
Втім, цей пост, звісно, не про цей селф-мониторинг, а просто непоганий приклад того, як запустити Influx з NGINX і Grafana, як імпортувати дані, і як створити веб-сторінку з Flask для додавання нових метрик в InfluxDB.
Поточні дані в Google Sheets
На прикладі таблиці Sleep:
Тут Sleep_rate – суб’єктивна оцінка якості сну, Sleepy_day – наскільки сильна була сонливість цього дня, Wake_ups – скільки раз за ніч прокидався, і Mults – наскільки яскраві і насичені були сни, бо іноді вони бувають дійсно “мультфільмами” – наче всю ніч в кінотеатрі просидів 🙂
План дій
Робитись все буде на тому самому сервері з Debian, де зараз хоститься сам блог RTFM.
Що будемо робити:
- запустимо InfluxDB в Docker
- налаштуємо vitrtualhost в NGINX
- імпортуємо існуючі дані з Google Sheets в InfluxDB
- подивимось, які дашборди можемо зробити в InfluxDB
- додамо форму для введення нових даних
- додамо Grafana для повноцінної візуалізації
Окремо треба буде зробити бекап і підтюнити InfluxDB та Grafana, бо сервер маленький, лише 2 гігабайти пам’яті, але це вже іншим разом.
Поїхали.
Запуск InfluxDB з Docker Compose
Простіше всього зробити з docker-compose, аби потім легше було переносити на інший сервер.
Встановлення Docker та Docker Compose на Debian
Встановлюємо Docker та Docker Compose, документація тут>>>:
root@setevoy-do-2023-09-02:~# apt-get update root@setevoy-do-2023-09-02:~# apt-get install ca-certificates curl root@setevoy-do-2023-09-02:~# install -m 0755 -d /etc/apt/keyrings root@setevoy-do-2023-09-02:~# curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc root@setevoy-do-2023-09-02:~# chmod a+r /etc/apt/keyrings/docker.asc root@setevoy-do-2023-09-02:~# echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null root@setevoy-do-2023-09-02:~# cat /etc/apt/sources.list.d/docker.list deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable root@setevoy-do-2023-09-02:~# apt-get update root@setevoy-do-2023-09-02:~# apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Запускаємо сервіс, додаємо в автостарт:
root@setevoy-do-2023-09-02:~# systemctl start docker root@setevoy-do-2023-09-02:~# systemctl enable docker
В Debian пакет docker-compose-plugin якось дивно встановлює docker-compose executable, довелось шукати по системі:
root@setevoy-do-2023-09-02:/opt/influx# find / -name docker-compose -type f /usr/libexec/docker/cli-plugins/docker-compose
Додаємо собі в $PATH:
root@setevoy-do-2023-09-02:/opt/influx# echo 'export PATH=$PATH:/usr/libexec/docker/cli-plugins/' >> ~/.bashrc root@setevoy-do-2023-09-02:/opt/influx# . ~/.bashrc
Docker Compose для InfluxDB та performance tuning
Я тут не особо копався, але на майбутнє можна буде подивитись.
Всі доступні опції – InfluxDB configuration options.
Змінні, які можна використати:
INFLUXD_REPORTING_DISABLED: телеметрія в InfluxData (О.о)INFLUXD_TASKS_ENABLED: користуватись поки не плануюINFLUXD_FLUX_LOG_ENABLED: детальні логи Flux queries, поки логи нехай будуть, але потім можна буде відключитиINFLUXD_QUERY_MEMORY_BYTES: можна задати ліміт по пам’яті на кожен запит, але з моїм об’ємом даних – не вартеINFLUXD_UI_DISABLED: можна відключити веб-інтерфейс і працювати тільки з API, поки нехай буде, як повністю на Grafana переключусь – можна буде відключити
Для даних буду робити каталог в /data, там в мене зараз живуть сайти, це окремий Digtical Ocean volume, який автоматом бекапиться самим Digtical Ocean:
root@setevoy-do-2023-09-02:~# ls -l /data/www/ total 8 drwxr-xr-x 4 root root 4096 Sep 2 2023 rtfm drwxr-xr-x 4 setevoy setevoy 4096 Sep 2 2023 setevoy
А для InfluxDB зробимо новий:
root@setevoy-do-2023-09-02:~# mkdir -p /data/influx/influxdb-data
Для файлу docker-compose.yaml робимо окремий каталог в /opt, в мене там всякі мої скрипти:
root@setevoy-do-2023-09-02:/opt# mkdir -p /opt/influx
Пишемо сам файл:
services:
influxdb:
image: influxdb:2.7
container_name: influxdb
restart: unless-stopped
ports:
- "8086:8086"
environment:
# disable telemetry reporting
- INFLUXD_REPORTING_DISABLED=true
# disable background Flux task scheduler
- INFLUXD_TASKS_ENABLED=false
# reduce Flux logging noise
#- INFLUXD_FLUX_LOG_ENABLED=false
# default retention
- DOCKER_INFLUXDB_INIT_RETENTION=infinite
volumes:
- /data/influx/influxdb-data:/var/lib/influxdb2
Запускаємо:
root@setevoy-do-2023-09-02:/opt/influx# docker-compose up
Не Kubernetes – port-forward не зробиш 🙁
Можна ssh-тунель, звісно, але будемо вже відразу робити через NGINX.
NGINX Setup
Додаємо новий рекорд в DNS:
SSL з Let’s Encrypt
Отримуємо сертифікат.
Треба зробити автоматизацію, але мені все лінь – OpenVPN: Let’s Encrypt DNS verification с certbot и AWS Route53 и обновление сертификата в OpenVPN Access Server.
Робимо максимально просто:
root@setevoy-do-2023-09-02:~# certbot certonly --preferred-challenges dns -d monitoring.example.org.ua --manual --email [email protected] --agree-tos Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for monitoring.example.org.ua - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name: _acme-challenge.monitoring.example.org.ua. with the following value: UlWc0fwbYvdNuylzbxwnSfjyHgBIcFXQqByNBeQIFD0 ...
Додаємо нову TXT, перевіряємо з домашнього компа, що вона вже з’явилась:
$ dig _acme-challenge.monitoring.example.org.ua txt +short "UlWc0fwbYvdNuylzbxwnSfjyHgBIcFXQqByNBeQIFD0"
Тицаємо Enter, сертифікат готовий:
... Press Enter to Continue Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/monitoring.example.org.ua/fullchain.pem Key is saved at: /etc/letsencrypt/live/monitoring.example.org.ua/privkey.pem This certificate expires on 2026-01-24.
Додавання NGINX virtualhost
В файлі /etc/nginx/conf.d/monitoring.example.org.ua.conf описуємо новий server і location:
server {
listen 80;
server_name monitoring.example.org.ua;
root /data/www/setevoy/monitoring.example.org.ua;
server_tokens off;
location ~ /.well-known {
allow all;
}
location / {
allow 62.***.***.83; # office
deny all;
return 301 https://monitoring.example.org.ua$request_uri;
}
}
server {
listen 443 ssl;
server_name monitoring.example.org.ua;
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains" always;
server_tokens off;
access_log /var/log/nginx/monitoring.example.org.ua-access.log;
error_log /var/log/nginx/monitoring.example.org.ua-error.log warn;
ssl_certificate /etc/letsencrypt/live/monitoring.example.org.ua/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/monitoring.example.org.ua/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
#ssl_stapling on;
#ssl_stapling_verify on;
client_max_body_size 300M;
location / {
# allow from home and office only
allow 62.***.***.83; # office
deny all;
# proxy to InfluxDB container
proxy_pass http://127.0.0.1:8086;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# to tune if need
proxy_read_timeout 300s;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
}
}
Перевіряємо синтаксис:
root@setevoy-do-2023-09-02:/opt/influx# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Перезавантажуємо конфіги:
root@setevoy-do-2023-09-02:/opt/influx# systemctl reload nginx
І відкриваємо наш InfluxDB:
Готово. База є – можна переносити дані.
Імпорт даних з Google Sheets – .csv в InfluxDB
Тепер сама весела частина 🙂
Точніше – перша весела.
Треба імпортувати вже існуючі дані з Google Sheets в InfluxDB і згенерувати метрики. Благо в мене з попередніх років в Таблицях все структуровано, InfluxDB вміє приймати .csv, тому тут проблем (майже) не було.
Завантажуємо таблицю собі на машину в .csv:
Отримуємо такий документ:
$ head 2025-Daily-Sleep-self.csv Date,Sleep_rate_my_day,Sleepy_day,Wake_ups,Mults 2025-01-01,7,1,, 2025-01-02,7,1,, 2025-01-03,7,2,, 2025-01-04,5,3,,
Таблиць в мене кілька:
Для кожної зробимо окрему метрику, а в тегах використаємо імена колонок:
Найпростіший спосіб завантажити csv – через UI:
Але в даному випадку він не спрацює, бо не той формат дати – в мене 2025-01-09, а InfluxDB хоче повний rfc3339, тобто 2025-01-09T00:00:00Z.
Згадуємо, що колись вміли в awk Йдемо до ChatGPT, отримуємо команду для форматування дати:
root@setevoy-do-2023-09-02:/data/influx/import# awk -F, 'NR==1{print;next} {printf "%sT00:00:00Z,%s,%s,%s,%s\n", $1, $2, $3, $4, $5}' 2025-Daily-Sleep-self.csv > 2025-Daily-Sleep-self-rfc3339.csv
Тепер маємо нормальну дату:
$ head 2025-Daily-Sleep-self-rfc3339.csv Date,Sleep_rate_my_day,Sleepy_day,Wake_ups,Mults 2025-01-01T00:00:00Z,7,1,, 2025-01-02T00:00:00Z,7,1,, 2025-01-03T00:00:00Z,7,2,,
Копіюємо файл на сервер:
$ scp -i /home/setevoy/.ssh/setevoy-do-2023-09-02 2025-Daily-Sleep-self.csv [email protected]:/data/influx/import 2025-Daily-Sleep-self.csv
Встановлюємо InfluxDB CLI:
root@setevoy-do-2023-09-02:/opt/influx# wget https://dl.influxdata.com/influxdb/releases/influxdb2-client-2.7.5-linux-amd64.tar.gz root@setevoy-do-2023-09-02:/opt/influx# tar xvzf ./influxdb2-client-2.7.5-linux-amd64.tar.gz ./ ./LICENSE ./README.md ./influx
Додаємо собі $PATH:/usr/libexec/docker/cli-plugins/:/opt/influx, налаштовуємо підключення:
root@setevoy-do-2023-09-02:/opt/influx# influx config create --config-name local --host-url http://localhost:8086 --org setevoy --token $INFLUX_TOKEN --active Active Name URL Org * local http://localhost:8086 setevoy
І завантажуємо дані – додаємо --header, бо формат InfluxDB вимагає цих анотацій, див. Extended annotated CSV:
root@setevoy-do-2023-09-02:/data/influx/import# influx write --bucket self-monitoring-1 --file 2025-Daily-Sleep-self-rfc3339.csv --format csv --header "#constant measurement,sleep_daily" --header "#datatype dateTime:RFC3339,double,double,double,double" 2025/10/26 11:32:24 line 303: no field data found 2025/10/26 11:32:24 line 304: no field data found 2025/10/26 11:32:24 Unable to batcher to error-file: invalid argument 2025/10/26 11:32:24 line 305: no field data found 2025/10/26 11:32:24 Unable to batcher to error-file: invalid argument 2025/10/26 11:32:24 line 306: no field data found ...
Таблиці за 2023 і 204 в мене окремими документами, аналогічно додаємо їх – і тепер маємо всі дані в одному місці:
Всі дані за 2.5 роки на одній дашборді.
Офігєть.
Веб-форма з Flask для внесення даних
Наступна задача – додати можливість вносити нові дані.
Перший варіант – продовжити писати в Google Sheets, на сервері скриптом отримувати їх, фіксити дату і пушити в базу, а скрипт запускати по крону.
Плюси – звична схема, і є “бекап” у вигляді гугл-таблиць.
Мінуси – буде проблема з тим, як в скрипті перевіряти які дані в базі вже є, аби не дублювати старі записи, і нові дані з Google Sheets в базі з’являться не відразу, а коли відпрацює крон.
Другий варіант – повністю нова схема: написати простеньку веб-сторінку, яка через InfluxDB клієнт буде записувати нові дані.
Мінуси – доведеться налаштовувати додатковий location в NGINX і запускати якийсь сервіс, який це скрипт буде оброблювати.
Врешті-решт все ж зупинився на другому варіанті.
Як це буде працювати:
gunicornдля запуску Flask appindex.htmlшаблонmetrics.jsonз описом метрик і їхніх тегівapp.py, який отримує дані з форми вводу в HTML і виконує операції в InfluxDB
Шаблон для метрик
Аби спростити життя далі – щоб простіше було додавати нові метрики – створимо JSON, який буде використовуватись в app.py аби формувати список метрик і їхніх тегів.
Отримуємо доступні метрики:
root@setevoy-do-2023-09-02:/data/influx/import# influx query '
import "influxdata/influxdb/schema"
schema.measurements(bucket: "self-monitoring-1")
'
Result: _result
Table: keys: []
_value:string
-------------------------
energy_productivity_daily
mood_smile_daily
sleep_daily
times_daily
weight_daily
І теги для кожної метрики:
root@setevoy-do-2023-09-02:/data/influx/import# influx query '
import "influxdata/influxdb/schema"
schema.measurementFieldKeys(bucket: "self-monitoring-1", measurement: "sleep_daily")
'
Result: _result
Table: keys: []
_value:string
----------------------
Mults
Sleep_rate_my_day
Sleepy_day
Wake_ups
Пишемо JSON:
{
"energy_productivity_daily": [
"Energy_day",
"Productivity_work",
"Productivity_home",
"Kognit_day",
"Prosperity_day",
"Study_day"
],
"mood_smile_daily": [
"Mood_day",
"Smile_day",
"Depression_day",
"Anxiety_day",
"Agression",
"Sickness",
"Kitty_index"
],
"sleep_daily": [
"Sleep_rate_my_day",
"Sleepy_day",
"Wake_ups",
"Mults"
],
"times_daily": [
"Sleep",
"Work",
"Rest",
"Study",
"Self"
],
"weight_daily": [
"Weight"
],
"testing_metric": [
"Testing_tag"
]
}
Flask і InfluxDBClient
Навайбокодив 🙂
Але працює.
Файл app.py:
import os
import json
from datetime import date, datetime, time, timezone
from flask import Flask, render_template, request, jsonify
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
app = Flask(__name__)
# === InfluxDB config ===
INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "tOx***iuw=="
INFLUX_ORG = "setevoy"
# default bucket, if user doesn't choose one from the html form
DEFAULT_BUCKET = "self-monitoring-1"
# load metrics from the 'metrics.json'
METRICS_FILE = os.path.join(os.path.dirname(__file__), "metrics.json")
with open(METRICS_FILE, "r") as f:
METRICS = json.load(f)
@app.get("/set")
@app.get("/set/")
def index():
"""Render HTML form with today's date pre-filled"""
return render_template(
"index.html",
metrics=METRICS,
today_date=date.today().isoformat()
)
@app.post("/set/submit")
def submit():
"""Handle form submission and write data to InfluxDB"""
form = request.form
# --- 1) Date from form or today ---
date_str = form.get("date")
if date_str:
try:
selected_date = datetime.fromisoformat(date_str).date()
except ValueError:
return jsonify({"ok": False, "error": "Bad date format, expected YYYY-MM-DD"}), 400
else:
selected_date = date.today()
# --- 2) Fixed time: 03:00 UTC ---
ts = datetime.combine(selected_date, time(3, 0, 0), tzinfo=timezone.utc)
wrote, errors = [], []
# --- 3) Get bucket from form or use default ---
bucket = form.get("bucket", DEFAULT_BUCKET)
try:
with InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG) as client:
write_api = client.write_api(write_options=SYNCHRONOUS)
for measurement, fields in METRICS.items():
for field in fields:
raw = form.get(field)
if raw is None or raw == "":
continue
try:
val = float(raw)
except ValueError:
errors.append(f"{measurement}.{field}: not a number: {raw!r}")
continue
point = (
Point(measurement)
.field(field, val)
.time(ts, WritePrecision.NS)
)
# write to selected bucket
write_api.write(bucket=bucket, record=point)
wrote.append(f"{bucket}: {measurement}.{field}={val}")
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 500
html = f"""
<html>
<body style="font-family:Arial;margin:40px;">
<h3>Data successfully written</h3>
<p><b>Date:</b> {selected_date.isoformat()}</p>
<ul>
{''.join(f'<li>{w}</li>' for w in wrote)}
</ul>
<p><a href="/set"><button>Return to main page</button></a></p>
</body>
</html>
"""
return html
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=True)
В принципі, тут доволі простий скрипт:
@app.get("/set/"): роут, де буде наша форма, генерує сторінку з файлуindex.html@app.post("/set/submit")і функціяsubmit(): де логіка виконання – є можливість задати дату, вибрати корзину в InfluxDB, в яку будемо писати, бере список метрик і тегів зmetrics.json, і черезInfluxDBClientвносить дані в InfluxDB- в кінці виводиться ще одна форма з інформацією про те, що саме було записано, і малює кнопку “повернутись назад”
Файл templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Self Monitoring</title>
<style>
body { font-family: Arial; margin: 40px; }
.metric-block { margin-bottom: 30px; }
label { display: inline-block; width: 180px; }
input { width: 80px; }
</style>
<script>
// set date input to yesterday in local time (YYYY-MM-DD)
function setYesterday() {
const d = new Date();
d.setDate(d.getDate() - 1);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
document.getElementById('date').value = `${y}-${m}-${day}`;
}
</script>
</head>
<body>
<h2>Self Monitoring</h2>
<form action="/set/submit" method="post">
<!-- Bucket selector -->
<div style="margin-bottom:16px;">
<label for="bucket">Bucket:</label>
<select id="bucket" name="bucket" required>
<option value="self-monitoring-1">self-monitoring-1</option>
<option value="self-monitoring-test">self-monitoring-test</option>
</select>
</div>
<!-- Date picker -->
<div style="margin-bottom:16px;">
<label for="date">Date:</label>
<input type="date" id="date" name="date" value="{{ today_date }}" required>
<button type="button" onclick="setYesterday()">Yesterday</button>
<small>UTC midnight will be used</small>
</div>
{% for measurement, fields in metrics.items() %}
<div class="metric-block">
<h3>{{ measurement }}</h3>
{% for field in fields %}
<div>
<label for="{{ field }}">{{ field }}:</label>
<input type="number" step="any" name="{{ field }}" id="{{ field }}">
</div>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="Submit">
</form>
</body>
</html>
Додаємо новий location в NGINX:
...
location /set/ {
auth_basic "Self Monitoring Access";
auth_basic_user_file /data/www/setevoy/.htpasswd_blog;
proxy_pass http://127.0.0.1:8080;
allow 62.***.***.83;
deny all;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
...
Встановлюємо пакет для Python virtualhost:
root@setevoy-do-2023-09-02:/data/influx/self-monitoring-form# apt install python3.11-venv root@setevoy-do-2023-09-02:/data/influx/self-monitoring-form# python3 -m venv venv
Встановлюємо залежності (старовєр з pip замість uv):
(venv) root@setevoy-do-2023-09-02:/data/influx/self-monitoring-form# pip install -r requirements.txt
Запускаємо gunicorn:
(venv) root@setevoy-do-2023-09-02:/data/influx/self-monitoring-form# gunicorn -w 1 -b 127.0.0.1:8080 app:app [2025-10-27 10:31:35 +0000] [488395] [INFO] Starting gunicorn 23.0.0 [2025-10-27 10:31:35 +0000] [488395] [INFO] Listening at: http://127.0.0.1:8080 (488395)
Заходимо на https://monitoring.example.org.ua/set, і маємо зручну форму:
Додаємо новий запис, перевіряємо в InfluxDB:
from(bucket: "self-monitoring-test") |> range(start: 2025-10-25T00:00:00Z, stop: 2025-10-28T00:00:00Z) |> filter(fn: (r) => r._measurement == "testing_metric") |> keep(columns: ["_time", "_field", "_value"])
Якщо треба видалити дані – робимо через CLI:
root@setevoy-do-2023-09-02:/data/influx# influx delete \ --bucket self-monitoring-1 \ --org setevoy \ --start '2025-10-27T00:00:00Z' \ --stop '2025-10-28T00:00:00Z' \ --predicate '_measurement="times_daily"' \ --host 'http://localhost:8086'
Запуск Grafana з NGINX
Додаємо контейнер з Grafana в наш docker-compose.yaml, відразу встановлюємо grafana-influxdb-flux-datasource:
...
grafana:
image: grafana/grafana
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SERVER_SERVE_FROM_SUB_PATH=true
- GF_SECURITY_COOKIE_SAMESITE=none
- GF_SECURITY_COOKIE_SECURE=true
- GF_SECURITY_COOKIE_NAME=grafana_session
- GF_SECURITY_COOKIE_REMEMBER_NAME=grafana_remember
- GF_SECURITY_COOKIE_LIFETIME=86400
- GF_SECURITY_ADMIN_USER=setevoy
- GF_SECURITY_ADMIN_PASSWORD=password
- GF_INSTALL_PLUGINS=grafana-influxdb-flux-datasource
# optional: disable telemetry
- GF_ANALYTICS_REPORTING_ENABLED=false
- GF_SERVER_ROOT_URL=https://monitoring.example.org.ua/grafana/
- GF_SERVER_SERVE_FROM_SUB_PATH=true
volumes:
- /data/influx/grafana:/var/lib/grafana
depends_on:
- influxdb
Налаштовуємо ще один location в NGINX:
...
location /grafana/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
allow 62.***.***.83;
deny all;
}
...
Логінимось в Grafana, налаштовуємо InfluxDB, в password вносимо наш токен з InfluxDB:
І створюємо дашборду:
І вся борда з усіма даними з жовтня 2023 року виглядає так:
Запуск gunicorn з Docker Compose
Зараз він запускається вручну, це не зручно, винесемо теж в docker-compose.
...
self-monitoring:
build: /data/influx/self-monitoring-form
container_name: self-monitoring
ports:
- "8080:8080"
environment:
- INFLUX_URL=http://influxdb:8086
- INFLUX_TOKEN="tOx***iuw=="
- INFLUX_ORG=setevoy
depends_on:
- influxdb
В /data/influx/self-monitoring-form додаємо Dockerfile:
FROM python:3.12-slim WORKDIR /app COPY . . RUN pip install --no-cache-dir -r requirements.txt EXPOSE 8080 CMD ["gunicorn", "-b", "0.0.0.0:8080", "app:app"]
Запускаємо docker-compose:
root@setevoy-do-2023-09-02:/opt/influx# docker-compose up [+] Building 23.9s (11/11) FINISHED => [internal] load local bake definitions 0.0s => => reading from stdin 553B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 200B 0.0s => [internal] load metadata for docker.io/library/python:3.12-slim ... [+] Running 5/5 ✔ influx-self-monitoring Built 0.0s ✔ Network influx_default Created 0.1s ✔ Container influxdb Created 0.1s ✔ Container self-monitoring Created 0.1s ✔ Container grafana Created ...
Створення systemd service
Спростимо запуск цього всього щастя – зробимо через systemd.
Додаємо файл /etc/systemd/system/self-monitoring.service:
[Unit] Description=Self-monitoring stack Requires=docker.service After=docker.service [Service] Type=oneshot WorkingDirectory=/opt/influx ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down RemainAfterExit=yes TimeoutStartSec=0 [Install] WantedBy=multi-user.target
Запускаємо його:
root@setevoy-do-2023-09-02:/opt/influx# systemctl start self-monitoring root@setevoy-do-2023-09-02:/opt/influx# systemctl enable self-monitoring Created symlink /etc/systemd/system/multi-user.target.wants/self-monitoring.service → /etc/systemd/system/self-monitoring.service.
bash скрипт для бекапу InfluxDB
Що ще треба буде зробити – це бекапи.
Я трохи повозився з influx backup, але постійно ловив 401, не став заморачуватись, бо дані оновлюються рідко, тому просто навайбокодив простенький скрипт на bash:
#!/bin/bash
# backup InfluxDB data directory and upload to S3
# set vars
SRC_DIR="/opt/influx"
BACKUP_DIR="/backups/influx"
DATE=$(date +%Y-%m-%d)
ARCHIVE_NAME="${DATE}-influx.tar.gz"
ARCHIVE_PATH="${BACKUP_DIR}/${ARCHIVE_NAME}"
S3_BUCKET="s3://setevoy-influx-backups"
# create backup directory if not exists
mkdir -p "$BACKUP_DIR"
# create tar.gz archive
tar -czf "$ARCHIVE_PATH" -C "$SRC_DIR" .
# check that archive was created
if [ ! -f "$ARCHIVE_PATH" ]; then
echo "❌ Failed to create backup archive!"
exit 1
fi
# upload to S3
aws s3 cp "$ARCHIVE_PATH" "$S3_BUCKET/$ARCHIVE_NAME"
# check upload result
if [ $? -eq 0 ]; then
echo "✅ Uploaded to S3: $S3_BUCKET/$ARCHIVE_NAME"
# remove local archive after successful upload
rm -f "$ARCHIVE_PATH"
echo "🧹 Local archive removed: $ARCHIVE_PATH"
else
echo "⚠️ Upload to S3 failed, keeping local copy."
exit 1
fi
Запускаємо для перевірки:
root@setevoy-do-2023-09-02:~# chmod +x /opt/influx/backup_data.sh root@setevoy-do-2023-09-02:~# /opt/influx/backup_data.sh upload: ../backups/influx/2025-10-27-influx.tar.gz to s3://setevoy-influx-backups/2025-10-27-influx.tar.gz ✅ Uploaded to S3: s3://setevoy-influx-backups/2025-10-27-influx.tar.gz 🧹 Local archive removed: /backups/influx/2025-10-27-influx.tar.gz
Додаємо в cron:
0 3 * * * /usr/local/bin/backup-influx.sh >> /var/log/backup-influx.log 2>&1
Готово.

















