Systemd: розширені сценарії керування сервісами

event 08.03.2026 22:00
| category DevOps | person iron_will | comment 0 | visibility 75 | |

Systemd давно перестав бути просто менеджером ініціалізації. Сьогодні це повноцінна екосистема для оркестрації процесів, управління ресурсами, налагодження залежностей і моніторингу сервісів у реальному часі. Незважаючи на це, більшість адміністраторів і DevOps-інженерів використовують лише поверхневий набір можливостей - systemctl start, stop, enable - залишаючи за кадром потужні механізми, що здатні суттєво спростити роботу з production-середовищами.

У цій статті розглянемо розширені сценарії роботи з systemd: від тонкого налаштування unit-файлів і управління залежностями до використання шаблонів, socket-активації та інтеграції з cgroups. Матеріал розрахований на фахівців, які вже знайомі з базовим синтаксисом systemd і хочуть перейти на якісно інший рівень автоматизації та надійності.

Важливо розуміти, що грамотна конфігурація systemd - це не лише питання зручності. Це безпосередньо впливає на час відновлення сервісів після збоїв, споживання системних ресурсів, порядок завантаження та ізоляцію процесів. Неправильно налаштовані залежності між сервісами є однією з найпоширеніших причин тихих відмов у виробничих системах.

Стаття побудована навколо реальних кейсів: мікросервісна архітектура, фонові воркери, сокетні сервери та периодичні задачі - усе те, з чим стикаються інженери щодня.

Анатомія unit-файлу: що насправді важливо

Unit-файл складається з трьох основних секцій: [Unit], [Service] (або інший тип unit), [Install]. Проте в кожній з них є директиви, які рідко потрапляють у туторіали.

Секція [Unit]: залежності та умови

[Unit]
Description=My Application Worker
Documentation=https://docs.example.com
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
BindsTo=postgresql.service

Різниця між Requires, Wants і BindsTo є критичною:

  • Requires - якщо залежність не запустилася, поточний сервіс також не запуститься. Але якщо залежність зупиняється після запуску, поточний сервіс продовжує роботу.
  • BindsTo - жорсткіший варіант: якщо залежність зупиниться з будь-якої причини, systemd автоматично зупинить і поточний сервіс. Ідеально для sidecar-процесів.
  • Wants - м'яка залежність. Якщо залежність не запустилась, поточний сервіс усе одно буде запущений.

Директива After визначає лише порядок запуску, але не залежність. Це поширена помилка: якщо вказати тільки After=postgresql.service без Requires, сервіс запуститься навіть якщо PostgreSQL не піднявся.

Умовні директиви

Systemd підтримує набір умов, які перевіряються перед запуском:

[Unit]
ConditionPathExists=/etc/myapp/config.yaml
ConditionFileNotEmpty=/var/lib/myapp/db.sqlite
AssertPathIsDirectory=/var/log/myapp

Різниця між Condition* і Assert*: при невиконанні умови Condition* сервіс просто пропускається (вважається успішно завершеним), тоді як Assert* генерує помилку. Це важливо для правильного відображення статусу в systemctl status.

Типи запуску та їх вплив на надійність

Type=notify та готовність сервісу

Один з найважливіших параметрів - Type. Він визначає, як systemd розуміє, що сервіс "готовий до роботи".

[Service]
Type=notify
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yaml
NotifyAccess=main
WatchdogSec=30s

При Type=notify процес повинен надіслати systemd сигнал готовності через sd_notify(3). Це гарантує, що залежні сервіси не запустяться, поки ваш застосунок справді не готовий приймати з'єднання - а не просто запустився.

У Python-застосунку це реалізується так:

import systemd.daemon

def main():
    # ініціалізація застосунку
    db = connect_database()
    cache = init_cache()
    
    # повідомляємо systemd, що ми готові
    systemd.daemon.notify('READY=1')
    
    # основний цикл
    serve_requests()

Для Go використовується пакет github.com/coreos/go-systemd/daemon:

daemon.SdNotify(false, daemon.SdNotifyReady)

Watchdog: автоматичний перезапуск при зависанні

WatchdogSec=30s активує механізм watchdog. Процес зобов'язаний регулярно надсилати WATCHDOG=1 через sd_notify. Якщо він цього не робить протягом вказаного інтервалу - systemd перезапускає сервіс. Це ефективніше, ніж стандартний Restart=always, оскільки реагує на зависання, а не лише на падіння.

Шаблони unit-файлів

Одна з найпотужніших, але недооцінених можливостей - шаблонні unit-файли. Вони дозволяють запускати кілька екземплярів одного сервісу з різними параметрами.

Файл називається з символом @: /etc/systemd/system/worker@.service

[Unit]
Description=Application Worker Instance %i
After=network.target rabbitmq.service
Requires=rabbitmq.service

