Systemd: сервисы, таймеры и журнал — полное руководство

systemdlinuxservicesautomation

Терминал с systemctl и journalctl: управление сервисами Linux

Systemd стоит на подавляющем большинстве серверов, и умение с ним работать — обязательный навык для любого, кто администрирует Linux. В этой статье разберём unit-файлы, типы сервисов, таймеры, журнал, ресурсные лимиты и hardening.

Анатомия unit-файла

Unit-файл состоит из трёх секций: [Unit] (метаданные и зависимости), [Service] (параметры процесса) и [Install] (интеграция с загрузкой).

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Server
After=network.target postgresql.service
Wants=postgresql.service
ConditionPathExists=/etc/myapp/config.yml

[Service]
Type=exec
User=app
Group=app
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=30s

[Install]
WantedBy=multi-user.target

Важное различие зависимостей: After задаёт порядок запуска, Requiresжёсткую зависимость (если зависимость упала, юнит тоже остановится), Wantsмягкую (продолжит работать). На практике чаще всего нужна комбинация After + Wants.

Типы сервисов: Type=

Тип определяет, как systemd понимает, что процесс запустился. Неправильный тип приводит к таймаутам и ложным перезапускам.

Type=exec (рекомендуется) — systemd считает сервис запущенным после успешного вызова exec(). Если бинарник не найден — сразу failed.

Type=simple (по умолчанию) — считает запущенным сразу после вызова ExecStart. Не знает, готово ли приложение реально.

Type=forking — для классических Unix-демонов, которые форкаются. Требует PIDFile.

Type=oneshot — для задач, которые выполняются и завершаются. Можно указать несколько ExecStart — они выполнятся последовательно. Используйте с RemainAfterExit=yes.

Type=notify — приложение само сообщает о готовности через sd_notify. Самый надёжный вариант, но требует поддержки от приложения.

Политики перезапуска

[Service]
# on-failure — при ненулевом exit code, сигнале или таймауте
# always — при любом завершении
# on-abnormal — при сигнале или таймауте (не при exit code)
Restart=on-failure
RestartSec=5s

# Защита от бесконечного цикла: не более 3 раз за 60 секунд
StartLimitBurst=3
StartLimitIntervalSec=60s

# Коды выхода, которые НЕ считаются ошибкой
SuccessExitStatus=143

Если сервис падает чаще лимита — что-то серьёзно сломано, и бесконечные перезапуски только усугубят ситуацию.

Лимиты ресурсов

Один прожорливый процесс не должен класть весь сервер:

[Service]
MemoryHigh=400M          # мягкий лимит (агрессивный своп)
MemoryMax=512M           # жёсткий лимит (OOM kill)
CPUQuota=200%            # два ядра (50% = пол-ядра)
TasksMax=100             # защита от fork-бомбы
LimitNOFILE=65535        # лимит открытых файлов

Проверить потребление:

systemctl show myapp.service | grep -E "Memory|CPU|Tasks"
systemd-cgtop

Таймеры вместо cron

Systemd timers дают логирование через journal, зависимости и обработку пропущенных запусков. Таймер — это два файла: .timer (расписание) и .service (задача).

# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
AccuracySec=1s

[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup job

[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup.sh
MemoryMax=256M
CPUQuota=50%
Nice=19
IOSchedulingClass=idle
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

Форматы расписания OnCalendar

OnCalendar=daily                      # каждый день в полночь
OnCalendar=hourly                     # каждый час
OnCalendar=Mon *-*-* 09:00:00        # каждый понедельник в 09:00
OnCalendar=*:0/15                     # каждые 15 минут
OnCalendar=Mon..Fri *-*-* 08:30:00   # будние дни в 08:30

Для монотонных интервалов (не привязанных к календарю):

OnBootSec=15min          # через 15 минут после загрузки
OnUnitActiveSec=5min     # через 5 минут после последнего срабатывания

Проверить расписание:

systemd-analyze calendar "Mon..Fri *-*-* 08:30:00"
systemctl list-timers --all

Серверная комната: физическая инфраструктура за systemd-сервисами

Journalctl: работа с журналом

journalctl -u myapp.service              # логи сервиса
journalctl -u myapp.service -f           # в реальном времени
journalctl -u myapp.service -n 100       # последние 100 строк
journalctl -u myapp.service --since today
journalctl -u myapp.service -p err       # только ошибки
journalctl -u myapp.service -o json-pretty -n 5  # JSON-формат
journalctl -k -p err                     # ошибки ядра

Управление хранением:

journalctl --disk-usage                  # размер журнала
sudo journalctl --vacuum-size=500M       # оставить 500 МБ
sudo journalctl --vacuum-time=2weeks     # удалить старше двух недель

Настройка в /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=1G
Storage=persistent
Compress=yes
MaxRetentionSec=30days

Socket activation

Systemd слушает сокет и запускает сервис только при первом запросе. Это экономит ресурсы и позволяет обновлять сервис без потери соединений:

# /etc/systemd/system/myapp.socket
[Unit]
Description=My App Socket

[Socket]
ListenStream=8080

[Install]
WantedBy=sockets.target

Hardening: безопасность сервисов

Каждый сервис должен иметь минимально необходимые права:

[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp /var/log/myapp
PrivateDevices=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectControlGroups=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service

Оценить уровень безопасности юнита:

systemd-analyze security myapp.service
# Оценка от 0 (полностью изолирован) до 10 (без изоляции)
# Цельтесь в значение до 3

Отладка проблем

systemctl status myapp.service
systemctl cat myapp.service
systemctl list-dependencies myapp.service
systemd-analyze blame     # время загрузки каждого юнита

Заключение

Три главных правила: всегда указывайте правильный Type= (это решает 90% проблем с запуском); используйте hardening-директивы NoNewPrivileges, ProtectSystem, PrivateTmp в каждом unit-файле; замените cron на systemd timers и получите логирование, зависимости и обработку пропущенных запусков бесплатно.

© 2026 Terminal Notes. Built with SvelteKit.