Освоение Systemd: создание первого собственного файла модуля службы
Изучите основы управления службами Systemd, создав собственный файл модуля. Это руководство разбирает основные разделы `[Unit]`, `[Service]` и `[Install]`, предоставляя пошаговые инструкции по определению, включению, запуску и проверке базовой фоновой службы в Linux с помощью `systemctl`.
Освоение Systemd: создание первого собственного файла модуля службы
Пользовательский модуль службы systemd — это то, к чему вы обращаетесь, когда скрипт или небольшое приложение переросло сеанс терминала, окно screen или хрупкий обходной путь cron. Возможно, у вас есть рабочий процесс, который должен перезапускаться после сбоя. Возможно, небольшому внутреннему API нужно запускаться после готовности сети. Возможно, скрипт резервного копирования должен работать как управляемая служба, чтобы его журналы хранились в journal, а операторы могли использовать те же команды systemctl, которые они используют для всего остального.
Полезная часть systemd не в том, что файл модуля сложен. Она в том, что файл модуля делает процесс явным: что запускается, от имени кого оно запускается, когда запускается, как останавливается, куда идут журналы и что systemd должен делать при сбое. Как только эти решения записаны, службой становится гораздо проще управлять.
Это пошаговое руководство создает небольшую службу с нуля. Пример намеренно прост, но шаблоны те же, что вы бы использовали для фонового рабочего процесса, потребителя очереди, экспортера метрик или внутреннего демона.
Начните с реальной команды, а не с файла модуля
Хороший модуль службы начинается с команды, которая уже работает вручную. Прежде чем писать конфигурацию systemd, убедитесь, что вы можете запустить программу напрямую и понимаете, что она делает на переднем плане.
Для этого примера создайте небольшой скрипт-репортер:
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
Добавьте это содержимое:
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
Сделайте его исполняемым и протестируйте:
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
Остановите его с помощью Ctrl-C после того, как увидите несколько строк. Обратите внимание, что скрипт пишет в стандартный вывод, а не напрямую добавляет в /var/log/reporter.log. Это сделано намеренно. Для большинства пользовательских служб позволить systemd захватывать stdout и stderr в journal чище, чем заставлять каждый скрипт управлять собственными правами на файлы журналов, ротацией и поведением при сбоях.
Создайте выделенного пользователя службы
Избегайте запуска служб приложений от имени root, если им действительно не нужны привилегии root. Скрипту пульса это не нужно. Веб-приложению обычно не нужно. Рабочему процессу, который читает из очереди и пишет в базу данных, обычно не нужно.
Создайте заблокированного системного пользователя:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
Если ваш дистрибутив использует другой путь к nologin, проверьте его с помощью:
command -v nologin || command -v false
Пользователь службы должен владеть только теми файлами, которые ему нужно записывать. В этом примере скрипт пишет в journal через systemd, поэтому ему не нужно владение /opt/my-custom-service.
Напишите модуль службы
Пользовательские системные модули, управляемые администратором, обычно находятся в /etc/systemd/system/. Модули пакетов поставщиков обычно находятся в /usr/lib/systemd/system/ или /lib/systemd/system/, в зависимости от дистрибутива. Не редактируйте файлы модулей поставщика напрямую, если это возможно; используйте /etc/systemd/system/ для своих собственных модулей и drop-in для переопределений.
Создайте модуль:
sudo nano /etc/systemd/system/my-reporter.service
Используйте это в качестве практической первой версии:
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Раздел [Unit] описывает взаимосвязи. After=network-online.target управляет порядком; он сам по себе не подтягивает цель network-online. Wants=network-online.target просит systemd также запустить эту цель. Если вашей службе не нужна сеть, удалите обе строки и сделайте модуль проще.
Раздел [Service] описывает процесс. Type=simple подходит для процесса переднего плана, который не форкается сам в фоновый режим. Это обычный случай для современных служб. Если устаревший демон форкается, пишет PID-файл и возвращает управление оболочке, тогда вам может понадобиться Type=forking, но не используйте его только потому, что слово звучит более по-демонски.
ExecStart должен быть абсолютным путем. Возможности оболочки, такие как конвейеры, перенаправления и &&, не интерпретируются, если вы явно не запускаете оболочку, например ExecStart=/bin/bash -lc 'command one && command two'. Предпочитайте скрипт, когда команде нужна логика оболочки; его легче тестировать и легче читать.
Restart=on-failure говорит systemd перезапускать службу после аварийных завершений. Он не будет перезапускаться после чистого systemctl stop. RestartSec=5s предотвращает плотный цикл перезапусков от нагрузки на машину.
Параметры ужесточения здесь скромны, но полезны. NoNewPrivileges=true предотвращает получение процессом и его дочерними процессами новых привилегий через setuid-бинарники или возможности файлов. PrivateTmp=true предоставляет службе частное представление /tmp. Обычно они безопасны для простых служб, но тестируйте их с реальными приложениями, так как некоторое программное обеспечение ожидает общие временные пути.
Загрузите и запустите модуль
После добавления или изменения файла модуля перезагрузите конфигурацию менеджера systemd:
sudo systemctl daemon-reload
Запустите службу сейчас:
sudo systemctl start my-reporter.service
Проверьте ее состояние:
systemctl status my-reporter.service
Вы должны увидеть Active: active (running). Если произошел сбой, не гадайте. Прочитайте журналы:
journalctl -u my-reporter.service -n 50 --no-pager
Следите за живыми журналами во время тестирования:
journalctl -u my-reporter.service -f
Если путь к скрипту неверен, отсутствуют права, пользователь не существует или команда немедленно завершается, systemd обычно скажет об этом прямо в journal.
Включите автозапуск при загрузке
Запуск службы и включение службы — это разные действия. start запускает ее сейчас. enable подключает ее к цели загрузки, чтобы она запускалась при будущих загрузках.
sudo systemctl enable my-reporter.service
Вы можете сделать оба действия одной командой после того, как модуль протестирован:
sudo systemctl enable --now my-reporter.service
Чтобы проверить, включена ли она:
systemctl is-enabled my-reporter.service
Сделайте сбои более диагностируемыми
Самые распространенные сбои первой службы — это обычные проблемы Linux, одетые в одежды systemd.
Если вы видите status=203/EXEC, systemd не смог выполнить команду. Проверьте путь, бит исполняемости, строку shebang и окончания строк. Скрипт, скопированный из Windows с окончаниями строк CRLF, может завершиться ошибкой, даже если он выглядит нормально в редакторе.
Если вы видите ошибки прав, помните, что служба работает от имени reporter, а не от имени вашего пользователя оболочки. Протестируйте с помощью:
sudo -u reporter /opt/my-custom-service/reporter.sh
Если служба запускается и немедленно останавливается, процесс, вероятно, завершается. Type=simple ожидает, что команда будет продолжать работать. Команда одноразовой настройки должна использовать Type=oneshot, а не simple.
Если журналы отсутствуют, проверьте, пишет ли приложение в файлы вместо stdout/stderr, или меняет ли оно пользователей внутри. Для большинства небольших служб запись в stdout является наименее удивительным вариантом.
Полезные команды управления
Как только модуль установлен, повседневная работа проста:
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl cat особенно полезен на машинах с drop-in переопределениями, потому что он показывает эффективные фрагменты модуля, которые читает systemd.
Пользовательский файл модуля не должен быть умным. Он должен быть скучным, явным и тестируемым. Заставьте команду работать вручную, запустите ее от имени выделенного пользователя, напишите наименьший модуль, который точно описывает службу, перезагрузите systemd и используйте journal, когда что-то выходит из строя. Этот рабочий процесс масштабируется от игрушечного скрипта-репортера до реальных производственных демонов.
Добавляйте окружение и конфигурацию чисто
Рано или поздно службе понадобится конфигурация: порт, URL базы данных, флаг функции или путь. Избегайте закапывания этих значений внутрь файла модуля, когда они различаются в зависимости от окружения. Распространенный шаблон — файл окружения:
sudo nano /etc/my-reporter.env
Пример:
REPORT_INTERVAL=10
REPORT_LABEL=production
Заблокируйте файл, если он содержит что-то чувствительное:
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
Затем укажите его в модуле:
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
В скрипте прочитайте переменную со значением по умолчанию:
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
Для секретов будьте осторожны. Переменная окружения может быть раскрыта через проверку процесса или метаданные службы в зависимости от конфигурации системы и прав. Для высокочувствительных значений предпочитайте proper secret manager, файл учетных данных с строгими правами или более новые функции учетных данных systemd, если ваш дистрибутив их поддерживает. Важная привычка — принимать решение обдуманно, а не разбрасывать пароли в файлы модулей, потому что это удобно.
Используйте drop-in переопределения для локальных изменений
Если пакет устанавливает модуль, и вам нужно изменить одну настройку, не редактируйте файл поставщика. Используйте drop-in:
sudo systemctl edit my-reporter.service
Это открывает файл переопределения в /etc/systemd/system/my-reporter.service.d/. Например:
[Service]
RestartSec=15s
Перезагрузите и перезапустите после сохранения:
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
Проверьте объединенный результат:
systemctl cat my-reporter.service
Drop-in важны, потому что обновления пакетов могут заменять модули поставщика. Ваши переопределения в /etc остаются видимыми и намеренными.
Подумайте о поведении при завершении
Запуск — это только половина жизненного цикла. Служба также должна чисто останавливаться. По умолчанию systemd отправляет SIGTERM, ждет, а затем может отправить SIGKILL, если процесс не завершается. Для многих простых служб это нормально. Для рабочих процессов очередей, процессоров загрузок и писателей баз данных вам может понадобиться обрабатывать завершение, чтобы процесс безопасно завершил или отказался от текущей работы.
Вы можете настроить тайм-аут:
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
Не устанавливайте чрезвычайно длинные тайм-ауты остановки, если у вас нет причины. Длинные завершения замедляют развертывания, перезагрузки и восстановление после инцидентов. Рабочий процесс обычно должен прекратить принимать новую работу, завершить обрабатываемый элемент и выйти в течение ограниченного времени.
Предотвратите шумные циклы перезапуска
Restart=on-failure полезен, но сломанная служба все равно может перезапускаться многократно. Добавьте ограничения, когда режим сбоя может быть шумным:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Это говорит systemd прекратить попытки после слишком большого количества сбоев в течение интервала. Когда вы исправите проблему, сбросьте состояние сбоя:
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
Эта команда также полезна во время тестирования. Служба может оставаться в состоянии сбоя, даже если вы исправили скрипт или права.
Проверяйте модули, прежде чем полагаться на них
У systemd есть полезный верификатор:
systemd-analyze verify /etc/systemd/system/my-reporter.service
Он не поймает каждую проблему приложения, но может поймать синтаксические ошибки, неизвестные настройки для вашей версии systemd и некоторые проблемы с порядком. Запускайте его после больших правок или при копировании модуля между дистрибутивами. Возможности systemd различаются в зависимости от версии, поэтому опция ужесточения, работающая на новом сервере Fedora, может не существовать на более старом корпоративном дистрибутиве.
Также проверяйте представление зависимостей модуля, когда порядок запуска становится запутанным:
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
Первая команда показывает, что подтягивает или от чего зависит служба. Обратное представление показывает, что зависит от нее. Это полезно, когда служба запускается неожиданно или когда отключение одного модуля влияет на другой.
Решите, является ли служба системной или пользовательской
В этой статье используется системная служба в /etc/systemd/system/. Это правильный выбор для машинных служб, которые должны запускаться при загрузке и работать независимо от сеанса входа. Systemd также поддерживает пользовательские службы, обычно управляемые с помощью systemctl --user, для фоновых процессов на пользователя.
Не используйте пользовательскую службу для инфраструктурных демонов только для того, чтобы избежать sudo. Пользовательские службы имеют другие правила жизненного цикла, обработку окружения и поведение при входе. Для рабочих процессов приложений, экспортеров и агентов уровня хоста системная служба с выделенным пользователем с минимальными привилегиями обычно проще для понимания.
Держите первый модуль скучным
Заманчиво добавить каждую директиву ужесточения, которую вы найдете в интернете: частные устройства, пути только для чтения, фильтры системных вызовов, ограничения возможностей, ограничения пространств имен и многое другое. Это ценные инструменты, но добавляйте их по одному после того, как служба заработает. Когда сильно ограниченная служба не запускается, новички часто не могут сказать, неправильный ли модуль, неправильное ли приложение или настройка песочницы заблокировала файл, который ему нужен.
Хороший производственный путь — инкрементальный: работающая служба, выделенный пользователь, надежное журналирование, политика перезапуска, базовое ужесточение, затем более сильная изоляция после того, как у вас есть тест, доказывающий, что приложение все еще ведет себя правильно.