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

Уведомления о падении 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=oneshot
EnvironmentFile=/etc/notifly.env
ExecStart=/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.service
EOF
done
sudo 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-failure
RestartSec=5s
StartLimitBurst=3
StartLimitIntervalSec=120
[Unit]
OnFailure=notifly@%n.service
StartLimitAction=none

Тогда уведомление придёт после третьего падения за 2 минуты — то есть когда авто-восстановление не справляется.

Аналог для Windows-серверов: подписываемся на события Service Control Manager в System Event Log (Event ID 7031, 7034 — сервис упал/перезапущен). Использует Windows Event Trigger в Task Scheduler.

C:\scripts\Notifly-Service-Failed.ps1
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
C:\scripts\Register-NotiflyServiceWatch.ps1
$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-Service-Poll.ps1
. 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.