Собственные systemd units: overrides, timers и ограничения
Автор: Казачкин Даниил Михайлович · Обновлено
Системный менеджер полезен, когда у процесса должны быть предсказуемый запуск, журнал, перезапуск и ограничения ресурсов. Unit описывает контракт исполнения, а не просто команду в фоне. Начнём с пользовательского сервиса: он позволяет изучить механизм без установки демона от root и изменения системных служб.
Собственный пользовательский сервис
Пример рассчитан на обычный сеанс Linux с доступным user manager systemd. В контейнере, SSH-сеансе без пользовательского manager или WSL без включённого systemd команды могут быть недоступны. До записи файла убедитесь, что имя course-report.service не занято вашей конфигурацией; для существующего unit выберите другое учебное имя.
mkdir -p "$HOME/.config/systemd/user"
cat >"$HOME/.config/systemd/user/course-report.service" <<'UNIT'
[Unit]
Description=Training report
[Service]
Type=oneshot
ExecStart=/usr/bin/printf 'report complete\n'
UNIT
systemd-analyze --user verify "$HOME/.config/systemd/user/course-report.service"
systemctl --user daemon-reload
systemctl --user start course-report.service
journalctl --user -u course-report.service -n 10 --no-pagerОжидаемая строка журнала — report complete. После успешного oneshot состояние может стать inactive (dead): команда завершилась, это нормально. ExecStart не исполняется через shell автоматически; pipeline, > и раскрытие $HOME не имеют обычного shell-смысла. Для сложной работы создайте отдельный скрипт с shebang и проверкой ошибок.
Пользовательский сервис работает с правами пользователя; системный располагают в /etc/systemd/system/ и настраивают отдельного непривилегированного User=. Он может запускаться до входа в графический сеанс, но его пути, разрешения и зависимости требуют отдельной проверки.
Override вместо изменения пакетного файла
systemctl cat имя.service показывает исходный unit вместе с drop-in. Команда systemctl edit имя.service создаёт переопределение; для пользовательского manager добавьте --user. Содержимое небольшого drop-in может выглядеть так:
[Service]
Restart=on-failure
RestartSec=5s
EnvironmentFile=-/etc/course-app/environment
MemoryMax=256M
CPUQuota=50%
NoNewPrivileges=yesПример относится к системному долгоживущему приложению, а не к учебному мгновенному printf. Restart=on-failure реагирует на ошибочное завершение; намеренная остановка через systemctl stop не запускает процесс заново. Серия частых падений может достичь start limit: сначала исправьте причину и прочитайте журнал, затем сбрасывайте failed-state по необходимости.
EnvironmentFile содержит присваивания NAME=value в синтаксисе systemd; это не Bash-скрипт. Минус перед путём допускает отсутствие файла. Окружение не является универсальным безопасным хранилищем секретов. Для замены ExecStart в drop-in сначала задают пустое ExecStart=, затем новую команду: многие списочные параметры накапливаются, а не заменяются одной строкой.
Ограничения и изоляция
MemoryMax ограничивает потребление cgroup: превышение способно привести к OOM в группе. CPUQuota=50% ограничивает процессорное время половиной одного CPU суммарно; это не привязка к конкретному ядру. Проверяйте фактические свойства через systemctl show unit -p MemoryMax -p CPUQuotaPerSecUSec и наблюдайте workload, чтобы лимит не превращался в случайный отказ.
NoNewPrivileges=yes запрещает получать новые привилегии через exec. PrivateTmp=yes изолирует привычные временные каталоги, а ProtectSystem=strict ограничивает запись в файловую иерархию; необходимые каталоги описывают через StateDirectory, RuntimeDirectory или узкие ReadWritePaths. Добавляйте ограничения по одному с проверкой функций приложения. Возможности user units зависят от ядра и доступной делегации cgroups.
systemd-analyze security unit оценивает настройки sandbox, но не доказывает безопасность программы. systemd-analyze critical-chain показывает цепочку времени активации units; она помогает искать задержку загрузки, но не заменяет журнал конкретного сервиса и не отражает все параллельные причины.
Таймер с календарём
Рядом с course-report.service создайте course-report.timer:
[Unit]
Description=Training report schedule
[Timer]
OnCalendar=*-*-* 09:00:00 Europe/Moscow
Persistent=true
Unit=course-report.service
[Install]
WantedBy=timers.targetВыражение можно проверить без установки через systemd-analyze calendar '*-*-* 09:00:00 Europe/Moscow'. После записи выполните systemctl --user daemon-reload, systemctl --user enable --now course-report.timer и systemctl --user list-timers --all. В списке должны появиться таймер и время следующего запуска. enable создаёт связь для будущего старта, --now запускает таймер сейчас; сама service сработает по расписанию, если Persistent не вызывает компенсацию пропуска.
Persistent применим к календарным таймерам: при возобновлении manager выполняется один пропущенный запуск, а не очередь за каждую пропущенную минуту. Если service ещё активна в момент очередного события, timer не создаёт второй экземпляр того же unit. Это помогает избежать пересечений, но означает, что длительное задание требует собственного учёта необработанных периодов.
Пользовательский manager и завершение сеанса
systemctl --user обращается к менеджеру текущего пользователя, обычный systemctl — к системному. Их units, журналы и daemon-reload различны. Сообщение о невозможности подключения к bus не исправляют добавлением sudo перед --user: тогда меняется пользователь и контекст. Проверьте наличие полноценного сеанса и доступность user manager.
Для работы user units без входа может понадобиться loginctl enable-linger имя с подходящими правами. Это изменение жизненного цикла пользователя нужно согласовать с назначением машины. Для обязательной серверной задачи зачастую проще системный unit с непривилегированным User. Для журнала после перезагрузки также требуется постоянное хранение journald; timer Persistent сам по себе его не включает.
Ошибка и диагностика
Изменение файла без daemon-reload оставляет прежнее описание в manager. После reload новая конфигурация применяется при следующем запуске; работающий процесс не получает новые переменные автоматически. Проверьте systemctl cat, show, status и журнал, затем выполняйте управляемый restart. Сервис, который сразу падает из-за неверного пути, не станет исправным от бесконечного Restart.
Практика
Создайте пользовательский oneshot, проверьте unit и найдите строку журнала. Подготовьте таймер, проверьте календарь и сравните следующий запуск с часовым поясом. Временно отключите таймер и убедитесь, что ручной start service по-прежнему работает. Завершая практику, выполните systemctl --user disable --now course-report.timer, удалите только созданные учебные файлы и перечитайте manager. Опишите, какие настройки понадобились бы для настоящей фоновой службы.
Что важно запомнить
Units задают исполнение, timers — время активации, а cgroups — ресурсы. Проверяйте загруженное описание и фактическое состояние отдельно; короткий успешный oneshot не обязан оставаться active.
Частые вопросы
Почему enable не запустил службу сразу?
enable настраивает запуск при достижении target в будущем. Для немедленного старта используется start или enable --now, если unit поддерживает нужную установочную связь.
Достаточно ли daemon-reload для нового EnvironmentFile?
Перечитывание manager само не меняет окружение уже работающего процесса. Обычно нужен новый запуск; поведение reload конкретного сервиса зависит от реализации приложения.
Заменяет ли Persistent=true резервную копию результата?
Нет. Параметр помогает компенсировать пропущенное время активации. Он не хранит бизнес-данные, не проверяет корректность отчёта и не повторяет каждую неудачную операцию автоматически.