Администрирование Linux-серверов

Как создать службу systemd для приложения или скрипта

Создайте service unit, задайте пользователя, перезапуск и зависимости, затем перечитайте unit-файлы через daemon-reload. Проверьте запуск и сообщения службы командами systemctl и journalctl.

11 минут
абстрактная иллюстрация управления службой Linux
Собственное приложение запускают через service unit — описание для systemd, как запускать, отслеживать и останавливать процесс. Локальный .service-файл размещают в предназначенном для администратора каталоге, выполняют systemctl daemon-reload, а затем отдельно запускают службу и при необходимости включают автозагрузку. Пользователя, окружение, зависимости и перезапуск задают директивами unit-файла, а первичную проверку выполняют через systemctl и journalctl.

Где хранить собственный service unit

Service unit — это вид Systemd unit, описывающий управляемую системную службу. Для постоянной локальной конфигурации обычно используют /etc/systemd/system/имя.service. Каталог /run/systemd/system/ предназначен для unit-файлов, созданных на время текущей загрузки: после перезагрузки его содержимое исчезает.

Файлы, поставленные пакетами, обычно находятся в каталогах вроде /usr/lib/systemd/system/, /lib/systemd/system/ или другом пути из unit load path конкретного дистрибутива. Состав путей зависит от дистрибутива и версии systemd. Определение с тем же именем в /etc/systemd/system/ имеет более высокий приоритет, чем пакетное, поэтому локальный файл может полностью заменить unit пакета.

Не редактируйте unit-файлы пакетов напрямую

Обновление пакета может перезаписать изменения. Для изменения установленной службы обычно создают drop-in-файл в /etc/systemd/system/имя.service.d/. Drop-in-конфигурации объединяются с основным unit-файлом; одноимённый drop-in из /etc имеет приоритет над вариантом из /run и каталогов поставщика. Порядок нескольких файлов определяется их именами. Точные правила загрузки и перечень каталогов смотрите в man pages systemd.unit и systemd.directives вашей системы.

Unit для постоянно работающего приложения

Ниже — заменяемый шаблон для приложения, которое должно работать постоянно. Пути /opt/example/bin/app, /etc/example-app/example-app.env, каталог /opt/example/app и имя appuser — примеры, а не обязательные значения.

/etc/systemd/system/example-app.service
[Unit]
Description=Example application
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=exec
User=appuser
Group=appuser
WorkingDirectory=/opt/example/app
EnvironmentFile=/etc/example-app/example-app.env
ExecStart=/opt/example/bin/app --config /etc/example-app/config.yaml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Такой unit не зависит от того, является /opt/example/bin/app бинарным файлом или скриптом. Существенно поведение: главный процесс должен продолжать работать, пока служба активна. Если программа сама отделяется от родительского процесса, создаёт PID-файл или работает по иной модели, нужны настройки по документации приложения; переносить этот шаблон без проверки нельзя.

Директивы шаблона

ДирективаНазначение
Type=execsystemd считает запуск успешным после успешного execve() программы. Этот тип удобен, когда важно получить ошибку отсутствующего или неисполняемого файла уже при старте unit.
User= и Group=Запускают процесс от заранее созданной учётной записи пользователя и основной либо явно указанной группы пользователей, а не от root.
WorkingDirectory=Устанавливает рабочий каталог процесса. Относительные пути, которые использует само приложение, будут отсчитываться от него.
EnvironmentFile=Загружает переменные окружения из отдельного файла в синтаксисе, который понимает systemd. Это не произвольный скрипт shell.
ExecStart=Задаёт запускаемую программу и её аргументы.
StandardOutput= и StandardError=Явно направляют стандартный вывод и поток ошибок в journal.

Type=simple во многих конфигурациях является значением по умолчанию: systemd начинает последующие действия сразу после fork процесса, не дожидаясь результата execve(). Type=exec ждёт успешного выполнения execve() и потому иначе отражает ранние ошибки запуска. Доступность и детали Type=exec нужно сверить с systemd.service целевой версии: на старых выпусках этот тип может отсутствовать. Ни simple, ни exec не означают «для бинарного файла» или «для скрипта».

ExecStart= не является строкой командной оболочки. systemd не обрабатывает неявно пайпы, >, 2>&1, &&, подстановки и другие конструкции shell. Для последовательности команд или пайпа создайте отдельный исполняемый скрипт-обёртку либо явно вызовите нужную оболочку, например /bin/sh -c ...; второй вариант требует аккуратного экранирования и осознанной обработки входных данных.

Форма ExecStart

