При работе в операционной системе нужно постоянно запускать разные программы. Следить за их состоянием. Перезапускать упавшее. Существует целый пласт утилит, который решает эту задачу (от простейшего init.d до навороченного svc). Сейчас в Linux стандартом де-факто стал systemd – его используют все современные дистрибутивы. Это – очень короткое и очень простое введение в systemd. Минимум текста – максимум пользы.

SystemD – это менеджер загрузки. Он получает управление от ядра при старте операционной системы (init), запускает разные сервисы и следит за их состоянием (например – перезапускает упавшие). Идеолог – Леннар Поттеринг из RedHat. Systemd очень своеобразная штука, умеет она довольно много и устройство у нее довольно сложное. Systemd заслуженно любят за мощный и гибкий функционал – это здорово облегчает жизнь разработчика и админа. Systemd заслуженно ненавидят за тягу к изобретению уже реализованных в операционной системе вещей и спорное поведение в некоторых вопросах безопасности.

Эта статья – краткое практическое руководство. Теории тут – минимум. Если вас интересует устройство systemd – вам в официальное руководство.

Введение и терминология

Systemd отвечает за запуск программ и сервисов после старта операционной системы. Он следит за процессом загрузки, решает что запустить и как. Строго говоря, он делает ровно то же самое, что делал SystemV init. Systemd писали значительно позже init – в нем пытались починить вещи, которые в init сделаны плохо. Например, в init нет возможности сделать зависимость запуска одного сервиса от другого. Так же init не поддерживает параллельной загрузки. Параллельная загрузка сервисов радикально ускоряет старт операционной системы.

Unit

Unit – это описание сервиса (в широком смысле этого слова). Unit-файл описывает все настройки сервиса, как его запускать, когда (очередность, зависимости) и что делать, если запуск не удался. Unit-ы, которые пишет пользователь руками – должны находится в /etc/systemd/system и иметь окончание .service в названии. Юниты, которые устанавливают пакеты – находятся в ином месте. Если в нескольких папках лежит юнит с одним и тем же именем – применяется тот, что лежит в /etc/systemd/system. Пример юнита:

[Unit]
Description=etcd – highly-available key value store
Documentation=https://github.com/coreos/etcd
Documentation=man:etcd
After=network.target
Wants=network-online.target

[Service]
Environment=DAEMON_ARGS=
Environment=ETCD_NAME=%H
EnvironmentFile=-/etc/default/%p
WorkingDir=/var/lib/etcd
Type=notify
User=etcd
PermissionsStartOnly=true
ExecStart=/usr/bin/etcd $DAEMON_ARGS
Restart=on-abnormal
RestartSec=10s
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Я специально взял юнит посложнее, чтобы пример был наглядным. На что обратить внимание:

  • Description – человеко-читаемое описание. Показывается по команде service <name> status
  • After – начать загрузку после того, как начнется загрузка сервиса (или цели)
  • Wants – опциональная зависимость. Подробнее ниже, в разделе про зависимости
  • Environment – создать переменную окружения при запуске этого сервиса
  • WorkingDir – демон запускается из этой папки. Аналогично cd /var/lib/etcd перед запуском
  • Type – тип сервиса. Подробнее ниже
  • User – имя пользователя, от которого будет запущен сервис
  • PermissionsStartOnly – используется, если перед стартом нужна какая-то специальная подготовка – создание папок, изменение прав и так далее. При PermissionsStartOnly=true эти действия будут выполнятся от root. Без – от имени User
  • ExecStart – что, собственно, запускать. Обязательно полный путь
  • RestartOn – при каких условиях перезапускать
  • WantedBy – в какой target должен быть установлен сервис. Подробнее – в разделе про target-ы

Виды Unit-ов

