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

03/03/2026
| DevOps | iron_will | 0 | 11 | |
Ubuntu BestPractices DevOps

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 надає всі інструменти - залишається лише ними скористатися.

Схожі пости

Глибока оптимізація Linux-серверів під production-навантаження

Запустити сервер в Linux - справа нескладна. Але налаштувати його так, щоб він витримував тисячі одночасних з'єднань, мінімізував латентність і не «падав» під піковим навантаженням - це вже інженерна задача, яка потребує системного підходу. Дистрибут...

DevOps iron_will 03/03/2026

Fail2Ban: основи безпеки та практичні способи захисту серверів

Вступ Забезпечення базового рівня безпеки серверів - це не додатковий етап після розгортання інфраструктури, а обов’язкова складова її проєктування. Будь-який публічно доступний сервіс - SSH, вебсервер, поштовий шлюз або VPN - стає об’єктом автомати...

DevOps iron_will 26/02/2026

Ansible: основи автоматизації, принципи роботи та приклади корисних playbook

Вступ Автоматизація інфраструктури стала стандартом у сучасній розробці та експлуатації програмного забезпечення. Концепції Infrastructure as Code (IaC), безперервної інтеграції та безперервного розгортання (CI/CD), керування конфігураціями та масшт...

DevOps iron_will 24/02/2026

PowerShell: основні поняття та основи роботи з Windows та Active Directory

PowerShell - це потужне середовище автоматизації та керування системами, яке поєднує командний рядок, мову сценаріїв і доступ до .NET-екосистеми. Для IT-фахівців, що працюють із Windows-інфраструктурою, адмініструванням серверів або корпоративними ка...

DevOps iron_will 21/02/2026

Керівництво з роботи в Ubuntu: основи для системного адміністратора

Керівництво з роботи в Ubuntu: основи для системного адміністратора1. ВступUbuntu - це Linux-дистрибутив, який широко використовується як на серверних платформах, так і на робочих станціях. Для ефективної роботи з системою необхідно розуміти:структур...

IT Fundamentals iron_will 13/02/2026

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

Вступ Операційна система Linux спочатку проєктувалася як багатокористувацька. Це означає, що керування користувачами, групами та правами доступу є базовим механізмом безпеки системи. Коректне налаштування прав дозволяє обмежити доступ до ресурсів, м...

IT Fundamentals iron_will 24/01/2026

Коментарі (0)

Коментування доступне лише для авторизованих користувачів.

Цей сайт використовує cookies для покращення роботи. Детальніше