Понятный и переносимый пример использует абсолютный путь: ExecStart=/opt/example/bin/app .... Согласно systemd.service, в версиях, поддерживающих это правило, первым аргументом также может быть простое имя исполняемого файла без разделителей каталогов: systemd ищет его в собственном фиксированном пути поиска. Путь с / должен быть абсолютным. Скрипт можно запускать напрямую, если файл доступен для выполнения, имеет корректный shebang и указанный в нём интерпретатор доступен; альтернативно явно указывают интерпретатор, например ExecStart=/usr/bin/python3 /opt/example/bin/app.py. Проверьте эти условия в документации используемой версии systemd и приложения.

Пользователь, рабочий каталог и файл окружения

До запуска убедитесь, что appuser уже существует и может пройти по всем каталогам в пути к программе, прочитать конфигурацию и файл окружения, а также читать или записывать нужные приложению данные. Это определяется владельцем файла, группой и правами доступа к файлам. Если приложение открывает сокет, журнал или каталог для временных файлов, доступ нужен и к этим объектам.

Пример минимальной проверки, что служебная учётная запись может выполнить программу и прочитать файл окружения. Проверки доступа к конфигурации, данным и каталогам добавляют по фактическим путям приложения.
sudo -u appuser test -x /opt/example/bin/app && sudo -u appuser test -r /etc/example-app/example-app.env && echo OK
Вывод:
OK

Не помещайте в EnvironmentFile= секреты, если права на файл и модель хранения секретов не оценены отдельно: переменные окружения не являются универсальным защищённым хранилищем. Создание пользователей, назначение групп и изменение прав разобраны в статье «Пользователи, группы, sudo и права файлов в Linux».

Перезапуск и лимит неудачных запусков

Restart=on-failure повторно запускает службу при неуспешном завершении: например, при ненулевом коде выхода, сигнале, тайм-ауте или срабатывании watchdog. Он не перезапускает процесс после штатного выхода с кодом 0 и ряда ожидаемых сигналов завершения, а также после явной остановки через systemctl stop. Для другого поведения есть Restart=always, on-success, on-abnormal, on-abort и on-watchdog; выбирайте режим по тому, какие исходы приложение считает нормальными.

RestartSec=5 задаёт паузу перед очередной попыткой. StartLimitIntervalSec=60 и StartLimitBurst=5 в секции [Unit] ограничивают число запусков за заданный интервал. После превышения лимита systemd отклоняет новые попытки; когда предыдущие попытки выйдут за пределы интервала, запуск снова станет возможен. Команда systemctl reset-failed имя.service сбрасывает состояние failed и счётчик ограничения запусков. Эти параметры не исправляют неверный путь в ExecStart=, отсутствующую конфигурацию, ошибку приложения или недостаточные права.

Ограничение Type=oneshot

Для Type=oneshot нельзя сочетать Restart=always или Restart=on-success. Другие варианты перезапуска и их смысл проверяйте по systemd.service вашей версии. Не превращайте однократную задачу в бесконечный цикл перезапусков: причина постоянной ошибки должна быть устранена.

Unit для однократной задачи

Если команда должна выполниться и завершиться, используйте Type=oneshot. Это также не зависит от того, написана задача как скрипт или скомпилированная программа.

/etc/systemd/system/example-maintenance.service
[Unit]
Description=Example one-time maintenance task

[Service]
Type=oneshot
User=appuser
WorkingDirectory=/opt/example/app
ExecStart=/opt/example/bin/maintenance --cleanup

После успешного выполнения такой unit обычно становится inactive (dead): задача завершена, а процесса больше нет. RemainAfterExit=yes меняет это представление на active (exited), но не оставляет программу работающей. Если задача должна запускаться по времени, используйте systemd timer или cron — это отдельный сценарий из статьи «Cron и systemd timers: задачи по расписанию и диагностика сбоев».

Как задать зависимости и порядок запуска

After= и Before= задают только порядок, когда systemd запускает оба unit. Wants= и Requires= задают зависимости запуска. Wants= пытается активировать указанный unit вместе с текущим, но неудача или остановка желательного unit сама по себе обычно не делает текущий unit неуспешным. Requires= сильнее: systemd также активирует обязательный unit, а его явная остановка приводит к остановке зависимого. Чтобы не запускать текущий unit при неудачной активации обязательного, обычно сочетают Requires= с After=. Эти директивы сами по себе не подтверждают готовность службы и не гарантируют совместную активность после любого самопроизвольного сбоя.

Фрагмент секции [Unit]
[Unit]
Wants=example-cache.service
After=example-cache.service

