Уведомления о падении systemd-сервисов
В современных дистрибутивах Linux всё крутится под systemd. Если nginx, postgres, ваш бэкенд или контейнерный рантайм внезапно «упал» — узнать об этом нужно сразу, а не когда позвонят пользователи.
Самое красивое решение — встроить отправку уведомлений прямо в systemd через
OnFailure= — без дополнительных watcher-ов и cron-ов.
Systemd умеет автоматически запускать другой unit, когда основной сервис
переходит в состояние failed. Достаточно один раз создать «универсальный»
unit-нотификатор и подключить его ко всем нужным сервисам через drop-in.
Универсальный нотификатор
Заголовок раздела «Универсальный нотификатор»/etc/systemd/system/notifly@.service:
[Unit]Description=Notify about failed unit %i
[Service]Type=oneshotEnvironmentFile=/etc/notifly.envExecStart=/bin/bash -c '\ STATUS=$(systemctl status %i --no-pager -n 20 | tail -n 20 | sed "s/\"/\\\\\\\"/g"); \ /usr/local/bin/notifly-send \ "🛑 Сервис %i упал на $(hostname -s)" \ "$STATUS" 9'Активируйте:
sudo systemctl daemon-reloadПроверьте отправку «вручную»:
sudo systemctl start notifly@nginx.serviceПодключение к сервисам
Заголовок раздела «Подключение к сервисам»Не нужно редактировать оригинальные unit-файлы. Используйте drop-in:
sudo systemctl edit nginx.serviceВ открывшемся редакторе впишите:
[Unit]OnFailure=notifly@%n.serviceСохраните, затем:
sudo systemctl daemon-reloadАналогично подключите ко всем критичным сервисам:
for s in nginx postgresql redis docker your-backend; do sudo mkdir -p "/etc/systemd/system/${s}.service.d" cat <<EOF | sudo tee "/etc/systemd/system/${s}.service.d/notifly.conf" >/dev/null[Unit]OnFailure=notifly@%n.serviceEOFdonesudo systemctl daemon-reloadСимулируйте падение:
# Сломаем конфиг nginx и попробуем перезапуститьsudo nginx -t || true # проверка реального конфигаsudo systemd-run --unit=test-fail.service /bin/falseЧерез несколько секунд в Notifly прилетит сообщение с заголовком и последними 20 строками статуса упавшего сервиса.
Дополнительно: уведомление об автоматическом перезапуске
Заголовок раздела «Дополнительно: уведомление об автоматическом перезапуске»Если у сервиса включён Restart=on-failure, systemd может молча его перезапустить.
Чтобы тоже это видеть, добавьте OnFailure= + поднимите счётчик:
[Service]Restart=on-failureRestartSec=5sStartLimitBurst=3StartLimitIntervalSec=120
[Unit]OnFailure=notifly@%n.serviceStartLimitAction=noneТогда уведомление придёт после третьего падения за 2 минуты — то есть когда авто-восстановление не справляется.
Windows: PowerShell + Service Control Manager
Заголовок раздела «Windows: PowerShell + Service Control Manager»Аналог для Windows-серверов: подписываемся на события Service Control Manager в System Event Log (Event ID 7031, 7034 — сервис упал/перезапущен). Использует Windows Event Trigger в Task Scheduler.
Скрипт-нотификатор
Заголовок раздела «Скрипт-нотификатор»param([string]$ServiceName, [string]$EventId). C:\scripts\Notifly.ps1
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue$status = if ($svc) { $svc.Status } else { "Unknown" }
# Последние 20 записей из System log по этому сервису$logs = Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Service Control Manager'} ` -MaxEvents 20 -ErrorAction SilentlyContinue | Where-Object { $_.Message -match $ServiceName } | Select-Object -First 5 | ForEach-Object { "$($_.TimeCreated.ToString('HH:mm:ss')) $($_.Message)" } | Out-String
Send-Notifly ` -Title "🛑 Сервис $ServiceName упал на $env:COMPUTERNAME" ` -Message "Статус: $status`nEvent ID: $EventId`n`n$logs" ` -Priority 9Подписка на события критичных сервисов
Заголовок раздела «Подписка на события критичных сервисов»$services = @("Spooler", "W3SVC", "MSSQLSERVER", "nginx")
foreach ($s in $services) { $xml = @"<QueryList> <Query Id="0" Path="System"> <Select Path="System"> *[System[Provider[@Name='Service Control Manager'] and (EventID=7031 or EventID=7034)]] and *[EventData[Data='$s']] </Select> </Query></QueryList>"@ $Trigger = New-ScheduledTaskTrigger -AtStartup $Trigger.Subscription = $xml $Action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Service-Failed.ps1 -ServiceName $s -EventId 7031" Register-ScheduledTask -TaskName "Notifly Watch $s" -Trigger $Trigger -Action $Action ` -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest) ` -Force}Альтернатива: опрос по расписанию
Заголовок раздела «Альтернатива: опрос по расписанию»Если подписка на события по какой-то причине не работает — опрашивайте сервисы каждую минуту:
. C:\scripts\Notifly.ps1$watch = @("Spooler", "W3SVC", "MSSQLSERVER")$state = "C:\ProgramData\Notifly\service-state.json"$prev = if (Test-Path $state) { Get-Content $state | ConvertFrom-Json } else { @{} }$now = @{}foreach ($s in $watch) { $svc = Get-Service -Name $s -ErrorAction SilentlyContinue $now[$s] = if ($svc) { $svc.Status.ToString() } else { "Missing" } if ($prev.$s -and $prev.$s -ne $now[$s] -and $now[$s] -ne "Running") { Send-Notifly -Title "🛑 $s: $($now[$s]) на $env:COMPUTERNAME" ` -Message "Было: $($prev.$s)" -Priority 9 }}$now | ConvertTo-Json | Set-Content $state- Ноль ложных срабатываний: сообщение приходит ровно тогда, когда unit реально
ушёл в
failed. - Без агентов: systemd уже стоит у всех — не нужны Zabbix, Prometheus или Datadog ради простого «упало/не упало».
- Контекст в кармане: в сообщении сразу 20 строк журнала, часто этого достаточно, чтобы понять причину прямо с телефона.
Что улучшить дальше
Заголовок раздела «Что улучшить дальше»- Добавить ссылку «Перезапустить» — отдельный action в Notifly с дип-линком на внутренний админ-портал.
- Сделать разные уровни приоритета: для второстепенных cron-юнитов
priority=4, для prod-баз —priority=10.