Самодостаточный docker compose стек с деплоем через deploy.sh, bootstrap-установкой с нуля и конфигурацией через .env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 KiB
Мониторинг серверов
Самодостаточный стек: node_exporter + cAdvisor + Prometheus + Alertmanager + Grafana в одном docker compose. Вся конфигурация в репозитории; всё специфичное для конкретного сервера — только в .env. Один репозиторий раскатывается на любой сервер сменой .env.
| Сервис | Версия | Порт (на BIND_IP) |
Зачем |
|---|---|---|---|
| node_exporter | v1.9.1 | 9100 | CPU, RAM, диски, температуры (hwmon), сеть, systemd-юниты |
| cAdvisor | v0.52.1 | — (только внутренняя сеть) | метрики docker-контейнеров |
| Prometheus | v3.5.0 LTS | 9090 | сбор и хранение (30 дней, scrape 30s) |
| Alertmanager | v0.28.1 | 9093 | API активных алертов + Telegram |
| Grafana | 12.0.2 | 3001 | дашборды (Node Exporter Full, Cadvisor exporter) |
Наружу на 0.0.0.0 не публикуется ничего: каждый сервис слушает только BIND_IP.
Установка одной командой (bootstrap)
На чистом сервере достаточно указать репозиторий — bootstrap.sh сам поставит зависимости (git, curl, htpasswd, docker + compose через get.docker.com), склонирует репо в /opt/monitoring, при первом запуске интерактивно спросит BIND_IP (сам предложит tailnet-адрес, если есть tailscale), SERVER_NAME и пароли, создаст .env и запустит деплой:
curl -fsSL https://git.sab-core.ru/ShiZa/server-monitoring/raw/branch/main/bootstrap.sh \
| sudo bash -s -- --repo https://git.sab-core.ru/ShiZa/server-monitoring.git
Флаги: --repo <url> (обязателен на чистом сервере), --dir <path> (по умолчанию /opt/monitoring), --branch <ветка>. Для приватного репо используй URL с токеном (https://user:token@…) или предварительно настрой SSH-ключ и передай ssh-URL.
Повторный запуск идемпотентен: git pull + deploy.sh, .env не перезаписывается — так же можно обновлять уже установленный сервер. Из существующего клона запускается без флагов: sudo bash bootstrap.sh.
Если терминала нет (например, запуск из CI), скрипт создаст .env из шаблона и остановится, попросив заполнить его и запустить bash deploy.sh.
Новый сервер за 5 минут (вручную)
Требования: Debian-подобная ОС, docker + плагин docker compose, curl.
git clone https://git.sab-core.ru/ShiZa/server-monitoring.git && cd server-monitoring
cp .env.example .env && chmod 600 .env
nano .env # заполнить BIND_IP, SERVER_NAME, GRAFANA_ADMIN_PASSWORD, остальное по необходимости
bash deploy.sh
deploy.sh идемпотентен: проверяет окружение, рендерит конфиги из .env в rendered/ (в git не попадает), валидирует конфиг promtool-ом, поднимает стек и прогоняет smoke-тесты по всем пяти сервисам с выводом OK/FAIL. Запускать повторно можно всегда — после каждого изменения .env или правил.
Подробная установка
Шаг 0. Подготовка (для обоих вариантов)
# docker + compose уже должны стоять; проверка:
docker --version && docker compose version && curl --version | head -1
# клонировать репозиторий (путь любой, например /opt)
sudo git clone https://git.sab-core.ru/ShiZa/server-monitoring.git /opt/monitoring
cd /opt/monitoring
cp .env.example .env
chmod 600 .env # в .env будут пароли — закрыть от чужих глаз
Дальше — по одному из двух вариантов.
Вариант 1: сервер в tailnet (Headscale/Tailscale)
1. Узнай tailnet-адрес сервера:
tailscale ip -4
# или, если команда недоступна:
ip -o -4 addr show tailscale0 | awk '{print $4}'
2. Заполни .env (минимум):
BIND_IP=100.64.0.5 # tailnet-адрес из шага 1
SERVER_NAME=srv-home # уникальное имя этого сервера
GRAFANA_ADMIN_PASSWORD=<придумай>
METRICS_PASSWORD= # можно пусто: снаружи tailnet порты недоступны
CRITICAL_CONTAINERS="authentik|forgejo"
CRITICAL_UNITS="headscale.service"
3. Деплой и проверка:
bash deploy.sh
Все пять проверок должны показать [OK]. Веб-интерфейсы открываются только с устройств твоего tailnet: Grafana http://100.64.0.5:3001, Prometheus http://100.64.0.5:9090, Alertmanager http://100.64.0.5:9093.
Замечание: если tailscaled стартует позже docker, после перезагрузки сервера контейнеры могут пару минут перезапускаться, пока tailnet-адрес не поднимется — это штатно. Хочешь убрать — добавь зависимость docker от tailscaled (systemctl edit docker.service → After=tailscaled.service).
Вариант 2: сервер без tailnet (смотрит в мир напрямую)
1. Узнай публичный IP (из панели VPS-провайдера или ip -o -4 addr show; это должен быть адрес интерфейса сервера, а не NAT).
2. Заполни .env — здесь пароль на метрики обязателен:
BIND_IP=203.0.113.10 # публичный IP сервера
SERVER_NAME=vps-1
GRAFANA_ADMIN_PASSWORD=<придумай>
METRICS_USER=admin
METRICS_PASSWORD=<придумай> # basic auth на Prometheus/Alertmanager/node_exporter
CRITICAL_CONTAINERS="..." # что крутится на этом сервере
CRITICAL_UNITS="..." # какие юниты критичны (headscale тут может не быть)
deploy.sh при пустом METRICS_PASSWORD выдаст предупреждение — на публичном сервере не игнорируй его.
3. Ограничь доступ фаерволом (рекомендуется, вторая линия защиты после basic auth). Пример для ufw — доступ к мониторингу только со своих IP:
MY_IP=<твой домашний/рабочий IP>
for p in 9090 9093 9100 3001; do sudo ufw allow from $MY_IP to any port $p proto tcp; done
for p in 9090 9093 9100 3001; do sudo ufw deny $p/tcp; done
sudo ufw status numbered
4. Деплой и проверка:
bash deploy.sh
Проверь, что без пароля API закрыт, а с паролем работает:
curl -s -o /dev/null -w '%{http_code}\n' http://203.0.113.10:9090/-/healthy # 401
curl -su admin:ПАРОЛЬ -o /dev/null -w '%{http_code}\n' http://203.0.113.10:9090/-/healthy # 200
Grafana (http://IP:3001) защищена собственным логином, cAdvisor наружу не публикуется вовсе.
После установки (оба варианта)
- Зайди в Grafana (
admin/GRAFANA_ADMIN_PASSWORD) → папка Monitoring → дашборды «Node Exporter Full» и «Cadvisor exporter» уже на месте. - Проверь таргеты:
http://BIND_IP:9090/targets— все четыре job (prometheus, node, cadvisor + сам alertmanager в разделе alerting) должны быть UP. - Проверь температуры (раздел ниже) и Telegram: временно останови тестовый контейнер — через ~4 минуты придёт алерт
ContainerDown, после запуска — resolved.
Переменные .env
| Переменная | Обязательная | Что делает |
|---|---|---|
BIND_IP |
да | IP, на котором слушает стек: tailnet-IP (100.x.y.z) или публичный IP сервера |
SERVER_NAME |
да | лейбл server во всех метриках и алертах |
GRAFANA_ADMIN_PASSWORD |
да | пароль admin в Grafana |
METRICS_USER / METRICS_PASSWORD |
для публичных серверов | basic auth на Prometheus, Alertmanager и node_exporter. Пусто — без аутентификации (допустимо только когда BIND_IP доступен лишь по tailnet) |
TG_BOT_TOKEN / TG_CHAT_ID |
нет | Telegram-уведомления; пусто — алерты только в API/веб-интерфейсе |
CRITICAL_CONTAINERS |
нет | контейнеры, обязанные работать (ContainerDown), подстроки имени через |, например "authentik|forgejo" |
CRITICAL_UNITS |
нет | systemd-юниты, обязанные быть active (SystemdUnitDown), через | |
*_PORT |
нет | порты сервисов; Grafana по умолчанию 3001, чтобы не конфликтовать с forgejo |
Значения с | — в двойных кавычках. Добавить контейнер или юнит под алерт = дописать его в переменную и снова запустить bash deploy.sh.
Безопасность: tailnet против «смотрит в мир»
- Сервер в tailnet:
BIND_IP= tailnet-адрес,METRICS_PASSWORDможно оставить пустым — доступ и так только из tailnet. - Сервер с публичным IP:
METRICS_PASSWORDобязателен (deploy.sh сгенерирует bcrypt и включит basic auth на Prometheus/Alertmanager/node_exporter; Grafana защищена своим логином; cAdvisor наружу не торчит вообще). Дополнительно стоит ограничить порты 9090/9093/9100/3001 фаерволом по своим IP. BIND_IP=127.0.0.1(только SSH-туннель) не поддерживается: Prometheus из контейнера не достучится до node_exporter на loopback хоста.- Если
BIND_IP— tailnet-адрес и tailscaled стартует позже docker, контейнеры при загрузке могут несколько раз перезапуститься, пока адрес не появится — это нормально (restart: unless-stoppedдоведёт до рабочего состояния).
Трей-клиент забирает активные алерты с http://BIND_IP:9093/api/v2/alerts (с basic auth, если включён).
Алерты
Статические (prometheus/rules/alerts.yml): InstanceDown (critical, 2м), HighCPU >90% 5м, HighRAM >90% 5м, DiskFull >85% warning / >95% critical, HighTemp >80°C 3м (сенсоры coretemp/k10temp). Генерируемые из .env (rendered/generated-rules.yml): SystemdUnitDown (critical), ContainerDown (critical, ловит и остановленный, и удалённый контейнер). Critical-алерт глушит одноимённый warning (inhibit_rules).
Температура CPU
Метрики берутся из hwmon (node_hwmon_temp_celsius). Проверка на сервере:
curl -su "$METRICS_USER:$METRICS_PASSWORD" http://$BIND_IP:9100/metrics | grep node_hwmon_temp_celsius
Если пусто — на хосте не загружен драйвер сенсоров:
ls /sys/class/hwmon/ # пусто или только nvme/acpi?
sudo modprobe coretemp # Intel
sudo modprobe k10temp # AMD
echo coretemp | sudo tee /etc/modules-load.d/sensors.conf # автозагрузка
Правило HighTemp фильтрует чипы по regex coretemp|k10temp — если у железа другой чип (смотри лейбл chip в метриках), поправь regex в prometheus/rules/alerts.yml.
Как добавить удалённый сервер (пока только описание)
Вариант A — центральный Prometheus + экспортеры на удалёнке (проще, для серверов в одном tailnet):
- На удалённом сервере поднять из этого же репозитория только
node_exporterиcadvisor(свой.envс его tailnet-IP). - В
prometheus/prometheus.yml.tplцентрального сервера добавить вscrape_configsjob с таргетом100.x.y.z:9100(и при необходимости пробросить порт cAdvisor на tailnet-IP удалёнки) с лейбломserver. bash deploy.shна центральном — правила и дашборды подхватят новый сервер автоматически (везде есть лейблserver/instance).
Вариант B — полный стек на каждом сервере (для серверов вне tailnet, «смотрят в мир»): каждый сервер алертит сам за себя в тот же Telegram-чат, а централизованный обзор — либо federation (центральный Prometheus забирает срез метрик с https://ip:9090/federate с basic auth), либо просто второй datasource в центральной Grafana. Не требует связности между серверами, кроме доступа к 9090.
Эксплуатация
docker compose ps # статус
docker compose logs -f prometheus # логи сервиса
bash deploy.sh # применить изменения .env/правил (идемпотентно)
docker compose pull && bash deploy.sh # обновление образов (после смены тегов в compose)
docker compose down # остановить (данные в volumes сохраняются)
Существующие контейнеры и сети (authentik, forgejo и т.д.) стек не трогает — их метрики читает cAdvisor через ro-маунты docker.