Перейти к содержимому

Уведомление о деградации RAID-массива

RAID придуман, чтобы пережить смерть диска без простоя — но только если вы вовремя замените выпавший диск. Проблема в том, что деградированный массив работает почти незаметно: сервис жив, приложение отвечает, а на самом деле у вас уже нет избыточности, и смерть второго диска означает потерю данных. Узнать о выпавшем диске нужно в тот же час, а не через неделю на post-mortem.

Скрипт раз в 15 минут проверяет здоровье хранилища и присылает уведомление, когда:

  • массив деградировал или пересобирается (/proc/mdstat + mdadm --detail) — высокий приоритет;
  • SMART сообщает о плохом здоровье диска (smartctl -H) — высокий приоритет;
  • всё снова clean — сообщение о восстановлении.

Как и в рецепте про место на диске, мы храним трёхуровневое состояние (ok/warn/crit) в /var/lib/notifly-raid/, чтобы не получать одно и то же сообщение каждые 15 минут.

Скрипту нужны пакеты mdadm и smartmontools (smartctl). Сохраните как /usr/local/bin/notifly-raid-check:

#!/usr/bin/env bash
set -eu
set -a; source /etc/notifly.env; set +a
STATE_DIR=/var/lib/notifly-raid
mkdir -p "$STATE_DIR"
HOST=$(hostname -s)
# --- Проверяем каждый программный RAID-массив по /proc/mdstat ---
for MD in /sys/block/md*; do
[ -e "$MD" ] || continue
DEV=$(basename "$MD")
NODE="/dev/$DEV"
DETAIL=$(mdadm --detail "$NODE" 2>/dev/null || true)
STATE=$(echo "$DETAIL" | awk -F: '/State :/ {sub(/^[ \t]+/, "", $2); print $2; exit}')
LEVEL_RAID=$(echo "$DETAIL" | awk -F: '/Raid Level :/ {gsub(/[ \t]/, "", $2); print $2; exit}')
STATE_FILE="$STATE_DIR/$DEV"
LAST=$(cat "$STATE_FILE" 2>/dev/null || echo ok)
LEVEL=ok
# active/clean без флагов — норма; всё остальное считаем проблемой
case "$STATE" in
*degraded*|*FAILED*|*failed*) LEVEL=crit ;;
*recovering*|*resyncing*|*rebuild*|*resync*) LEVEL=warn ;;
esac
if [ "$LEVEL" != "$LAST" ]; then
case "$LEVEL" in
warn)
/usr/local/bin/notifly-send \
"⚠️ RAID $DEV пересобирается — $HOST" \
"$LEVEL_RAID $DEV: состояние «$STATE». Идёт восстановление, дождитесь окончания и проверьте диск." 5
;;
crit)
/usr/local/bin/notifly-send \
"🔥 RAID $DEV ДЕГРАДИРОВАЛ — $HOST" \
"$LEVEL_RAID $DEV: состояние «$STATE». Массив без избыточности! Срочно замените выпавший диск." 10
;;
ok)
/usr/local/bin/notifly-send \
"✅ RAID $DEV снова в норме — $HOST" \
"$LEVEL_RAID $DEV: состояние «$STATE» (clean)." 4
;;
esac
echo "$LEVEL" > "$STATE_FILE"
fi
done
# --- Проверяем SMART-здоровье физических дисков ---
for DISK in /dev/sd?; do
[ -e "$DISK" ] || continue
NAME=$(basename "$DISK")
HEALTH=$(smartctl -H "$DISK" 2>/dev/null \
| awk -F: '/(overall-health|SMART Health Status)/ {gsub(/[ \t]/, "", $2); print $2; exit}')
[ -n "$HEALTH" ] || continue
STATE_FILE="$STATE_DIR/smart-$NAME"
LAST=$(cat "$STATE_FILE" 2>/dev/null || echo ok)
# PASSED/OK — норма, всё остальное (FAILED!) — критично
LEVEL=ok
case "$HEALTH" in
PASSED|OK) LEVEL=ok ;;
*) LEVEL=crit ;;
esac
if [ "$LEVEL" != "$LAST" ]; then
case "$LEVEL" in
crit)
/usr/local/bin/notifly-send \
"🔥 SMART: диск $NAME умирает — $HOST" \
"smartctl -H $DISK вернул «$HEALTH». Замените диск, пока массив ещё цел." 9
;;
ok)
/usr/local/bin/notifly-send \
"✅ SMART диска $NAME в норме — $HOST" \
"smartctl -H $DISK снова «$HEALTH»." 4
;;
esac
echo "$LEVEL" > "$STATE_FILE"
fi
done

Сделайте исполняемым:

Окно терминала
sudo chmod +x /usr/local/bin/notifly-raid-check
Окно терминала
sudo crontab -e

Добавьте строку:

*/15 * * * * /usr/local/bin/notifly-raid-check >/dev/null 2>&1