[Service]
Type=simple
User=appuser
Environment=WORKER_ID=%i
Environment=QUEUE_NAME=queue-%i
ExecStart=/usr/local/bin/worker --id %i --queue queue-%i
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=worker-%i

[Install]
WantedBy=multi-user.target

Запуск кількох екземплярів:

systemctl enable --now worker@1.service
systemctl enable --now worker@2.service
systemctl enable --now worker@3.service

Перегляд логів конкретного воркера:

journalctl -u worker@2.service -f

Корисні специфікатори шаблонів:

СпецифікаторЗначення
%i Рядок між @ і .service
%n Повна назва unit
%H Ім'я хоста
%u Ім'я користувача (з User=)
Socket-активація: запуск за вимогою

Socket-активація - це механізм, при якому systemd відкриває сокет і передає його застосунку лише тоді, коли надходить перше з'єднання. Це зменшує час завантаження системи та економить ресурси.

Кейс: HTTP API-сервер із socket-активацією

Файл /etc/systemd/system/myapi.socket:

[Unit]
Description=My API Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target

Файл /etc/systemd/system/myapi.service:

[Unit]
Description=My API Service
Requires=myapi.socket

[Service]
Type=notify
ExecStart=/usr/local/bin/myapi
StandardInput=socket

У застосунку на Go отримання сокету від systemd:

import "github.com/coreos/go-systemd/activation"

func main() {
    listeners, err := activation.Listeners()
    if err != nil || len(listeners) == 0 {
        // fallback: відкриваємо сокет самостійно
        ln, _ = net.Listen("tcp", ":8080")
    } else {
        ln = listeners[0]
    }
    http.Serve(ln, handler)
}

Перевага socket-активації також у тому, що при перезапуску сервісу вхідні з'єднання не відхиляються — вони буферизуються в сокеті systemd до моменту готовності нового процесу.

Управління ресурсами через cgroups

Systemd повністю інтегрований з cgroups v2, що дозволяє встановлювати жорсткі ліміти на ресурси для кожного сервісу.

[Service]
# CPU
CPUQuota=50%
CPUWeight=100

# Пам'ять
MemoryMax=512M
MemorySwapMax=0

# I/O
IOWeight=50
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 20M

# Мережа (потребує додаткової конфігурації)
IPAddressAllow=10.0.0.0/8 192.168.0.0/16
IPAddressDeny=any

MemorySwapMax=0 повністю забороняє свопування для цього сервісу - критично важлива опція для latency-sensitive застосунків.

Перевірити поточне споживання ресурсів:

systemctl status myapp.service
# або детальніше:
systemd-cgtop

Ізоляція та безпека

Systemd надає вбудовані механізми пісочниці, які не вимагають ні Docker, ні іншого контейнеризатора.

[Service]
# Файлова система
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/myapp /var/log/myapp
PrivateTmp=true

# Мережа
PrivateNetwork=false
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Системні виклики
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallArchitectures=native

# Привілеї
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

# Простори імен
PrivateUsers=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

ProtectSystem=strict монтує /usr, /boot та /etc як read-only. У поєднанні з ReadWritePaths це фактично задає whitelist директорій, до яких сервіс має доступ на запис.

Перевірка рівня безпеки сервісу:

systemd-analyze security myapp.service

Команда виводить оцінку від 0 (максимальний захист) до 10 (незахищений) з переліком конкретних рекомендацій.

Таймери як заміна cron

Systemd-таймери є потужнішою альтернативою cron: вони підтримують залежності, логуються через journald і дозволяють точно контролювати поведінку при пропущених запусках.

Файл /etc/systemd/system/backup.timer:

[Unit]
Description=Daily Database Backup Timer

