InfluxDB: запуск на Debian з NGINX і підключення Grafana

Автор |  28/10/2025
 

Отже, продовження попереднього посту 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 app
  • index.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

Готово.