Systemd может обслуживать процессы с разным поведением. Тип описывает, как systemd будет с ним взаимодействовать. Есть следующие варианты:

  • Type=Simple – самый стандартный тип. Процесс остается в foreground, stdout перехватывается systemd. Это тип по умолчанию.
  • Type=Forking – прямая противоположность. Процесс должен форкнуться и отсоединится от foreground. Для этого типа юнитов должен быть указан pid через директиву PIDFile.
  • Type=oneshot – процесс, который успешно выполняется (не делая fork) и завершается. Пример – монтирование файловых систем. Рекомендуется добавить RemainAfterExit=yes в юнит, чтобы результаты работы процесса остался в статусе юнита.
  • Type=notify – аналог simple, но в этом случае сам процесс сообщит systemd о том, что он закончил загрузку и готов к работе.

Взаимодействие с unit-ами

После каждого изменения файла юнита (создание/изменение/удаление) – нужно перечитывать изменения, так как состояния юнитов systemd кеширует:

systemctl daemon-reload

Запус, состояние, остановка:

#запуск
systemctl start [unit]
service [unit] start

#состояние
systemctl status [unit]
service [unit] status

#остановка
systemctl stop [unit]
service [unit] stop

#сервис в автозагрузку
systemctl enable [unit]

#полностью запретить запуск сервиса (даже команда service [unit] start не поможет)
systemctl mask [unit]

#разрешить запуск обратно
systemctl unmask [unit]

Systemd имеет свою собственную реализацию логирования (хотя по умолчанию в syslog копию сообщения он тоже отправляет). Чтение сообщений от сервисов – командой journalctl. Команда очень мощная, умеет много. Ниже примеры

#чтение информации по юниту
journalctl -u [UNIT]

#чтение по PID
journalctl _PID=12

#аналогично по конкретному файлу
journalctl /usr/bin/atd

#чтение информаций о юнитах, завершившихся с ошибкой
journalctl -xn

#чтение журнала с момента загрузки
journalctl -b

#чтение журнала с определенного момента
journalctl --since="2018-01-24 10:15:10"
journalctl --since "10 minutes ago"

#постоянное отслеживание событий (аналог tail -f)
journalctl -f

#по умолчанию systemd обрезает строки по длине экрана. Запретим ему это:
journalctl -l

#фильтры можно комбинировать
journalctl -u redis -f -l --since "10 minutes ago"

Управление зависимостями, очередность загрузки юнитов

Для управления зависимостями в unit есть ключевые слова Wants, Requires и After:

  • After – сервис начнет загрузку после того, как начнет загружаться сервис, указанный в After.
  • Wants – сервис начнет загрузку после того, как закончит загружаться сервис, указанный в Wants. Статус загрузки этого сервиса не важен – даже если он упал и загрузится не смог – юнит попытается стартовать. То есть зависимость эта опциональная, и нужна она только для того, чтобы наш сервис начал загружаться не раньше, чем другой – закончит.
  • Requires – сервис начнет загрузку после того, как сервис, указанный в Requires закончит загрузку успешно. Если сервис-зависимость загрузится не смог – наш сервис так же упадет с ошибкой (точнее – он даже не будет стартовать).

Targets

Target – целевое состояние системы. Именно Target определяет, какие сервисы будут загружены и в каком порядке. Аналог из мира sysV init – runlevel. Основные виды таргетов:

  • poweroff – отключение системы
  • rescue – режим восстановления, однопользовательский (init 1)
  • multi-user – сетевой режим без графической оболочки, (init 3)
  • graphical – сетевой режим с графической оболочкой (init 5)
  • reboot – перезагрузка
  • emergency – аварийная командная строка, минимальный функционал

Цели могут наследоваться друг от друга. Например, graphical включает в себя загрузку всего, что есть multiuser + после этого – подгрузку графической оболочки.

Взаимодействие с целями:

#список целей
systemctl list-units --type=target

#перейти в нужную цель (например – загрузится из сетевого режима в графический)
systemctl isolate graphical.target

#выбрать target по умолчанию
systemctl set-default multi-user.target

Заключение

systemd на данный момент - стандарт в linux-based операционных системах. Инструмент мощный, удобный и популярный, пусть и не без особенностей. Надеюсь, эта статья поможет начать им пользоваться.