Стек мониторинга: Prometheus + Alertmanager + Grafana + node_exporter + cAdvisor
Самодостаточный docker compose стек с деплоем через deploy.sh, bootstrap-установкой с нуля и конфигурацией через .env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
74b6f92cb8
15 changed files with 17242 additions and 0 deletions
41
.env.example
Normal file
41
.env.example
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Скопируй в .env и заполни: cp .env.example .env && chmod 600 .env
|
||||
# Значения со спецсимволами (| $ пробелы) — обязательно в двойных кавычках.
|
||||
# Файл читается и docker compose, и deploy.sh (bash), поэтому в паролях
|
||||
# избегай символов $ ` \ " — надёжнее всего [A-Za-z0-9_.-].
|
||||
|
||||
# === Обязательные ===========================================================
|
||||
# IP, на котором слушает весь стек: tailnet-IP (100.x.y.z) или публичный IP.
|
||||
# Наружу на 0.0.0.0 ничего не публикуется.
|
||||
BIND_IP=100.64.0.1
|
||||
|
||||
# Имя сервера — попадает лейблом server во все метрики и алерты
|
||||
SERVER_NAME=srv-1
|
||||
|
||||
# Пароль admin в Grafana
|
||||
GRAFANA_ADMIN_PASSWORD=changeme
|
||||
|
||||
# === Basic auth для Prometheus / Alertmanager / node_exporter ===============
|
||||
# Пусто = без аутентификации (нормально для серверов, доступных только по tailnet).
|
||||
# Для серверов с публичным BIND_IP — задать обязательно.
|
||||
# Трей-клиент ходит в http://BIND_IP:9093/api/v2/alerts с этими же кредами.
|
||||
METRICS_USER=admin
|
||||
METRICS_PASSWORD=
|
||||
|
||||
# === Telegram-уведомления ===================================================
|
||||
# Пусто = уведомления отключены (алерты видны в API/веб-интерфейсе Alertmanager)
|
||||
TG_BOT_TOKEN=
|
||||
TG_CHAT_ID=
|
||||
|
||||
# === Что алертить ===========================================================
|
||||
# Контейнеры, которые обязаны работать (regex-подстроки имени, разделитель «|»)
|
||||
CRITICAL_CONTAINERS="authentik|forgejo"
|
||||
|
||||
# systemd-юниты, которые обязаны быть active (разделитель «|»)
|
||||
CRITICAL_UNITS="headscale.service"
|
||||
|
||||
# === Порты (на BIND_IP) =====================================================
|
||||
NODE_EXPORTER_PORT=9100
|
||||
PROMETHEUS_PORT=9090
|
||||
ALERTMANAGER_PORT=9093
|
||||
# 3001, чтобы не конфликтовать с forgejo на 3000
|
||||
GRAFANA_PORT=3001
|
||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Репозиторий редактируется в том числе с Windows — на сервере нужны LF
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.tpl text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
.env.example text eol=lf
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# секреты
|
||||
.env
|
||||
|
||||
# конфиги, отрендеренные deploy.sh из шаблонов и .env
|
||||
rendered/
|
||||
205
README.md
Normal file
205
README.md
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# Мониторинг серверов
|
||||
|
||||
Самодостаточный стек: **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.
|
||||
24
alertmanager/alertmanager.yml.tpl
Normal file
24
alertmanager/alertmanager.yml.tpl
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Шаблон: entrypoint.sh рендерит его в /tmp/alertmanager.yml при старте контейнера.
|
||||
# Строки с префиксом #TG# включаются, только если заданы TG_BOT_TOKEN и TG_CHAT_ID.
|
||||
|
||||
route:
|
||||
receiver: __DEFAULT_RECEIVER__
|
||||
group_by: ['alertname', 'server', 'instance']
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
repeat_interval: 4h
|
||||
|
||||
# critical глушит warning того же алерта (например, DiskFull 95% глушит 85%)
|
||||
inhibit_rules:
|
||||
- source_matchers: ['severity = critical']
|
||||
target_matchers: ['severity = warning']
|
||||
equal: ['alertname', 'server', 'instance', 'mountpoint']
|
||||
|
||||
receivers:
|
||||
- name: blackhole
|
||||
#TG# - name: telegram
|
||||
#TG# telegram_configs:
|
||||
#TG# - bot_token: '__TG_BOT_TOKEN__'
|
||||
#TG# chat_id: __TG_CHAT_ID__
|
||||
#TG# parse_mode: 'HTML'
|
||||
#TG# send_resolved: true
|
||||
25
alertmanager/entrypoint.sh
Normal file
25
alertmanager/entrypoint.sh
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
# Рендерит alertmanager.yml из шаблона: секреты приходят через переменные
|
||||
# окружения контейнера и не попадают в файлы на хосте.
|
||||
set -eu
|
||||
|
||||
TPL=/etc/alertmanager/alertmanager.yml.tpl
|
||||
OUT=/tmp/alertmanager.yml
|
||||
|
||||
if [ -n "${TG_BOT_TOKEN:-}" ] && [ -n "${TG_CHAT_ID:-}" ]; then
|
||||
sed -e 's/^#TG#//' \
|
||||
-e 's|__DEFAULT_RECEIVER__|telegram|' \
|
||||
-e "s|__TG_BOT_TOKEN__|${TG_BOT_TOKEN}|" \
|
||||
-e "s|__TG_CHAT_ID__|${TG_CHAT_ID}|" \
|
||||
"$TPL" > "$OUT"
|
||||
else
|
||||
echo "WARN: TG_BOT_TOKEN / TG_CHAT_ID не заданы — Telegram-уведомления отключены (receiver: blackhole)" >&2
|
||||
sed -e '/^#TG#/d' \
|
||||
-e 's|__DEFAULT_RECEIVER__|blackhole|' \
|
||||
"$TPL" > "$OUT"
|
||||
fi
|
||||
|
||||
exec /bin/alertmanager \
|
||||
--config.file="$OUT" \
|
||||
--storage.path=/alertmanager \
|
||||
--web.config.file=/etc/alertmanager/web.yml
|
||||
141
bootstrap.sh
Normal file
141
bootstrap.sh
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#!/usr/bin/env bash
|
||||
# Установка «с нуля» одной командой: ставит зависимости (git, curl, docker,
|
||||
# compose, htpasswd), клонирует/обновляет репозиторий, создаёт .env (интерактивно
|
||||
# при первом запуске) и запускает deploy.sh. Идемпотентен — повторный запуск
|
||||
# просто подтянет свежий код и переприменит конфигурацию.
|
||||
#
|
||||
# Использование (на чистом сервере, от root):
|
||||
# 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
|
||||
#
|
||||
# Или из уже склонированного репозитория:
|
||||
# sudo bash bootstrap.sh
|
||||
#
|
||||
# Флаги: --repo <url> URL git-репозитория (обязателен вне клона)
|
||||
# --dir <path> куда клонировать (по умолчанию /opt/monitoring)
|
||||
# --branch <br> ветка (по умолчанию ветка по умолчанию репо)
|
||||
set -euo pipefail
|
||||
|
||||
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RESET=$'\033[0m'
|
||||
say() { echo "${C_GREEN}==>${C_RESET} $*"; }
|
||||
warn() { echo "${C_YELLOW}[WARN]${C_RESET} $*"; }
|
||||
die() { echo "${C_RED}[ERR]${C_RESET} $*" >&2; exit 1; }
|
||||
|
||||
REPO_URL=""
|
||||
TARGET_DIR="/opt/monitoring"
|
||||
BRANCH=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--repo) REPO_URL="${2:?}"; shift 2 ;;
|
||||
--dir) TARGET_DIR="${2:?}"; shift 2 ;;
|
||||
--branch) BRANCH="${2:?}"; shift 2 ;;
|
||||
-h|--help) grep '^#' "$0" | head -20; exit 0 ;;
|
||||
*) die "неизвестный аргумент: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$(id -u)" = 0 ] || die "нужны права root: запусти через sudo"
|
||||
command -v apt-get >/dev/null 2>&1 || die "поддерживается только Debian/Ubuntu (нужен apt-get)"
|
||||
|
||||
# --- 1. Базовые пакеты -------------------------------------------------------
|
||||
declare -A CMD_PKG=([curl]=curl [git]=git [htpasswd]=apache2-utils)
|
||||
MISSING=()
|
||||
for cmd in "${!CMD_PKG[@]}"; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || MISSING+=("${CMD_PKG[$cmd]}")
|
||||
done
|
||||
if [ "${#MISSING[@]}" -gt 0 ]; then
|
||||
say "Ставлю пакеты: ${MISSING[*]}"
|
||||
apt-get update -qq
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ca-certificates "${MISSING[@]}"
|
||||
fi
|
||||
|
||||
# --- 2. Docker + compose -----------------------------------------------------
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
say "Docker не найден — ставлю через get.docker.com"
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
fi
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
say "Плагин docker compose не найден — ставлю docker-compose-plugin"
|
||||
apt-get update -qq
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-compose-plugin \
|
||||
|| die "не удалось поставить docker-compose-plugin — поставь вручную"
|
||||
fi
|
||||
systemctl enable --now docker >/dev/null 2>&1 || true
|
||||
docker info >/dev/null 2>&1 || die "docker-демон не запущен (systemctl status docker)"
|
||||
|
||||
# --- 3. Репозиторий ----------------------------------------------------------
|
||||
if [ -z "${REPO_URL}" ] && [ -f ./deploy.sh ] && [ -f ./docker-compose.yml ]; then
|
||||
# запущен из уже склонированного репозитория
|
||||
TARGET_DIR="$(pwd)"
|
||||
say "Использую текущий клон: ${TARGET_DIR}"
|
||||
if [ -d .git ] && git remote get-url origin >/dev/null 2>&1; then
|
||||
git pull --ff-only || warn "git pull не удался (локальные изменения?) — продолжаю с текущим кодом"
|
||||
fi
|
||||
elif [ -d "${TARGET_DIR}/.git" ]; then
|
||||
say "Обновляю ${TARGET_DIR}"
|
||||
git -C "${TARGET_DIR}" fetch --all --prune
|
||||
if [ -n "${BRANCH}" ]; then git -C "${TARGET_DIR}" checkout "${BRANCH}"; fi
|
||||
git -C "${TARGET_DIR}" pull --ff-only || warn "git pull не удался — продолжаю с текущим кодом"
|
||||
else
|
||||
[ -n "${REPO_URL}" ] || die "укажи --repo <url> (или запусти скрипт из клона репозитория)"
|
||||
say "Клонирую ${REPO_URL} → ${TARGET_DIR}"
|
||||
if [ -n "${BRANCH}" ]; then
|
||||
git clone --branch "${BRANCH}" "${REPO_URL}" "${TARGET_DIR}"
|
||||
else
|
||||
git clone "${REPO_URL}" "${TARGET_DIR}"
|
||||
fi
|
||||
fi
|
||||
cd "${TARGET_DIR}"
|
||||
# стек может лежать в подкаталоге monitoring/ репозитория
|
||||
[ -f deploy.sh ] || { [ -f monitoring/deploy.sh ] && cd monitoring; } || die "deploy.sh не найден в репозитории"
|
||||
|
||||
# --- 4. .env: интерактивное создание при первом запуске ----------------------
|
||||
if [ ! -f .env ]; then
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
if [ -r /dev/tty ] && [ -w /dev/tty ]; then
|
||||
say "Первый запуск — настрою .env (значения можно потом поменять: nano .env && bash deploy.sh)"
|
||||
|
||||
DEFAULT_IP=""
|
||||
if command -v tailscale >/dev/null 2>&1; then
|
||||
DEFAULT_IP="$(tailscale ip -4 2>/dev/null | head -1 || true)"
|
||||
fi
|
||||
[ -n "${DEFAULT_IP}" ] || DEFAULT_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
echo "Адреса на интерфейсах:" >/dev/tty
|
||||
ip -o -4 addr show scope global 2>/dev/null | awk '{print " " $2 ": " $4}' >/dev/tty || true
|
||||
|
||||
read -r -p "BIND_IP (tailnet или публичный IP) [${DEFAULT_IP}]: " BIND_IP </dev/tty
|
||||
BIND_IP="${BIND_IP:-${DEFAULT_IP}}"
|
||||
[ -n "${BIND_IP}" ] || die "BIND_IP обязателен"
|
||||
|
||||
DEFAULT_NAME="$(hostname -s)"
|
||||
read -r -p "SERVER_NAME [${DEFAULT_NAME}]: " SERVER_NAME </dev/tty
|
||||
SERVER_NAME="${SERVER_NAME:-${DEFAULT_NAME}}"
|
||||
|
||||
GRAFANA_ADMIN_PASSWORD=""
|
||||
while [ -z "${GRAFANA_ADMIN_PASSWORD}" ]; do
|
||||
read -r -s -p "Пароль admin для Grafana: " GRAFANA_ADMIN_PASSWORD </dev/tty; echo >/dev/tty
|
||||
done
|
||||
|
||||
read -r -s -p "METRICS_PASSWORD (basic auth на Prometheus/Alertmanager/node_exporter; ПУСТО допустимо только для tailnet-серверов): " METRICS_PASSWORD </dev/tty
|
||||
echo >/dev/tty
|
||||
[ -n "${METRICS_PASSWORD}" ] || warn "METRICS_PASSWORD пуст — метрики будут без аутентификации"
|
||||
|
||||
sed -i \
|
||||
-e "s|^BIND_IP=.*|BIND_IP=${BIND_IP}|" \
|
||||
-e "s|^SERVER_NAME=.*|SERVER_NAME=${SERVER_NAME}|" \
|
||||
-e "s|^GRAFANA_ADMIN_PASSWORD=.*|GRAFANA_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}|" \
|
||||
-e "s|^METRICS_PASSWORD=.*|METRICS_PASSWORD=${METRICS_PASSWORD}|" \
|
||||
.env
|
||||
say ".env создан. Списки CRITICAL_CONTAINERS / CRITICAL_UNITS и Telegram-токены — правь в $(pwd)/.env"
|
||||
else
|
||||
warn "Нет терминала для вопросов: создан $(pwd)/.env из шаблона."
|
||||
warn "Заполни его (nano .env) и запусти: bash deploy.sh"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 5. Деплой ---------------------------------------------------------------
|
||||
say "Запускаю deploy.sh"
|
||||
bash deploy.sh
|
||||
179
deploy.sh
Normal file
179
deploy.sh
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#!/usr/bin/env bash
|
||||
# Идемпотентный деплой стека мониторинга: рендер конфигов из .env,
|
||||
# валидация, запуск, smoke-тесты. Безопасно запускать повторно.
|
||||
set -euo pipefail
|
||||
cd "$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
C_GREEN=$'\033[32m'; C_RED=$'\033[31m'; C_YELLOW=$'\033[33m'; C_RESET=$'\033[0m'
|
||||
ok() { echo "${C_GREEN}[OK]${C_RESET} $*"; }
|
||||
fail() { echo "${C_RED}[FAIL]${C_RESET} $*"; FAILED=1; }
|
||||
warn() { echo "${C_YELLOW}[WARN]${C_RESET} $*"; }
|
||||
die() { echo "${C_RED}[ERR]${C_RESET} $*" >&2; exit 1; }
|
||||
FAILED=0
|
||||
|
||||
# --- 1. Проверки окружения --------------------------------------------------
|
||||
command -v docker >/dev/null 2>&1 || die "docker не найден"
|
||||
docker compose version >/dev/null 2>&1 || die "плагин docker compose (v2) не найден"
|
||||
command -v curl >/dev/null 2>&1 || die "curl не найден (apt install curl)"
|
||||
[ -f .env ] || die "нет .env — выполни: cp .env.example .env && chmod 600 .env, затем заполни"
|
||||
|
||||
set -a; . ./.env; set +a
|
||||
|
||||
: "${BIND_IP:?BIND_IP не задан в .env}"
|
||||
: "${SERVER_NAME:?SERVER_NAME не задан в .env}"
|
||||
: "${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD не задан в .env}"
|
||||
|
||||
METRICS_USER="${METRICS_USER:-admin}"
|
||||
METRICS_PASSWORD="${METRICS_PASSWORD:-}"
|
||||
NODE_EXPORTER_PORT="${NODE_EXPORTER_PORT:-9100}"
|
||||
PROMETHEUS_PORT="${PROMETHEUS_PORT:-9090}"
|
||||
ALERTMANAGER_PORT="${ALERTMANAGER_PORT:-9093}"
|
||||
GRAFANA_PORT="${GRAFANA_PORT:-3001}"
|
||||
CRITICAL_UNITS="${CRITICAL_UNITS:-headscale.service}"
|
||||
CRITICAL_CONTAINERS="${CRITICAL_CONTAINERS:-}"
|
||||
|
||||
if ! ip -o addr 2>/dev/null | grep -qF " ${BIND_IP}/"; then
|
||||
warn "адрес ${BIND_IP} не найден на интерфейсах сервера — bind может не подняться"
|
||||
fi
|
||||
|
||||
# --- 2. Рендер конфигов в rendered/ ------------------------------------------
|
||||
mkdir -p rendered
|
||||
|
||||
# 2.1 basic auth: web.yml (bcrypt) + файл пароля для scrape-конфигов Prometheus
|
||||
bcrypt_hash() {
|
||||
if command -v htpasswd >/dev/null 2>&1; then
|
||||
htpasswd -nbB x "$1" | cut -d: -f2
|
||||
else
|
||||
docker run --rm httpd:2.4-alpine htpasswd -nbB x "$1" | cut -d: -f2 \
|
||||
|| die "не удалось сгенерировать bcrypt-хеш: поставь apache2-utils (htpasswd) или дай docker доступ в сеть"
|
||||
fi
|
||||
}
|
||||
|
||||
printf '%s' "${METRICS_PASSWORD}" > rendered/password
|
||||
if [ -n "${METRICS_PASSWORD}" ]; then
|
||||
HASH="$(bcrypt_hash "${METRICS_PASSWORD}")"
|
||||
[ -n "${HASH}" ] || die "пустой bcrypt-хеш"
|
||||
printf 'basic_auth_users:\n %s: %s\n' "${METRICS_USER}" "${HASH}" > rendered/web.yml
|
||||
AUTH_ENABLED=1
|
||||
ok "basic auth включён (пользователь ${METRICS_USER})"
|
||||
else
|
||||
printf '# basic auth отключён: METRICS_PASSWORD пуст\n' > rendered/web.yml
|
||||
AUTH_ENABLED=0
|
||||
warn "METRICS_PASSWORD пуст — Prometheus/Alertmanager/node_exporter доступны без аутентификации"
|
||||
fi
|
||||
chmod 644 rendered/password rendered/web.yml
|
||||
|
||||
# 2.2 prometheus.yml из шаблона
|
||||
SED_ARGS=(
|
||||
-e "s|__SERVER_NAME__|${SERVER_NAME}|g"
|
||||
-e "s|__BIND_IP__|${BIND_IP}|g"
|
||||
-e "s|__NODE_EXPORTER_PORT__|${NODE_EXPORTER_PORT}|g"
|
||||
-e "s|__METRICS_USER__|${METRICS_USER}|g"
|
||||
)
|
||||
if [ "${AUTH_ENABLED}" = 1 ]; then
|
||||
SED_ARGS+=(-e 's|^#AUTH#||')
|
||||
else
|
||||
SED_ARGS+=(-e '/^#AUTH#/d')
|
||||
fi
|
||||
sed "${SED_ARGS[@]}" prometheus/prometheus.yml.tpl > rendered/prometheus.yml
|
||||
ok "rendered/prometheus.yml"
|
||||
|
||||
# 2.3 правила из .env: SystemdUnitDown + ContainerDown
|
||||
{
|
||||
echo "# Сгенерировано deploy.sh из .env — не редактировать вручную."
|
||||
echo "groups:"
|
||||
echo " - name: generated"
|
||||
echo " rules:"
|
||||
any=0
|
||||
for u in $(printf '%s' "${CRITICAL_UNITS}" | tr '|,' ' '); do
|
||||
any=1
|
||||
cat <<EOF
|
||||
- alert: SystemdUnitDown
|
||||
expr: node_systemd_unit_state{name="${u}",state="active"} == 0 or absent(node_systemd_unit_state{name="${u}"})
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
unit: ${u}
|
||||
annotations:
|
||||
summary: 'systemd-юнит ${u} не активен'
|
||||
description: 'Юнит ${u} не в состоянии active (или метрика недоступна) дольше 1 минуты.'
|
||||
EOF
|
||||
done
|
||||
for c in $(printf '%s' "${CRITICAL_CONTAINERS}" | tr '|,' ' '); do
|
||||
any=1
|
||||
cat <<EOF
|
||||
- alert: ContainerDown
|
||||
expr: absent(container_last_seen{name=~".*${c}.*"}) or (time() - max(container_last_seen{name=~".*${c}.*"})) > 120
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
container: ${c}
|
||||
annotations:
|
||||
summary: 'Контейнер ${c} не работает'
|
||||
description: 'Контейнер с именем ~ ".*${c}.*" отсутствует или не отвечает дольше 2 минут.'
|
||||
EOF
|
||||
done
|
||||
[ "${any}" = 1 ] || echo " []"
|
||||
} > rendered/generated-rules.yml
|
||||
ok "rendered/generated-rules.yml (юниты: ${CRITICAL_UNITS}; контейнеры: ${CRITICAL_CONTAINERS:-нет})"
|
||||
|
||||
# --- 3. Валидация конфигурации Prometheus ------------------------------------
|
||||
echo "— promtool check config…"
|
||||
docker compose run --rm --no-deps --entrypoint promtool prometheus \
|
||||
check config /etc/prometheus/prometheus.yml \
|
||||
|| die "конфигурация Prometheus не прошла валидацию"
|
||||
|
||||
# --- 4. Запуск ----------------------------------------------------------------
|
||||
echo "— docker compose up -d…"
|
||||
docker compose up -d --remove-orphans
|
||||
# перечитать конфиги, если контейнеры уже работали (рендер мог измениться)
|
||||
docker compose kill -s HUP prometheus alertmanager >/dev/null 2>&1 || true
|
||||
|
||||
# --- 5. Smoke-тесты -----------------------------------------------------------
|
||||
echo "— smoke-тесты (до 40 секунд на сервис)…"
|
||||
AUTH=()
|
||||
[ -n "${METRICS_PASSWORD}" ] && AUTH=(-u "${METRICS_USER}:${METRICS_PASSWORD}")
|
||||
|
||||
http_check() { # имя url use_auth(yes|no)
|
||||
local name="$1" url="$2" use_auth="${3:-yes}" i
|
||||
local opts=()
|
||||
[ "${use_auth}" = yes ] && opts=("${AUTH[@]}")
|
||||
for i in $(seq 1 20); do
|
||||
if curl -fsS -o /dev/null --max-time 3 "${opts[@]}" "${url}"; then
|
||||
ok "${name} ${url}"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
fail "${name} ${url} — недоступен (docker compose logs ${name})"
|
||||
}
|
||||
|
||||
http_check node_exporter "http://${BIND_IP}:${NODE_EXPORTER_PORT}/metrics"
|
||||
http_check prometheus "http://${BIND_IP}:${PROMETHEUS_PORT}/-/healthy"
|
||||
http_check alertmanager "http://${BIND_IP}:${ALERTMANAGER_PORT}/-/healthy"
|
||||
http_check grafana "http://${BIND_IP}:${GRAFANA_PORT}/api/health" no
|
||||
|
||||
cadvisor_ok=0
|
||||
for i in $(seq 1 10); do
|
||||
if docker compose exec -T cadvisor wget -q -O /dev/null http://localhost:8080/healthz 2>/dev/null; then
|
||||
cadvisor_ok=1; break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "${cadvisor_ok}" = 1 ]; then
|
||||
ok "cadvisor внутренний /healthz (наружу не публикуется)"
|
||||
else
|
||||
fail "cadvisor /healthz — недоступен (docker compose logs cadvisor)"
|
||||
fi
|
||||
|
||||
# --- 6. Итог -------------------------------------------------------------------
|
||||
echo
|
||||
if [ "${FAILED}" = 0 ]; then
|
||||
echo "Готово. Сервисы:"
|
||||
echo " Grafana: http://${BIND_IP}:${GRAFANA_PORT} (admin / GRAFANA_ADMIN_PASSWORD)"
|
||||
echo " Prometheus: http://${BIND_IP}:${PROMETHEUS_PORT}"
|
||||
echo " Alertmanager: http://${BIND_IP}:${ALERTMANAGER_PORT} (API алертов: /api/v2/alerts)"
|
||||
else
|
||||
echo "Часть проверок провалилась — смотри вывод выше и docker compose logs."
|
||||
exit 1
|
||||
fi
|
||||
121
docker-compose.yml
Normal file
121
docker-compose.yml
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
name: monitoring
|
||||
|
||||
services:
|
||||
node_exporter:
|
||||
image: prom/node-exporter:v1.9.1
|
||||
container_name: node_exporter
|
||||
restart: unless-stopped
|
||||
# host-сеть обязательна для настоящих хостовых сетевых метрик:
|
||||
# из bridge-сети виден только netns контейнера
|
||||
network_mode: host
|
||||
pid: host
|
||||
# root нужен systemd-коллектору для чтения состояния юнитов через D-Bus
|
||||
user: root
|
||||
volumes:
|
||||
- /:/host:ro,rslave
|
||||
- /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro
|
||||
- ./rendered/web.yml:/etc/node_exporter/web.yml:ro
|
||||
command:
|
||||
- --path.rootfs=/host
|
||||
- --path.procfs=/host/proc
|
||||
- --path.sysfs=/host/sys
|
||||
- --web.listen-address=${BIND_IP:?BIND_IP не задан — заполни .env}:${NODE_EXPORTER_PORT:-9100}
|
||||
- --web.config.file=/etc/node_exporter/web.yml
|
||||
- --collector.systemd
|
||||
# без фильтра коллектор тащит сотни юнитов; разделитель списка — «|»
|
||||
- --collector.systemd.unit-include=^(${CRITICAL_UNITS:-headscale.service})$$
|
||||
|
||||
cadvisor:
|
||||
image: gcr.io/cadvisor/cadvisor:v0.52.1
|
||||
container_name: cadvisor
|
||||
restart: unless-stopped
|
||||
# порт наружу не публикуется намеренно: у cAdvisor нет аутентификации,
|
||||
# Prometheus ходит к нему по внутренней сети
|
||||
privileged: true
|
||||
devices:
|
||||
- /dev/kmsg
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker:/var/lib/docker:ro
|
||||
- /dev/disk:/dev/disk:ro
|
||||
command:
|
||||
- --docker_only=true
|
||||
- --housekeeping_interval=30s
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.5.0
|
||||
container_name: prometheus
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- cadvisor
|
||||
- alertmanager
|
||||
ports:
|
||||
- "${BIND_IP:?}:${PROMETHEUS_PORT:-9090}:9090"
|
||||
volumes:
|
||||
- ./rendered/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- ./prometheus/rules/alerts.yml:/etc/prometheus/rules/alerts.yml:ro
|
||||
- ./rendered/generated-rules.yml:/etc/prometheus/rules/generated.yml:ro
|
||||
- ./rendered/web.yml:/etc/prometheus/web.yml:ro
|
||||
- ./rendered/password:/etc/prometheus/auth/password:ro
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- --config.file=/etc/prometheus/prometheus.yml
|
||||
- --storage.tsdb.path=/prometheus
|
||||
- --storage.tsdb.retention.time=30d
|
||||
- --web.config.file=/etc/prometheus/web.yml
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:v0.28.1
|
||||
container_name: alertmanager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${BIND_IP:?}:${ALERTMANAGER_PORT:-9093}:9093"
|
||||
environment:
|
||||
TG_BOT_TOKEN: ${TG_BOT_TOKEN:-}
|
||||
TG_CHAT_ID: ${TG_CHAT_ID:-}
|
||||
# entrypoint рендерит alertmanager.yml из шаблона (подстановка секретов из env),
|
||||
# секреты не появляются в файлах ни в репо, ни на диске
|
||||
entrypoint: ["/bin/sh", "/etc/alertmanager/entrypoint.sh"]
|
||||
volumes:
|
||||
- ./alertmanager:/etc/alertmanager:ro
|
||||
- ./rendered/web.yml:/etc/alertmanager/web.yml:ro
|
||||
- alertmanager_data:/alertmanager
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.0.2
|
||||
container_name: grafana
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- prometheus
|
||||
ports:
|
||||
- "${BIND_IP:?}:${GRAFANA_PORT:-3001}:3000"
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: admin
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD не задан — заполни .env}
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_ANALYTICS_REPORTING_ENABLED: "false"
|
||||
# прокидываются в provisioning datasource (basic auth к Prometheus)
|
||||
METRICS_USER: ${METRICS_USER:-admin}
|
||||
METRICS_PASSWORD: ${METRICS_PASSWORD:-}
|
||||
volumes:
|
||||
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||
- grafana_data:/var/lib/grafana
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
alertmanager_data:
|
||||
grafana_data:
|
||||
|
||||
networks:
|
||||
monitoring:
|
||||
driver: bridge
|
||||
12
grafana/provisioning/dashboards/dashboards.yml
Normal file
12
grafana/provisioning/dashboards/dashboards.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: monitoring
|
||||
folder: Monitoring
|
||||
type: file
|
||||
disableDeletion: false
|
||||
allowUiUpdates: true
|
||||
updateIntervalSeconds: 120
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards/json
|
||||
foldersFromFilesStructure: false
|
||||
817
grafana/provisioning/dashboards/json/docker-containers.json
Normal file
817
grafana/provisioning/dashboards/json/docker-containers.json
Normal file
|
|
@ -0,0 +1,817 @@
|
|||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "Prometheus",
|
||||
"description": "Prometheus as the datasource is obligatory",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "7.4.5"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "graph",
|
||||
"name": "Graph",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "prometheus",
|
||||
"name": "Prometheus",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "table",
|
||||
"name": "Table",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": 14282,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1617715580880,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 8,
|
||||
"panels": [],
|
||||
"title": "CPU",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 15,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_cpu_usage_seconds_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name) *100",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "CPU Usage",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "percent",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 11,
|
||||
"panels": [],
|
||||
"title": "Memory",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 9,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(container_memory_rss{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Memory Usage",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 9
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 14,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(container_memory_cache{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Memory Cached",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
},
|
||||
"id": 2,
|
||||
"panels": [],
|
||||
"title": "Network",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"hideEmpty": false,
|
||||
"hideZero": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"sideWidth": null,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_network_receive_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Received Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:674",
|
||||
"format": "Bps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:675",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 18
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 6,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_network_transmit_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Sent Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:832",
|
||||
"format": "Bps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:833",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 26
|
||||
},
|
||||
"id": 19,
|
||||
"panels": [],
|
||||
"title": "Misc",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null,
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "id"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.width",
|
||||
"value": 260
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Running"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "d"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.displayMode",
|
||||
"value": "color-text"
|
||||
},
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "dark-green",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 27
|
||||
},
|
||||
"id": 17,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"sortBy": []
|
||||
},
|
||||
"pluginVersion": "7.4.5",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "(time() - container_start_time_seconds{instance=~\"$host\",name=~\"$container\",name=~\".+\"})/86400",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Containers Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterFieldsByName",
|
||||
"options": {
|
||||
"include": {
|
||||
"names": [
|
||||
"container_label_com_docker_compose_project",
|
||||
"container_label_com_docker_compose_project_working_dir",
|
||||
"image",
|
||||
"instance",
|
||||
"name",
|
||||
"Value",
|
||||
"container_label_com_docker_compose_service"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {},
|
||||
"indexByName": {},
|
||||
"renameByName": {
|
||||
"Value": "Running",
|
||||
"container_label_com_docker_compose_project": "Label",
|
||||
"container_label_com_docker_compose_project_working_dir": "Working dir",
|
||||
"container_label_com_docker_compose_service": "Service",
|
||||
"image": "Registry Image",
|
||||
"instance": "Instance",
|
||||
"name": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 27,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"cadvisor",
|
||||
"docker"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": "Prometheus",
|
||||
"definition": "label_values({__name__=~\"container.*\"},instance)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Host",
|
||||
"multi": false,
|
||||
"name": "host",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values({__name__=~\"container.*\"},instance)",
|
||||
"refId": "Prometheus-host-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 5,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": "Prometheus",
|
||||
"definition": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Container",
|
||||
"multi": false,
|
||||
"name": "container",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
|
||||
"refId": "Prometheus-container-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Cadvisor exporter",
|
||||
"uid": "pMEd7m0Mz",
|
||||
"version": 1,
|
||||
"description": "Simple exporter for cadvisor only"
|
||||
}
|
||||
15536
grafana/provisioning/dashboards/json/node-exporter-full.json
Normal file
15536
grafana/provisioning/dashboards/json/node-exporter-full.json
Normal file
File diff suppressed because it is too large
Load diff
19
grafana/provisioning/datasources/prometheus.yml
Normal file
19
grafana/provisioning/datasources/prometheus.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Автопровижининг источника данных. METRICS_USER/METRICS_PASSWORD
|
||||
# приходят из окружения контейнера (Grafana разворачивает $VAR в provisioning).
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
uid: prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: false
|
||||
basicAuth: true
|
||||
basicAuthUser: $METRICS_USER
|
||||
secureJsonData:
|
||||
basicAuthPassword: $METRICS_PASSWORD
|
||||
jsonData:
|
||||
timeInterval: 30s
|
||||
httpMethod: POST
|
||||
43
prometheus/prometheus.yml.tpl
Normal file
43
prometheus/prometheus.yml.tpl
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Шаблон: deploy.sh рендерит его в rendered/prometheus.yml.
|
||||
# __ПЛЕЙСХОЛДЕРЫ__ подставляются из .env; строки с префиксом #AUTH#
|
||||
# включаются только при непустом METRICS_PASSWORD.
|
||||
|
||||
global:
|
||||
scrape_interval: 30s
|
||||
evaluation_interval: 30s
|
||||
external_labels:
|
||||
server: '__SERVER_NAME__'
|
||||
|
||||
rule_files:
|
||||
- /etc/prometheus/rules/*.yml
|
||||
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets: ['alertmanager:9093']
|
||||
#AUTH# basic_auth:
|
||||
#AUTH# username: '__METRICS_USER__'
|
||||
#AUTH# password_file: /etc/prometheus/auth/password
|
||||
|
||||
scrape_configs:
|
||||
- job_name: prometheus
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
#AUTH# basic_auth:
|
||||
#AUTH# username: '__METRICS_USER__'
|
||||
#AUTH# password_file: /etc/prometheus/auth/password
|
||||
|
||||
# node_exporter работает в host-сети и слушает BIND_IP
|
||||
- job_name: node
|
||||
static_configs:
|
||||
- targets: ['__BIND_IP__:__NODE_EXPORTER_PORT__']
|
||||
#AUTH# basic_auth:
|
||||
#AUTH# username: '__METRICS_USER__'
|
||||
#AUTH# password_file: /etc/prometheus/auth/password
|
||||
|
||||
# cAdvisor доступен только по внутренней compose-сети, без auth
|
||||
- job_name: cadvisor
|
||||
static_configs:
|
||||
- targets: ['cadvisor:8080']
|
||||
|
||||
# Сюда добавляются удалённые серверы (scrape по tailnet) — см. README
|
||||
65
prometheus/rules/alerts.yml
Normal file
65
prometheus/rules/alerts.yml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Статические правила алертов (одинаковые для всех серверов).
|
||||
# Правила, зависящие от .env (SystemdUnitDown, ContainerDown),
|
||||
# генерирует deploy.sh в rendered/generated-rules.yml.
|
||||
|
||||
groups:
|
||||
- name: host
|
||||
rules:
|
||||
- alert: InstanceDown
|
||||
expr: up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: 'Экспортер {{ $labels.job }} недоступен'
|
||||
description: 'Таргет {{ $labels.instance }} (job={{ $labels.job }}) недоступен дольше 2 минут.'
|
||||
|
||||
- alert: HighCPU
|
||||
expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'CPU > 90%'
|
||||
description: 'Загрузка CPU на {{ $labels.instance }}: {{ $value | humanize }}% дольше 5 минут.'
|
||||
|
||||
- alert: HighRAM
|
||||
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'RAM > 90%'
|
||||
description: 'Использование памяти на {{ $labels.instance }}: {{ $value | humanize }}% дольше 5 минут.'
|
||||
|
||||
- alert: DiskFull
|
||||
expr: >
|
||||
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|ramfs|overlay|squashfs|iso9660"}
|
||||
/ node_filesystem_size_bytes) * 100 > 85
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'Диск {{ $labels.mountpoint }} заполнен более чем на 85%'
|
||||
description: 'Раздел {{ $labels.mountpoint }} ({{ $labels.device }}) на {{ $labels.instance }}: занято {{ $value | humanize }}%.'
|
||||
|
||||
- alert: DiskFull
|
||||
expr: >
|
||||
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|ramfs|overlay|squashfs|iso9660"}
|
||||
/ node_filesystem_size_bytes) * 100 > 95
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: 'Диск {{ $labels.mountpoint }} заполнен более чем на 95%'
|
||||
description: 'Раздел {{ $labels.mountpoint }} ({{ $labels.device }}) на {{ $labels.instance }}: занято {{ $value | humanize }}%. Срочно освободи место.'
|
||||
|
||||
- alert: HighTemp
|
||||
# только сенсоры CPU; если метрик нет вообще — проверь модули coretemp/k10temp (см. README)
|
||||
expr: max by (instance, chip) (node_hwmon_temp_celsius{chip=~".*coretemp.*|.*k10temp.*"}) > 80
|
||||
for: 3m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'Температура CPU > 80°C'
|
||||
description: 'Сенсор {{ $labels.chip }} на {{ $labels.instance }}: {{ $value | humanize }}°C дольше 3 минут.'
|
||||
Loading…
Add table
Add a link
Reference in a new issue