В этом примере приложение хочет запустить example-cache.service и, если оба запускаются в одной транзакции, будет запущено после него. Одного After= недостаточно, чтобы подтянуть cache-службу в запуск; одного Wants= недостаточно, чтобы обеспечить порядок. Поэтому ordering dependency и requirement dependency решают разные задачи и часто применяются вместе. Если без внешней службы приложение неработоспособно, вместо Wants= может потребоваться Requires=, но это нужно подтверждать поведением приложения.

Порядок не равен готовности

After=network.target означает порядок относительно target, а не гарантирует доступность конкретного удалённого сервиса. Для сетевых, базовых и иных зависимостей используйте unit и условия готовности, которые документированы для конкретного приложения и дистрибутива.

Перечитать unit, запустить службу и включить автозагрузку

Создать systemd service и применить конфигурацию

Команды ниже выполняются после сохранения /etc/systemd/system/example-app.service. Имя unit в командах соответствует имени файла.

  1. 1
    Проверьте синтаксис и ссылки unit-файла
    В системах, где утилита доступна, выполните sudo systemd-analyze verify /etc/systemd/system/example-app.service. Она помогает обнаружить часть ошибок в unit-файле и ссылках на units, но не заменяет реальный запуск, проверку прав и работоспособности приложения. Доступность и диагностику этой команды сверяйте с man page systemd-analyze вашей версии.
  2. 2
    Перечитайте определения units
    Выполните sudo systemctl daemon-reload. Менеджер systemd заново читает unit-файлы и drop-in-конфигурации. Команда не запускает службу, не перезапускает уже работающий процесс и не перечитывает конфигурационный файл самого приложения.
  3. 3
    Запустите службу отдельно
    Выполните sudo systemctl start example-app.service, затем проверьте sudo systemctl status example-app.service. После изменения unit-файла работающей службы обычно требуется осознанно применить изменение через restart либо иной подходящий для приложения способ; daemon-reload сам процесс не меняет.
  4. 4
    Включите службу в автозагрузку
    Выполните sudo systemctl enable example-app.service. Для шаблона это создаёт связь с multi-user.target согласно [Install]. enable настраивает запуск при будущей загрузке, но сам по себе не обязан запускать службу сейчас; для обоих действий можно использовать sudo systemctl enable --now example-app.service.

Поддерживаемый приложением reload — отдельное действие: оно имеет смысл, только если приложение умеет безопасно перечитывать свою конфигурацию и unit описывает этот механизм. Не путайте его с systemctl daemon-reload: первая операция относится к конфигурации приложения, вторая — только к определениям units. Базовые различия команд управления описаны в статье «systemd и systemctl: управление службами и автозагрузкой Linux».

Проверка статуса и журналов службы

Показывает состояние unit, основной PID, последние сообщения и причину неудачи, если systemd её зафиксировал.
sudo systemctl status example-app.service
Показывает записи текущей загрузки, относящиеся к unit. Уберите `-b`, если нужны записи за предыдущие загрузки, когда они сохранены.
sudo journalctl -u example-app.service -b
Следит за новыми сообщениями unit в реальном времени; остановите просмотр сочетанием Ctrl+C.
sudo journalctl -u example-app.service -f

Стандартные потоки stdout и stderr обычно принимает systemd-journald, поэтому journalctl -u имя.service отбирает сообщения по имени unit. Это не произойдёт в ожидаемом виде, если unit направляет потоки в файл, null или другой механизм, либо приложение самостоятельно перенаправляет вывод. В шаблоне это явно задано через StandardOutput=journal и StandardError=journal.

Наличие старых записей после перезагрузки зависит от настройки хранения journald, в частности от Storage= и доступности постоянного каталога журнала; на части систем журнал может быть только в памяти. Поиск, хранение и расширенная фильтрация разобраны в статье «Журналы Linux: journalctl, rsyslog и logrotate».

Проблема

Служба не запускается или сразу завершается

Возможные причины

  • Указан несуществующий либо недоступный файл в `ExecStart=`, `WorkingDirectory=` или `EnvironmentFile=`
  • `appuser` не имеет нужного доступа к программе, каталогам, конфигурации или данным
  • Для постоянно работающего приложения выбран `Type=oneshot`, либо программа штатно завершается
  • Перезапуски достигли лимита `StartLimitBurst`

Решение

Сначала сохраните вывод systemctl status и журнал, затем сверяйте пути, способ запуска и права с документацией приложения. После изменения unit-файла выполните daemon-reload; после исправления конфигурации приложения примените именно поддерживаемый приложением способ. Подробная ветвящаяся диагностика приведена в статье «Служба systemd не запускается: диагностика ошибок и восстановление».

Как избежать

Проверяйте unit через `systemd-analyze verify`, тестируйте запуск от целевой учётной записи и не маскируйте постоянную ошибку бесконечным `Restart=`.