[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=600
Persistent=true
AccuracySec=1min

[Install]
WantedBy=timers.target

Файл /etc/systemd/system/backup.service:

[Unit]
Description=Database Backup
After=postgresql.service
Requires=postgresql.service

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal

Persistent=true — якщо система була вимкнена під час запланованого запуску, таймер спрацює одразу після завантаження. RandomizedDelaySec розподіляє навантаження, якщо кілька систем мають однакові таймери (актуально для fleet-адміністрування).

Перегляд усіх активних таймерів:

systemctl list-timers --all

Drop-in файли: розширення без редагування оригіналу

Drop-in файли дозволяють розширювати конфігурацію systemd-unit без зміни оригінального файлу - це критично важливо при роботі з пакетними менеджерами, де оновлення пакету перезаписує unit-файл.

systemctl edit postgresql.service

Команда відкриває редактор і створює файл /etc/systemd/system/postgresql.service.d/override.conf. Приклад - додавання environment-змінних і лімітів ресурсів до стандартного PostgreSQL unit:

[Service]
Environment=PGDATA=/data/postgresql/14/main
LimitNOFILE=65536
MemoryMax=4G
Restart=always
RestartSec=3s

Перегляд фінальної конфігурації з урахуванням усіх drop-in:

systemctl cat postgresql.service

Практичний кейс: мікросервіс із повним набором best practices

Зведемо разом усі розглянуті концепції у реалістичний приклад - production-ready unit для Node.js API-сервісу:

[Unit]
Description=Node.js Payment API
Documentation=https://wiki.internal/payment-api
After=network-online.target redis.service
Requires=redis.service
StartLimitIntervalSec=60s
StartLimitBurst=3

[Service]
Type=notify
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/payment-api

# Змінні середовища
EnvironmentFile=/etc/payment-api/env
Environment=NODE_ENV=production

# Запуск та зупинка
ExecStartPre=/usr/local/bin/node --check /opt/payment-api/index.js
ExecStart=/usr/local/bin/node /opt/payment-api/index.js
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGTERM
KillMode=mixed
TimeoutStopSec=30s

# Перезапуск
Restart=on-failure
RestartSec=5s
WatchdogSec=60s

# Ліміти ресурсів
MemoryMax=512M
MemorySwapMax=0
CPUQuota=80%
LimitNOFILE=65536

# Безпека
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/payment-api /var/lib/payment-api
PrivateTmp=true
SystemCallFilter=@system-service

# Логування
StandardOutput=journal
StandardError=journal
SyslogIdentifier=payment-api

[Install]
WantedBy=multi-user.target

ExecStartPre виконує синтаксичну перевірку JS-файлу перед стартом - сервіс не запуститься з некоректним кодом. KillMode=mixed спочатку надсилає SIGTERM головному процесу, а потім SIGKILL усій групі процесів - важливо для Node.js з дочірніми воркерами.

Висновки

Systemd - це не просто init. Це повноцінний інструментарій для управління lifecycle сервісів, їх ізоляції та моніторингу. Грамотне використання залежностей (BindsTo, After, Requires), типів запуску (Type=notify), watchdog-механізмів і cgroups-лімітів дає змогу будувати надійні, безпечні та передбачувані production-системи.

Шаблонні unit-файли усувають дублювання конфігурацій, socket-активація мінімізує час завантаження, а drop-in override-файли забезпечують зручне розширення без конфліктів із пакетним менеджером.

Найголовніше - завжди верифікуйте конфігурацію:

systemd-analyze verify /etc/systemd/system/myapp.service
systemd-analyze security myapp.service
journalctl -u myapp.service --since "1 hour ago"

Ці три команди після кожної зміни unit-файлу мають стати обов'язковим рефлексом. Systemd надає всі інструменти - залишається лише ними скористатися.

Related posts

Kubernetes: сучасна платформа оркестрації контейнерів для production-середовищ

Вступ Kubernetes став де-факто стандартом для запуску контейнеризованих застосунків у production-середовищах. Якщо Docker вирішив проблему пакування застосунку разом із залежностями, то Kubernetes вирішує значно складніше завдання - як масштабувати,...

category DevOps person iron_will event 19/04/2026

Файлові системи ext4, XFS, Btrfs - що обрати для production

Вступ Вибір файлової системи в Linux-середовищі - це не просто технічна деталь, а стратегічне рішення, яке безпосередньо впливає на продуктивність, надійність і масштабованість інфраструктури. У production-системах, де критичними є стабільність і пе...

category DevOps person iron_will event 14/04/2026

Zero Trust архітектура на практиці: принципи, впровадження та технічні кейси

Вступ Класичні моделі кібербезпеки, що базуються на периметрі мережі, давно втратили ефективність. Сучасні ІТ-інфраструктури характеризуються гібридністю, розподіленістю та активним використанням хмарних сервісів. У таких умовах концепція «довіряй,...

category Security person iron_will event 05/04/2026

Керування користувачами та правами доступу в Linux на enterprise-рівні

Вступ У сучасних корпоративних ІТ-інфраструктурах системи на базі Linux є критично важливими компонентами - від веб-серверів і контейнерних платформ до систем зберігання даних і DevOps-інструментів. У цьому контексті керування користувачами та права...

category Security person iron_will event 30/03/2026

Nmap: що це таке та як з ним працювати

Вступ У сучасному світі інформаційної безпеки та адміністрування мереж важливо мати інструменти, які дозволяють швидко отримувати інформацію про інфраструктуру, виявляти відкриті порти, служби та потенційні вразливості. Одним із найпотужніших і найп...

category Security person iron_will event 19/03/2026

Корисні команди Linux (Ubuntu): практичний довідник для системних адміністраторів та DevOps

Вступ Linux є основою більшості сучасної серверної інфраструктури. Веб-сервери, системи контейнеризації, хмарні платформи, CI/CD-пайплайни та мережеві сервіси у переважній більшості випадків працюють саме на Linux. Серед різних дистрибутивів особлив...

category CheatSheets person iron_will event 13/03/2026
cookie
This website uses cookies to improve your experience. Learn more