server-monitoring/README.md
ShiZa039 74b6f92cb8 Стек мониторинга: Prometheus + Alertmanager + Grafana + node_exporter + cAdvisor
Самодостаточный docker compose стек с деплоем через deploy.sh,
bootstrap-установкой с нуля и конфигурацией через .env.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:54:40 +02:00

205 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Мониторинг серверов
Самодостаточный стек: **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](bootstrap.sh) сам поставит зависимости (git, curl, htpasswd, docker + compose через get.docker.com), склонирует репо в `/opt/monitoring`, при первом запуске интерактивно спросит `BIND_IP` (сам предложит tailnet-адрес, если есть tailscale), `SERVER_NAME` и пароли, создаст `.env` и запустит деплой:
```bash
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`.
```bash
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. Подготовка (для обоих вариантов)
```bash
# 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-адрес сервера:**
```bash
tailscale ip -4
# или, если команда недоступна:
ip -o -4 addr show tailscale0 | awk '{print $4}'
```
**2. Заполни `.env`** (минимум):
```ini
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
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`** — здесь пароль на метрики **обязателен**:
```ini
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:
```bash
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
bash deploy.sh
```
Проверь, что без пароля API закрыт, а с паролем работает:
```bash
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 наружу не публикуется вовсе.
### После установки (оба варианта)
1. Зайди в Grafana (`admin` / `GRAFANA_ADMIN_PASSWORD`) → папка **Monitoring** → дашборды «Node Exporter Full» и «Cadvisor exporter» уже на месте.
2. Проверь таргеты: `http://BIND_IP:9090/targets` — все четыре job (prometheus, node, cadvisor + сам alertmanager в разделе alerting) должны быть UP.
3. Проверь температуры (раздел ниже) и 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`). Проверка на сервере:
```bash
curl -su "$METRICS_USER:$METRICS_PASSWORD" http://$BIND_IP:9100/metrics | grep node_hwmon_temp_celsius
```
Если пусто — на хосте не загружен драйвер сенсоров:
```bash
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):
1. На удалённом сервере поднять из этого же репозитория только `node_exporter` и `cadvisor` (свой `.env` с его tailnet-IP).
2. В `prometheus/prometheus.yml.tpl` центрального сервера добавить в `scrape_configs` job с таргетом `100.x.y.z:9100` (и при необходимости пробросить порт cAdvisor на tailnet-IP удалёнки) с лейблом `server`.
3. `bash deploy.sh` на центральном — правила и дашборды подхватят новый сервер автоматически (везде есть лейбл `server`/`instance`).
Вариант B — **полный стек на каждом сервере** (для серверов вне tailnet, «смотрят в мир»): каждый сервер алертит сам за себя в тот же Telegram-чат, а централизованный обзор — либо federation (центральный Prometheus забирает срез метрик с `https://ip:9090/federate` с basic auth), либо просто второй datasource в центральной Grafana. Не требует связности между серверами, кроме доступа к 9090.
## Эксплуатация
```bash
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.