Термины в статье

Владелец файла — числовой UID, записанный в метаданных объекта и определяющий применение класса прав владельца.
Группа пользователей — объект разграничения доступа, объединяющий учётные записи под общим GID для назначения прав на файлы, службы и иные ресурсы нескольким пользователям.
Демон — обычно длительно работающий неинтерактивный процесс, который в фоне выполняет системную, сетевую или прикладную функцию.
Командная оболочка — программа-интерпретатор, принимающая команды и запускающая процессы; в отличие от терминала, она обрабатывает синтаксис и сценарии.
Права доступа к файлам — разрешения файловой системы на чтение, запись и выполнение объекта для его владельца, группы-владельца и остальных пользователей.
Процесс — экземпляр выполняемой программы в Linux с PID, виртуальным адресным пространством, состоянием и связанными с ним ресурсами.
Системная служба — программный компонент, которым ОС управляет как логической единицей для фонового выполнения задач или предоставления функций программам и пользователям.
Учётная запись пользователя — запись в системе идентификации ОС, связывающая имя и UID с параметрами входа, домашним каталогом и членством в группах.
Bash — командная оболочка Unix-подобных систем, реализующая язык POSIX shell и добавляющая расширения для интерактивной работы и сценариев.
Cron — служба-планировщик в Unix-подобных ОС, запускающая команды по расписаниям из таблиц crontab, обычно с минутной дискретностью.
journalctl — консольная утилита чтения и фильтрации журнала systemd, выводящая сообщения по времени, юниту, загрузке системы, приоритету и другим полям.
systemctl — командная утилита управления systemd, которая запускает и останавливает юниты, показывает их состояние и настраивает автоматическую активацию.
systemd — система инициализации и менеджер служб Linux, управляющий службами и другими ресурсами как объектами unit, обычно описанными unit-файлами.
systemd timer — юнит systemd, который по календарному или монотонному расписанию активирует другой юнит, по умолчанию одноимённый сервис.
Systemd unit — именованный объект systemd, представляющий отслеживаемый или управляемый ресурс, службу, задачу либо состояние системы.
systemd-journald — служба systemd для приёма, индексации и хранения структурированных журналов ядра, служб, процессов и системных сообщений.

Вопросы по теме

Все вопросы и ответы

Как включить службу в автозагрузку?

Выполните sudo systemctl enable example-app.service. Для такого unit будет создана связь с multi-user.target согласно секции [Install], и служба будет запускаться при будущих загрузках. Команда enable не обязана запускать службу сразу; чтобы одновременно включить автозагрузку и запустить её, используйте sudo systemctl enable --now example-app.service.

Что делает systemctl daemon-reload?

systemctl daemon-reload заставляет менеджер systemd заново прочитать unit-файлы и drop-in-конфигурации. Команда нужна после создания или изменения service unit. Она не запускает службу, не перезапускает уже работающий процесс и не перечитывает конфигурационный файл самого приложения. Изменения работающей службы обычно применяют отдельным restart или другим подходящим способом.

Как понять, почему служба systemd не запускается?

Сначала выполните sudo systemctl status example-app.service, затем посмотрите журнал командой sudo journalctl -u example-app.service -b. Status покажет состояние unit, основной PID, последние сообщения и зафиксированную причину ошибки. В журнале обычно видны проблемы с путями, пользователем, правами, конфигурацией или самим приложением.

Почему служба сразу завершается после запуска?

Причина зависит от типа unit и поведения программы. Для постоянно работающего приложения процесс должен оставаться запущенным; если программа штатно завершилась, служба может стать inactive. Type=oneshot предназначен для однократных задач и после успеха обычно становится inactive (dead). Также проверьте путь ExecStart=, права пользователя, рабочий каталог, окружение и лимит неудачных запусков.

Можно ли использовать пайпы и перенаправления в ExecStart=?

Нет, неявно нельзя. ExecStart= не является строкой командной оболочки: systemd не обрабатывает пайпы, >, 2>&1, &&, подстановки и другие shell-конструкции. Для последовательности команд или пайпа создайте исполняемый скрипт-обёртку либо явно вызовите оболочку, например /bin/sh -c ..., тщательно экранируя аргументы и контролируя входные данные.

Можно ли редактировать unit-файл службы из пакета?

Не рекомендуется. Unit-файлы, поставленные пакетами, обновление может перезаписать. Для изменения установленной службы создайте drop-in-файл в /etc/systemd/system/имя.service.d/: его настройки объединятся с основным unit-файлом, а вариант из /etc имеет приоритет над конфигурациями из /run и каталогов поставщика.