systemd за 30 минут: от юнитов до таймеров
systemd давно стал стандартом инициализации в большинстве Linux-дистрибутивов, но многие сисадмины до сих пор знают его на уровне systemctl restart nginx и не глубже. Между тем systemd умеет гораздо больше: управлять таймерами, монтированием, сетевыми зависимостями и даже изолировать процессы без контейнеров. Разберём всё по порядку.
Типы юнитов
systemd оперирует юнитами (unit) — файлами конфигурации с расширением, определяющим тип:
.service— управление демонами и процессами.timer— замена cron, запуск по расписанию.mount— точки монтирования (генерируется из/etc/fstab).socket— socket activation, запуск сервиса по первому соединению.target— группы юнитов, аналог runlevel.path— мониторинг файловой системы, запуск при изменении файла
Системные юниты хранятся в /lib/systemd/system/, пользовательские переопределения — в /etc/systemd/system/.
Создаём свой сервис
Предположим, у нас есть скрипт /opt/myapp/server.py. Создаём юнит:
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Application Server
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/server.py
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
Активируем и запускаем:
systemctl daemon-reload
systemctl enable --now myapp.service
systemctl status myapp.service
Зависимости: After, Requires, Wants
Три ключевых директивы для порядка запуска:
After=— этот юнит стартует ПОСЛЕ указанных. Только порядок, не зависимость.Requires=— жёсткая зависимость. Если зависимость не запустилась — этот юнит тоже не запустится.Wants=— мягкая зависимость. Попробует запустить, но не упадёт если не получилось.
Типичная ошибка: написать After=postgresql.service без Requires= и удивляться, почему приложение стартует раньше базы данных при определённых условиях.
Journalctl: просмотр логов
Забудьте о tail -f /var/log/syslog — journald хранит структурированные логи с метаданными:
# Логи конкретного сервиса
journalctl -u myapp.service
# Следить в реальном времени
journalctl -u myapp.service -f
# Логи с момента последнего запуска
journalctl -u myapp.service -b
# Логи за последний час
journalctl -u myapp.service --since "1 hour ago"
# Только ошибки
journalctl -u myapp.service -p err
# Вывод в JSON
journalctl -u myapp.service -o json-pretty | head -50
systemd Timers: замена cron
systemd timers — это полноценная замена cron с рядом преимуществ: логи в journald, зависимости от других юнитов, возможность запуска после загрузки системы.
Для каждого таймера нужны два файла. Пример — ежедневный бэкап:
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3am
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Директива Persistent=true запускает таймер, если он пропустил запуск (например, машина была выключена). Очень полезно для серверов с периодическими перезагрузками.
systemctl enable --now backup.timer
systemctl list-timers --all
Hardening: изоляция без контейнеров
systemd позволяет существенно ограничить права сервисов прямо в unit-файле:
[Service]
# Файловая система только для чтения (кроме /tmp, /var)
ProtectSystem=strict
# Приватный /tmp, недоступный другим процессам
PrivateTmp=true
# Запрет получения новых привилегий через suid/sudo
NoNewPrivileges=true
# Запрет записи в домашний каталог
ProtectHome=true
# Доступные для записи пути (исключение из ProtectSystem=strict)
ReadWritePaths=/var/lib/myapp /var/log/myapp
# Ограничение системных вызовов
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# Запуск от непривилегированного пользователя
User=myapp
Group=myapp
# Ограничение capabilities
CapabilityBoundingSet=
AmbientCapabilities=
Проверить уровень изоляции сервиса можно командой:
systemd-analyze security myapp.service
Она выдаст оценку от 0 (максимальная защита) до 10 (никакой защиты) с конкретными рекомендациями.
Частые ошибки
1. Забыть daemon-reload после изменения unit-файла. systemd читает файлы в кэш — без reload изменения не применятся.
2. Type=forking без PIDFile. Если процесс форкается (как старые демоны), нужно указать PIDFile= или использовать Type=notify если приложение поддерживает sd_notify.
3. ExecStart с shell-синтаксисом. systemd не запускает команды через shell. Конструкция ExecStart=/bin/bash -c "cmd1 && cmd2" работает, но ExecStart=cmd1 && cmd2 — нет.
4. Права на файл юнита. Файлы в /etc/systemd/system/ должны принадлежать root и не иметь прав на запись для других.
systemd — мощный инструмент, который при правильном использовании заменяет cron, init-скрипты, supervisor и отчасти даже контейнеры для изоляции. Стоит потратить время на изучение.
Таймеры реально удобнее cron — особенно то, что можно посмотреть когда последний раз запускался и когда следующий запуск через list-timers. В cron это было настоящей болью.