/etc/systemd/system/notifly-raid.service:

[Unit]
Description=Notifly RAID health check
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notifly-raid-check

/etc/systemd/system/notifly-raid.timer:

[Unit]
Description=Run notifly-raid every 15 minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target

Активируем:

Окно терминала
sudo systemctl daemon-reload
sudo systemctl enable --now notifly-raid.timer
sudo systemctl list-timers notifly-raid.timer

На Windows аналог /proc/mdstat — это Get-PhysicalDisk (HealthStatus каждого диска) и Storage Spaces: виртуальные диски проверяются через Get-VirtualDisk. Скрипт шлёт тревогу, если хоть один диск или виртуальный том не Healthy. Используется общая функция Send-Notifly из шаблона sysadmin/index.

C:\scripts\Notifly-Raid-Check.ps1
. C:\scripts\Notifly.ps1
$StateDir = "C:\ProgramData\Notifly\raid-state"
New-Item -ItemType Directory -Path $StateDir -Force | Out-Null
$Host = $env:COMPUTERNAME
function Get-Level([string]$health) {
switch ($health) {
"Healthy" { "ok" }
"Warning" { "warn" }
default { "crit" } # Unhealthy и всё неизвестное
}
}
# --- Здоровье физических дисков ---
Get-PhysicalDisk | ForEach-Object {
$name = "phys-$($_.DeviceId)"
$level = Get-Level $_.HealthStatus
$stateFile = Join-Path $StateDir "$name.txt"
$last = if (Test-Path $stateFile) { (Get-Content $stateFile -Raw).Trim() } else { "ok" }
if ($level -eq $last) { return }
switch ($level) {
"warn" { Send-Notifly -Title "⚠️ Диск $($_.FriendlyName) — предупреждение — $Host" `
-Message "HealthStatus: $($_.HealthStatus). Проверьте SMART и запланируйте замену." -Priority 5 }
"crit" { Send-Notifly -Title "🔥 Диск $($_.FriendlyName) УМИРАЕТ — $Host" `
-Message "HealthStatus: $($_.HealthStatus). Срочно замените диск, пока пул цел!" -Priority 9 }
"ok" { Send-Notifly -Title "✅ Диск $($_.FriendlyName) в норме — $Host" `
-Message "HealthStatus: $($_.HealthStatus)." -Priority 4 }
}
$level | Set-Content $stateFile
}
# --- Здоровье виртуальных дисков Storage Spaces ---
Get-VirtualDisk -ErrorAction SilentlyContinue | ForEach-Object {
$name = "vdisk-$($_.FriendlyName)"
$level = Get-Level $_.HealthStatus
$stateFile = Join-Path $StateDir "$($name -replace '[\\/:]','_').txt"
$last = if (Test-Path $stateFile) { (Get-Content $stateFile -Raw).Trim() } else { "ok" }
if ($level -eq $last) { return }
switch ($level) {
"warn" { Send-Notifly -Title "⚠️ Том $($_.FriendlyName) деградирует — $Host" `
-Message "HealthStatus: $($_.HealthStatus), OperationalStatus: $($_.OperationalStatus)." -Priority 5 }
"crit" { Send-Notifly -Title "🔥 Том $($_.FriendlyName) ДЕГРАДИРОВАЛ — $Host" `
-Message "HealthStatus: $($_.HealthStatus). Пул без избыточности — срочно замените диск!" -Priority 10 }
"ok" { Send-Notifly -Title "✅ Том $($_.FriendlyName) в норме — $Host" `
-Message "HealthStatus: $($_.HealthStatus) (Healthy)." -Priority 4 }
}
$level | Set-Content $stateFile
}

Регистрация в Task Scheduler (от администратора, каждые 15 минут от SYSTEM):

Окно терминала
$Action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Raid-Check.ps1"
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 15)
$Princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "Notifly RAID Check" `
-Action $Action -Trigger $Trigger -Principal $Princ -Description "Notifly: проверка RAID и SMART"
  • Выпавший диск не остаётся незамеченным. Деградированный массив работает «как обычно», и без уведомления вы узнаёте о проблеме, только когда умирает второй диск — то есть когда уже поздно.
  • SMART предупреждает заранее. Многие диски сообщают о плохом здоровье ещё до полного отказа — этого времени хватает, чтобы спокойно заказать замену.
  • Трёхуровневая логика (okwarncrit) исключает спам: пока состояние не меняется — уведомлений нет, а пересборку и восстановление вы видите отдельно.
  • Прикладывать полный вывод cat /proc/mdstat и mdadm --detail /dev/mdX через поле extras, чтобы видеть, какой именно диск выпал, прямо в приложении.
  • Добавить heartbeat-проверку: если сам скрипт перестанет запускаться (например, smartctl пропал), молчание тоже станет заметным.
  • Мониторить скорость пересборки (speed/finish из /proc/mdstat) и слать отдельное сообщение по её завершении — см. также мониторинг сервисов.