Systemd для администраторов, часть 3: HOW-TO: преобразование SysV init-скрипта в systemd service-файл
Традиционно, службы Unix и Linux (демоны) запускаются через SysV init-скрипты. Эти скрипты пишутся на языке Bourne Shell (/bin/sh), располагаются в специальном каталоге (обычно /etc/rc.d/init.d/) и вызываются с одним из стандартных параметров (start, stop, reload и т.п.) — таким образом указывается действие, которое необходимо прозвести над службой (запустить, остановить, заставить перечитать конфигурацию). При запуске службы такой скрипт, как правило, вызывает бинарник демона, который, в свою очередь, форкается, порождая фоновый процесс (т.е. демонизируется). Заметим, что shell-скрипты, как правило, отличается низкой скоростью работы, излишней подробностью изложения и крайней хрупкостью. Читать их, из-за изобилия всевозможного вспомогательного и дополнительного кода, чрезвычайно тяжело. Впрочем, нельзя не упомянуть, что эти скрипты являются очень гибким инструментом (ведь, по сути, это всего лишь код, который можно модифицировать как угодно). С другой стороны, многие задачи, возникающие при работе со службами, довольно тяжело решить средствами shell-скриптов. К таким задачам относятся: организация параллельного исполнения, корректное отслеживание процессов, конфигурирование различных параметров среды исполнения процесса. systemd обеспечивает совместимость с init-скриптами, однако, с учетом описанных выше их недостатков, более правильным решением будет использование штатных service-файлов systemd для всех установленных в системе служб. Стоит отметить что, в отличие от init-скриптов, которые часто приходится модифицировать при переносе из одного дистрибутива в другой, один и тот же service-файл будет работать в любом дистрибутиве, использующем systemd (а таких дистрибутивов с каждым днем становится все больше и больше). Далее мы вкратце рассмотрим процесс преобразования SysV init-скрипта в service-файл systemd. Вообще говоря, service-файл должен создаваться разработчиками каждого демона, и включаться в комплект его поставки. Если вам удалось успешно создать работоспособный service-файл для какого-либо демона, настоятельно рекомендуем вам отправить этот файл разработчикам. Вопросы по полноценной интеграции демонов с systemd, с максимальным использованием всех его возможностей, будут рассмотрены в последующих статьях этого цикла, пока же ограничимся ссылкой на страницу официальной документации.
Итак, приступим. В качестве пример возьмем init-скрипт демона ABRT (Automatic Bug Reporting Tool, службы, занимающейся сбором crash dump'ов). Исходный скрипт (в варианте для дистрибутива Fedora) можно загрузить здесь.
Начнем с того, что прочитаем исходный скрипт (неожиданный ход, правда?) и выделим полезную информацию из груды хлама. Практически у всех init-скриптов большая часть кода является чисто вспомогательной, и мало чем отличается от одного скрипта к другому. Как правило, при создании новых скриптов этот код просто копируется из уже существующих (разработка в стиле copy-paste). Итак, в исследуемом скрипте нас интересует следующая информация:
- Строка описания службы: «Daemon to detect crashing apps». Как нетрудно заметить, комментарии в заголовке скрипта весьма пространны и описывают не сколько саму службу, сколько скрипт, ее запускающий. service-файлы systemd также включают описание, но оно относится исключительно к службе, а не к service-файлу.
- LSB-заголовок[1], содержащий информацию о зависимостях. systemd, базирующийся на идеях socket-активации, обычно не требует явного описания зависимостей (либо требует самого минимального описания). Заметим, что основополагающие принципы systemd, включая socket-активацию, рассмотрены в статье Rethinking PID 1, в которой systemd был впервые представлен широкой публике. Ее русский перевод можно прочитать здесь: часть 1, часть 2. Возвращаясь к нашему примеру: в данном случае ценной информацией о зависимостях является только строка Required-Start: $syslog, сообщающая, что для работы abrtd требуется демон системного лога. Информация о второй зависимости, $local_fs, является избыточной, так как systemd приступает к запуску служб уже после того, как все файловые системы готовы для работы.
- Также, LSB-заголовок сообщает, что данная служба должна быть запущена на уровнях исполнения (runlevels) 3 (консольный многопользовательский) и 5 (графический многопользовательской).
- Исполняемый бинарник демона называется /usr/sbin/abrtd.
Вот и вся полезная информация. Все остальное содержимое 115-строчного скрипта является чисто вспомогательным кодом: операции синхронизации и упорядочивания запуска (код, относящийся к lock-файлам), вывод информационных сообщений (команды echo), разбор входных параметров (монструозный блок case).
На основе приведенной выше информации, мы можем написать следующий service-файл:
[Unit] Description=Daemon to detect crashing apps After=syslog.target [Service] ExecStart=/usr/sbin/abrtd Type=forking [Install] WantedBy=multi-user.target
Рассмотрим этот файл поподробнее.
Секция [Unit] содержит самую общую информацию о службе. Не будем забывать, что systemd управляет не только службами, но и многими другими объектами, в частности, устройствами, точками монтирования, таймерами и т.п. Общее наименование всех этих объектов — юнит (unit). Одноименная секция конфигурационного файла определяет наиболее общие свойства, которые могут быть присущи любому юниту. В нашем случае это, во-первых, строка описания, и во-вторых, указание, что данный юнит рекомендуется активировать после запуска демона системного лога[2]. Эта информация, как мы помним, была указана в LSB-заголовке исходного init-скрипта. В нашем конфигурационном файле мы указываем зависимость от демона системного лога при помощи директивы After, указывающей на юнит syslog.taget. Это специальный юнит, позволяющий ссылаться на любую реализацию демона системного лога, независимо от используемой программы (например, rsyslog или syslog-ng) и типа активации (как обычной службы или через log-сокет). Подробнее о таких специальных юнитах можно почитать страницу официальной документации. Обратите внимание, что директива After, в отсутствие директивы Requires, задает лишь порядок загрузки, но не задает жесткой зависимости. То есть, если при загрузке конфигурация systemd будет предписывать запуск как демона системного лога, так и abrtd, то сначала будет запущен демон системного лога, и только потом abrtd. Если же конфигурация не будет содержать явного указания запустить демон системного лога, он не будет запущен даже при запуске abrtd. И это поведение нас полностью устраивает, так как abrtd прекрасно может обходиться и без демона системного лога. В противном случае, мы могли бы воспользоваться директивой Requires, задающей жесткую зависимость между юнитами.
Следующая секция, [Service], содержит информацию о службе. Сюда включаются настройки, относящие именно к службам, но не к другим типам юнитов. В нашем случае, таких настроек две: ExecStart, определяющая расположение бинарника демона и аргументы, с которыми он будет вызван (в нашем случае они отсутствуют), и Type, позволяющая задать метод, по которому systemd определит окончание периода запуска службы. Традиционный для Unix метод демонизации процесса, когда исходный процесс форкается, порождая демона, после чего завершается, описывается типом forking (как в нашем случае). Таким образом, systemd считает службу запущенной с момента завершения работы исходного процесса, и рассматривает в качестве основного процесса этой службы порожденный им процесс-демон.
И наконец, третья секция, [Install]. Она содержит рекомендации по установке конкретного юнита, указывающие, в каких ситуациях он должен быть активирован. В нашем случае, служба abrtd запускается при активации юнита multi-user.target. Это специальный юнит, примерно соответствующий роли третьего уровня исполнения классического SysV[3]. Директива WantedBy никак не влияет на уже работающую службу, но она играет важную роль при выполнении команды systemctl enable, задавая, в каких условиях должен активироваться устанавливаемый юнит. В нашем примере, служба abrtd будет активироваться при переходе в состояние multi-user.target, т.е., при каждой нормальной загрузке[4] (к «ненормальным» можно отнести, например, загрузки в режиме emergency.target, который является аналогом первого уровня исполнения в классической SysV).
Вот и все. Мы получили минимальный рабочий service-файл systemd. Чтобы проверить его работоспособность, скопируем его в /etc/systemd/system/abrtd.service, после чего командой systemctl daemon-reload уведомим systemd об изменении конфигурации. Теперь нам остается только запустить нашу службу: systemctl start abrtd.service. Проверить состояние службы можно командой systemctl status abrtd.service, а чтобы остановить ее, нужно скомандовать systemctl stop abrtd.service. И наконец, команда systemctl enable abrtd.service выполнит установку service-файла, обеспечив его активацию при каждой загрузке (аналог chkconfig abrtd on в классическом SysV).
Приведенный выше service-файл является практический точным переводом исходного init-скрипта, и он никак не использует широкий спектр возможностей, предоставляемых systemd. Ниже приведен немного улучшенный вариант этого же файла:
[Unit] Description=ABRT Automated Bug Reporting Tool After=syslog.target [Service] Type=dbus BusName=com.redhat.abrt ExecStart=/usr/sbin/abrtd -d -s [Install] WantedBy=multi-user.target
Чем же новый вариант отличается от предыдущего? Ну, прежде всего, мы уточнили описание службы. Однако, ключевым изменением является замена значения Type с forking на dbus и связанные с ней изменения: добавление имени службы в шине D-Bus (директива BusName) и задание дополнительных аргументов abrtd «-d -s». Но зачем вообще нужна эта замена? Каков ее практический смысл? Чтобы ответить на этот вопрос, мы снова возвращаемся к демонизации. В ходе этой операции, процесс дважды форкается и отключается от всех терминалов. Это очень удобно при запуске демона через скрипт, но в случае использования таких продвинутых систем инициализации, как systemd, подобное поведение не дает никаких преимуществ, но вызывает неоправданные задержки. Даже если мы оставим в стороне вопрос скорости загрузки, останется такой важный аспект, как отслеживание состояния служб. systemd решает и эту задачу, контролируя работу службы и при необходимости реагируя на различные события. Например, при неожиданном падении основного процесса службы, systemd должен зарегистрировать идентификатор и код выхода процесса, также, в зависимости от настроек, он может попытаться перезапустить службу, либо активировать какой-либо заранее заданный юнит. Операция демонизации несколько затрудняет решение этих задач, так как обычно довольно сложно найти связь демонизированного процесса с исходным (собственно, смысл демонизации как раз и сводится к уничтожению этой связи) и, соответственно, для systemd сложнее определить, какой из порожденных в рамках данной службы процессов является основным. Чтобы упростить для него решение этой задачи, мы и воспользовались типом запуска dbus. Он подходит для всех служб, которые в конце процесса инициализации регистрируют свое имя на шине D-Bus[5]. ABRTd относится к ним. С новыми настройками, systemd запустит процесс abrtd, который уже не будет форкаться (согласно указанным нами ключам «-d -s»), и в качестве момента окончания периода запуска данной службы systemd будет рассматривать момент регистрации имени com.redhat.abrt на шине D-Bus. В этом случае основным для данной службы будет считаться процесс, непосредственно порожденный systemd. Таким образом, systemd располагает удобным методом для определения момента окончания запуска службы, а также может легко отслеживать ее состояние.
Собственно, это все, что нужно было сделать. Мы получили простой конфигурационный файл, в 10 строчках которого содержится больше полезной информации, чем в 115 строках исходного init-скрипта. Добавляя в наш файл по одной строчке, мы можем использовать различные полезные функции systemd, создание аналога которых в традиционном init-скрипте потребовало бы значительных усилий. Например, добавив строку Restart=restart-always, мы приказываем systemd автоматически перезапускать службу после каждого ее падения. Или, например, добавив OOMScoreAdjust=-500, мы попросим ядро сберечь эту службу, даже если OOM Killer выйдет на тропу войны. А если мы добавим строчку CPUSchedulingPolicy=idle, процесс abrtd будет работать только в те моменты, когда система больше ничем не занята, что позволит не создавать помех для процессов, активно использующих CPU.
За более подробным описанием всех опций настройки, вы можете обратиться к страницам рукводства systemd.unit, systemd.service, systemd.exec. Полный список доступных страниц можно просмотреть здесь.
Конечно, отнюдь не все init-скрипты так же легко преобразовать в service-файлы. Но, к счастью, «проблемных» скриптов не так уж и много.
Примечания
1. LSB-заголовок — определенная в Linux Standard Base схема записи метаданных о службах в блоках комментариев соответствующих init-скриптов. Изначально эта схема была введена именно для того, чтобы стандартизировать init-скрипты во всех дистрибутивах. Однако разработчики многих дистрибутивов не считают нужным точно исполнять требования LSB, и поэтому формы представления метаданных в различных дистрибутивах могут отличаться. Вследствие этого, при переносе init-скрипта из одного дистрибутива в другой, скрипт приходится модифицировать. Например, демон пересылки почты при описании зависимостей может именоваться MTA или smtpdaemon (Fedora), smtp (openSUSE), mail-transport-agent (Debian и Ubuntu), mail-transfer-agent. Таким образом, можно утверждать, что стандарт LSB не справляется с поставленной задачей.
2. Строго говоря, эту зависимость здесь указывать не нужно — в системах, в которых демон системного лога активируется через сокет, эта зависимость является избыточной. Современные реализации демона системного лога (например, rsyslog начиная с пятой версии) поддерживают активацию через сокет. В системах, использующих такие реализации, явное указание After=syslog.target будет избыточным, так как соответствующая функциональность поддерживается автоматически. Однако, эту строчку стоит все-таки указать для обеспечения совместимости с системами, использующими устаревшие реализации демона системного лога.
3. В том контексте, в котором он используется в большинстве дистрибутивов семейства Red Hat, а именно, многопользовательский режим без запуска графической оболочки.
4. Обратите внимание, что режим графической загрузки в systemd (graphical.target, аналог runlevel 5 в SysV) является надстройкой над режимом многопользовательской консольной загрузки (multi-user.target, аналог runlevel 3 в SysV). Таким образом, все службы, запускаемые в режиме multi-user.target, будут также запускаться и в режиме graphical.target.
5. В настоящее время практически все службы дистрибутива Fedora после запуска регистрируется на шине D-Bus.