monwatch: сторож мониторинга fr1 на rpi — раз в час health-проверка, тревога в Telegram напрямую
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b881d861f0
commit
facc6c1e65
@@ -2,9 +2,37 @@
|
||||
# Forced command для пуш-ключей хостов за NAT (authorized_keys: command="~/bin/pushgw-ingest <instance>").
|
||||
# Клиент передаёт только имя job (ssh ... <job>), метрики — в stdin в text-формате Prometheus.
|
||||
# instance задаётся ключом, а не клиентом: хост не может писать метрики от чужого имени.
|
||||
#
|
||||
# Особый «job» `health` — только чтение: внешний сторож (rpi, monwatch) раз в час проверяет,
|
||||
# что сам мониторинг fr1 жив. Отдаёт строки key=value, ничего не меняет.
|
||||
instance="$1"
|
||||
job="$SSH_ORIGINAL_COMMAND"
|
||||
case "$instance" in ""|*[!a-z0-9_-]*) echo "bad instance" >&2; exit 2;; esac
|
||||
|
||||
if [ "$job" = health ]; then
|
||||
code() { curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$1"; }
|
||||
echo "prometheus_healthy=$(code http://127.0.0.1:9090/-/healthy)"
|
||||
echo "alertmanager_healthy=$(code http://127.0.0.1:9093/-/healthy)"
|
||||
echo "pushgateway_healthy=$(code http://127.0.0.1:9091/-/healthy)"
|
||||
# видит ли Prometheus свежие пуши этого хоста — сквозная проверка pushgateway → scrape → TSDB
|
||||
age=$(curl -s --max-time 10 http://127.0.0.1:9090/api/v1/query \
|
||||
--data-urlencode "query=time() - max(push_time_seconds{instance=\"$instance\"})" \
|
||||
| python3 -c 'import json,sys
|
||||
try: print(int(float(json.load(sys.stdin)["data"]["result"][0]["value"][1])))
|
||||
except Exception: print(-1)')
|
||||
echo "push_age_seconds=$age"
|
||||
# счётчики доставки в Telegram (монотонные; сторож сравнивает с прошлым замером)
|
||||
curl -s --max-time 10 http://127.0.0.1:9093/metrics | awk '
|
||||
/^alertmanager_notifications_failed_total\{integration="telegram"/ {f+=$2}
|
||||
/^alertmanager_notifications_total\{integration="telegram"/ {t+=$2}
|
||||
END {printf "tg_notifications_total=%d\ntg_notifications_failed_total=%d\n", t, f}'
|
||||
firing=$(curl -s --max-time 10 http://127.0.0.1:9093/api/v2/alerts?active=true | python3 -c 'import json,sys
|
||||
try: print(len(json.load(sys.stdin)))
|
||||
except Exception: print(-1)')
|
||||
echo "alerts_active=$firing"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$job" in ""|*[!a-z0-9_]*) echo "bad job" >&2; exit 2;; esac
|
||||
exec curl -sS --fail-with-body --max-time 30 -X PUT --data-binary @- \
|
||||
"http://127.0.0.1:9091/metrics/job/$job/instance/$instance"
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# monwatch — внешний сторож мониторинга fr1 («кто сторожит сторожа»).
|
||||
# Весь мониторинг (Prometheus, Alertmanager → Telegram) живёт на fr1: если упадёт fr1 или сломается
|
||||
# цепочка доставки, никаких алертов не будет вообще. Раз в час малина спрашивает fr1 `health`
|
||||
# (тот же ограниченный ключ метрик, режим только чтение) и при проблеме пишет в Telegram
|
||||
# НАПРЯМУЮ — через своего бота (@rasp_contol_bot, токен из /etc/astra-bot.env), минуя fr1.
|
||||
#
|
||||
# Тревога — при поломке, повтор раз в 6 ч, ✅ при восстановлении. Разовый сбой сети
|
||||
# не тревожит: перед тревогой одна повторная проверка через 2 минуты.
|
||||
set -uo pipefail
|
||||
STATE=/var/lib/monwatch; mkdir -p "$STATE"
|
||||
CHAT=${MONWATCH_CHAT:-607015}
|
||||
REPEAT=$((6 * 3600))
|
||||
MAX_PUSH_AGE=${MONWATCH_MAX_PUSH_AGE:-3600} # smart-metrics пушит каждые 15 мин
|
||||
|
||||
health() {
|
||||
timeout 60 ssh -i /etc/metrics-push/id_ed25519 -o UserKnownHostsFile=/etc/metrics-push/known_hosts \
|
||||
-o StrictHostKeyChecking=yes -o BatchMode=yes -o ConnectTimeout=20 rus@161.97.93.252 health 2>&1
|
||||
}
|
||||
|
||||
check() { # печатает проблемы по строке; пусто = всё хорошо
|
||||
local out rc
|
||||
out=$(health); rc=$?
|
||||
if [ $rc -ne 0 ] || ! grep -q '^prometheus_healthy=' <<< "$out"; then
|
||||
echo "fr1 не отвечает на проверку (ssh rc=$rc): $(tail -1 <<< "$out")"; return
|
||||
fi
|
||||
declare -A v; while IFS='=' read -r k val; do v[$k]=$val; done <<< "$out"
|
||||
[ "${v[prometheus_healthy]}" = 200 ] || echo "Prometheus не отвечает (HTTP ${v[prometheus_healthy]})"
|
||||
[ "${v[alertmanager_healthy]}" = 200 ] || echo "Alertmanager не отвечает (HTTP ${v[alertmanager_healthy]}) — алерты не доставляются"
|
||||
[ "${v[pushgateway_healthy]}" = 200 ] || echo "Pushgateway не отвечает (HTTP ${v[pushgateway_healthy]})"
|
||||
local age=${v[push_age_seconds]:--1}
|
||||
if [ "$age" -lt 0 ] || [ "$age" -gt $MAX_PUSH_AGE ]; then
|
||||
echo "Prometheus не видит свежих метрик малины ($([ "$age" -lt 0 ] && echo 'нет данных' || echo "$((age / 60)) мин"))"
|
||||
fi
|
||||
local failed=${v[tg_notifications_failed_total]:-0} prev
|
||||
prev=$(cat "$STATE/tg_failed" 2>/dev/null || echo "$failed")
|
||||
[ "$failed" -gt "$prev" ] && echo "Alertmanager не смог отправить в Telegram $((failed - prev)) уведомл. за последний час"
|
||||
echo "$failed" > "$STATE/tg_failed"
|
||||
}
|
||||
|
||||
tg() {
|
||||
local token
|
||||
token=$(sed -n 's/^TG_TOKEN=//p' /etc/astra-bot.env)
|
||||
curl -s -o /dev/null -w '%{http_code}' --max-time 30 "https://api.telegram.org/bot$token/sendMessage" \
|
||||
--data-urlencode "chat_id=$CHAT" --data-urlencode "text=$1" | grep -q 200 || echo "не удалось отправить в Telegram" >&2
|
||||
}
|
||||
|
||||
problems=$(check)
|
||||
if [ -n "$problems" ]; then
|
||||
sleep 120; problems=$(check) # повторная проверка: не тревожим из-за разового сбоя сети
|
||||
fi
|
||||
now=$(date +%s); state=$(cat "$STATE/state" 2>/dev/null || echo up); last=$(cat "$STATE/last_alert" 2>/dev/null || echo 0)
|
||||
|
||||
if [ -n "$problems" ]; then
|
||||
echo "DOWN: $problems"
|
||||
if [ "$state" != down ] || [ $((now - last)) -ge $REPEAT ]; then
|
||||
tg "🔴 Мониторинг fr1 сломан — алерты от него могут не приходить.
|
||||
$problems
|
||||
|
||||
(сторож на rpi, проверка раз в час; повтор через 6 ч)" && echo "$now" > "$STATE/last_alert"
|
||||
fi
|
||||
echo down > "$STATE/state"
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: мониторинг fr1 в порядке"
|
||||
[ "$state" = down ] && tg "✅ Мониторинг fr1 снова в порядке (сторож на rpi)."
|
||||
echo up > "$STATE/state"
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Watchdog for fr1 monitoring (alerts to Telegram directly, bypassing fr1)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/monwatch
|
||||
TimeoutStartSec=10min
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Hourly check that fr1 monitoring is alive
|
||||
|
||||
[Timer]
|
||||
OnBootSec=5min
|
||||
OnCalendar=hourly
|
||||
RandomizedDelaySec=2min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user