Самодостаточный docker compose стек с деплоем через deploy.sh, bootstrap-установкой с нуля и конфигурацией через .env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
205 lines
15 KiB
Markdown
205 lines
15 KiB
Markdown
# Мониторинг серверов
|
||
|
||
Самодостаточный стек: **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.
|