Linux свой systemd unit

/4te.me

Здесь покажу как писать инитники для автозапуска какого-либо демона в системе с systemd.

Systemd оперирует unit-файлами в качестве конфигурационных файлов. Это как .conf-файл для upstart или init-скрипты для initd. Unit-файлы также могут описывать и другие системные сущности, но в данном случае они нас интересуют как конфиг для автостарта сервиса. В Ubuntu 16.04 они лежат в /etc/systemd/ . Напишем свой unit-файл.

Допустим у нас есть программа, которую мы хотим запускать как демон. Вот здесь я писал как создать telegram-бота. У меня получился исполняемый файл, который запускается и висит ждет сообщений. Теперь хочу чтобы он как демон стартовал при загрузке системы.

Создаем такой файл:

и кладем его в /etc/systemd/system/telegram-bot.service

Конфиг похож на обычный ini-файл:

After — запускать этот юнит после определенных демонов или целей (цель — это набор юнитов). Здесь указан network.target , это значит, что демон запустится после того как поднимутся сетевые интерфейсы.

Type — тип того, как запускается демон. Чаще всего используется simple , forking или one-shot . simple — демон запускается и начинает ожидать на консоле и не отфоркивается. forking — демон запускается, потом форкается и завершает родительский процесс. Многие программы именно так и запускаются, чтобы перейти в background режим. Например, nginx запускается по такой схеме. one-shot — используется для запуска скриптов которые запускаются, отрабатывают и завершаются. В моем случае это не скрипт и программа не форкается , поэтому тип — simple

ExecStart — команда для запуска демона.

Restart — рестартовать демон, если он завершится/упадет. При always systemd будет перезапускать демон независимо от того почему он завершился. Можно указать on-failure , тогда будет перезапускаться если демон вышел с ненулевым кодом возврата или был завершен по сигналу (kill DAEMONPID)

WantedBy — говорим, что запускать этот демон когда система грузится в multi-user режиме

Далее делаем релоад systemd:

И добавляем созданный юнит в автозагрузку:

Все. Сервис добавлен в автозагрузку, но еще не запущен. Запустим:

Источник

Про Debian

В некоторых следующих статьях помимо прочего придется запускать некоторые сервисы без «изкоробочного» init-скрипта или юнита. В 2018 году мне уже пришлось смириться с победой systemd и показывать в тех статьях init-скрипты я уже не буду. Но и рассказывать в каждой статье всю последовательность действий мне будет лень. Поэтому здесь я расскажу, что делать с unit-ом помимо создания, собственно, текстового файла.
Правда, сначала я немного погружусь в воспоминания и включу ворчалку. Мне очень нравился upstart после SysV. Писать простенький конфиг в 7 строк вместо развесистых init-скриптов — лафа. Хотя иногда и требуется бОльшая гибкость, чем в upstart, или unit-ах, но это редкость. Systemd был бы очень клёвой штукой, хорошим продолжением после upstart, если очкарик не тащил бы в него всё подряд. Основные проблемы systemd связаны не с тем, как он запускает сервисы, а с тем, что внутри systemd вконопачены хреновенький dns, ntp и куча чего-то там ещё. На этом холивар предлагаю закончить — писать unit-ы проще, чем init.d (хоть они и не настолько гибкие).
Какой unit мы напишем?

  • Мы запустим программу /usr/bin/holycrap
  • Она будет выполняться в бесконечном цикле — умерла, запустилась заново (в том и смысл unit-ов основной), пока не скажешь stop. Ну то есть проще говоря, у нас из коробки есть простенький watchdog
  • программу запустим от отдельного пользователя и группы
  • передадим переменную окружения прямо из unit-а

Очень важно понимать, что наша программа должна работать именно в foreground (проще говоря — не отдавать консоль обратно сразу после запуска), дабы не мучаться с ExecStartPre, ExecReload и прочими. Если же программа будет сразу уходить в background, то unit из данного примера будет бесконечно перезапускать её.
Поехали. Создаём файл /etc/systemd/system/holycrap.service

Читайте также:  Visual studio проект linux

# открываем секцию Unit
[Unit]
# Описание Unit-а (видно в service status). Ну вдруг забудем, что это)
Description=Some usefull text

# Наш сервис будет запускаться после того, как поднимется сеть:
After=network.target

# открываем секцию [Service] — здесь будет описано всё про нашу программу.
[Service]
# указываем, что наш сервис — обычная программа, работающая в foreground
Type=simple
# указываем путь до исполняемого файла:
ExecStart=/usr/bin/holycrap
# указываем WorkingDirectory. По сути, эта настройка эмулирует команду «cd /home/holycrap» перед запуском самого бинаря (нужно для некоторого софта, например nodejs).
WorkingDirectory=/home/holycrap
# указываем параметры для watchdog:
# указываем, что бинарь нужно запустить снова, если он умрет
Restart=on-failure
# указываем, что перед повторным запуском нужно подождать 10 секунд.
RestartSec=10s
# указываем пользователя и группу, от имени которых будет запускаться процесс:
User=holycrap
Group=holycrap
# задаём Umask для процесса и его потомков.
# полезно, если хочется, чтобы все файлы данным процессом создавались с chmod 777 (тогда укажите umask 000), или chmod 700 (тогда umask 077). Не забудьте, что umask — инвертированный chmod.
UMask=077
# ну и передадим переменную окружения PORT=9000, местами бывает удобно:
Environment=PORT=9000

# указываем, что наш сервис нужно запускать в multi-user runlevel (оно тут строго говоря для того, чтобы не запускалось в single mode — так-то нам куда важнее network.target, указанный выше
[Install]
WantedBy=multi-user.target

То же самое, но «удобное для копипасты». Только учтите, что здесь почти нет обязательных параметров (в самых простых случаях я оставляю только Description=, After=, ExecStart= и WantedBy=

[Unit]
Description=Some usefull text
After=network.target

В названии файла /etc/systemd/system/holycrap.service важны 2 вещи. Во-первых, файлы без расширения .service будут игнорироваться. Во-вторых, holycrap в данном случае будет названием нашего сервиса (именно его мы будем передавать командам service или systemctl).
Файл мы создали, теперь нужно «применить» его. Учтите, что эту команду нужно выполнять после любого изменения .service-файла, иначе systemd будет использовать старый unit (ну вообще он чё-то там может поругаться, что unit изменился и вообще ничего не делать).

# systemctl daemon-reload

Теперь мы можем управлять нашим сервисом (ну и он уже в «автозагрузке», если что):

# service holycrap start|stop|restart

Неплохо было бы посмотреть, что наш бинарь понаписал в STDOUT/STDERR (ведь с первого раза по традиции ничего не заработает). Вообще логи с моей точки зрения — больное место systemd. Не потому что плохо работают, а потому что всю эту логику придумал больной на голову человек.
В systemd 236 появились, конечно, параметры StandardOutput=file:/path/to/file и StandardError= (видимо, больной человек голову вылечил и прислушался к пользователям), но проблема тут ещё и в том, что ни в debian 8, ни в debian 9 эти параметры работать не будут (версия старая). Поэтому придется понять логику этого психа.
Посмотреть что-то как-то из STDOUT и STDERR бинаря после запуска сервиса можно так:

# service holycrap status

Только вот это будет именно «что-то» и «как-то». Даже длинную строку поскроллить вправо не получится. Зато запомнить легко, ну а там — вдруг повезет.
Предполагается, что каждый пользователь должен уметь в journalctl. Кстати, эта пежня считает себя слишком гордой, чтобы использовать системный pager (less например), поэтому вы будете охуевать, пытаясь посмотреть большой лог, если не сделаете хотя бы | grep (ну или сразу |less).
Посмотрим все логи для нашего unit-а (там, кстати, пишут не только std, но и сообщения от самого systemd, если он не смог распарсить .service-файл, например:

Читайте также:  Не открывает диспетчер задач windows

# journalctl -u holycrap

Если логов слишком дохуя, попробуем посмотреть только с момента загрузки системы (тоже именно для нашего сервиса) — опция -b:

# journalctl -b -u holycrap

Проблемы начинаются, когда хочется посмотреть лог для текущего старта (не, ну можно tail -20 и глазами) — вот тут и всплывает инопланетная логика очкарика и его последователей. Так, блять, нельзя О_о. Ну или оно настолько неочевидно в мане и гугле, что за год я так и не смог найти.
Если мы знаем pid процесса, то в целом можно посмотреть лог по пиду. Если он один, мать его… Ах да, смотрите на чудесную инопланетную логику в действии. Чтобы посмотреть логи по пиду, нужно использовать journalctl вот с таким параметром (9999 — сам pid):

# journalctl _PID=9999

Я всё же привык к опциям, которые начинаются с дефиса или двух… Может быть с буквы (если это позиционный параметр). Но чтобы с _ ?
Можно посмотреть лог за последние несколько минут (это, кстати, удобно, если не обращать внимания на нашу текущую цель). Если вы помните, когда перезапускали сервис — поможет.

# journalctl -b -u holycrap —since=»25 min ago»

Можно запустить journalctl в режиме «tail -f» (опять же — для сервиса holycrap) и порестартить сервис в соседней консоли:

# journalctl -f -u holycrap

Источник

Systemd: пишем собственные .service и .target

У меня появился Linux на домашнем компьютере, и я поспешил обжиться в новой ОС. Она была установлена с systemd init process. Это было мое первое знакомство с этим новым инструментом. Cвой ноутбук я использую для каждодневной жизни и для программирования. Мне хотелось включать рабочие программы (Apache2 и MySQL) только на время, пока я их использую, чтобы не тратить впустую ресурсы своего компьютера. Дополнительно, для тестирования я написал bash скрипт, который выгружает содержимое одной из MySQL БД c жесткого диска в ОЗУ (в tmpfs) – так тесты выполняются значительно быстрее. По идее, я мог бы начинать свой рабочий день вот так:

И заканчивать его:

Но мне хотелось сделать вещи “как надо”.

Чего я хотел?

Я хотел достичь 2 целей:

  1. Мне было лень писать 2 команды (запуск apache и запуск mysql), т.к. я знал, что обе программы всегда будут выключаться и включаться синхронно. Хотелось выполнять эту операцию одной командой.
  2. Дело попахивало неприятностями, если компьютер перезагрузится пока моя база данных будет сидеть в tmpfs – все файлы будут потеряны. Конечно, я делал бекапы, но мне опять же было лень восстанавливать их вручную после каждой непредвиденной перезагрузки.

Что я сделал?

В итоге я объединил Apache2 и MySQL в один target. Это позволило запускать оба сервиса одной командой. А свой mysqld-tmpfs скрипт я декларировал в виде сервиса в глазах systemd. Будучи сервисом, я уверен, что systemd выполнит его корректную остановку, если система пойдет на перезагрузку или еще в какую-то нештатную ситуацию, и моя БД без потерь сохранится на жесткий диск.

Что такое service?

Это некоторая программа, которая выполняется в фоне и предоставляет полезную функциональность. К примеру, Apache веб сервер. Сервисы можно запускать и останавливать. Некоторые сервисы могут запускаться и останавливаться автоматически по определенным событиям (загрузка ОС, выгрузка ОС и тп). Так же их можно запускать/останавливать вручную. Сервис декларируется в /etc/systemd/system/my-name.service файлах (с суффиксом “.service”).

Что такое target?

Target в systemd очень похож на runlevel в openRC, но это все-таки разные вещи. Во-первых, target позволяет группировать 1 и более сервисов в единый блок. Группируя сервисы в targets, ими проще управлять. Во-вторых, systemd автоматически включает/выключает targets по событиям. “Включение” target означает включение всех сервисов, которые он объединяет в себе. К примеру, если в systemd настроен target по умолчанию my-favorite.target, то при загрузке системы systemd включит все сервисы, которые задекларированы внутри my-favorite.target. В какой-то момент в консоли можно набрать:

Читайте также:  Large file support windows

Все сервисы из my-another.target будут включены, и все включенные сервисы не из my-another.target будут выключены. Это очень похоже на переключение runlevel в openRC. Однако, systemd поддерживает включение более чем 1 target. Вот пример:

После выполнения этих команд в системе будет работать объединение сервисов из my-favorite.target и my-another.target.

Как я это сделал?

В итоге у меня получился вот такой mysqld-tmpfs.service файл:

И вот такой programming.target файл:

Какие были проблемы?

При остановке programming.target почему-то нижележащие apache2.service и mysqld.service не останавливались. Почитав как следует man page, я нашел проблему: systemd останавливает сервисы “лениво” — если никто не требует запущенный сервис, и он не был запущен явным образом, а как зависимость для какого-то другого сервиса, то systemd остановит его только при одном из 3 обстоятельств:

  1. Запустится какой-то другой сервис, который в своей декларации указывает, что он конфликтует с нашим сервисом.
  2. Выполнится systemctl isolate some-another.target или systemctl stop this.service.
  3. Наш сервис может запросить в своей декларации останавливать себя не ленивым образом, а активным, добавив вот такую строку в [Unit] секцию: StopWhenUnneeded=true

Декларации “чужих” сервисов можно менять создавая файлы /etc/systemd/system/name-i-alter.service.d/*.conf. Я просто создал /etc/systemd/system/apache2.service/auto-stop.conf и /etc/systemd/system/mysqld.service.d/auto-stop.conf и поместил туда ту строку.

Другая проблема, на которую я, наткнулся была в том, что systemd не очень любит symlinks. Я не большой любитель “загаживать” системные директории типа /etc, /bin, /usr своими локальными продуктами жизнедеятельности, поэтому изначально я попытался свой /etc/systemd/system/mysqld-tmpfs.service сделать symlink на /root/scripts/mysqld-tmpfs.service файл, т.е. хранить сам файл в домашнем каталоге root пользователя. Но systemctl команда отказывалась работать с таким сервисом выдавая малопонятные ошибки. Оказалось, что определенную часть своей внутренней кухни systemd делает именно на symlinks, и ему тогда “трудно” отличать внутреннюю кухню (свои symlinks) от сторонних *.service файлов (если они тоже являются symlinks). Удалив symlink из /etc/systemd/system/mysqld-tmpfs.service и скопировав туда содержимое настоящего файла, я решил эту проблему. Более подробное описание этой проблемы можно прочитать тут: bugzilla.redhat.com/show_bug.cgi?id=955379

Результат

Я достиг своей цели. Начиная рабочий день:

Когда нужно выполнить тесты на своем проекте:

Когда я хочу демонтировать БД из tmpfs в жесткий диск (хотя на практике я так почти не делаю, а просто оставляю БД в tmpfs на целый день, и при выключении systemd за меня запускает демонтировку из tmpfs в жесткий диск):

Когда я закончил работать и хочу остановить рабочие программы:

Cheat sheet

Некоторые полезности при работе с systemd:

  • Вызывайте systemctl daemon-reload, если вы изменили декларацию чего-либо (systemd считает файлы декларации заново)
  • systemctl start my-name.(service|target) – запуск сервиса или target
  • systemctl stop my-name.(service|target) – остановка сервиса или target
  • systemctl enable my-name.service – сервисы могут декларировать при каких включенных targets они должны включаться. Для этого используется [Install] секция в файле декларации сервиса. Вы, как сисадмин, имеете власть на установку этого “пожелания” сервиса. Часто сервисы “устанавливаются” в target по умолчанию multi-user.target или в похожее.
  • systemctl disable my-name.service – обратная операция по отношению к enable: деассоциировать связь между my-name.service и targets, которые он запросил в [Install] секции своей декларации.
  • systemctl isolate my.target — включить все сервисы из my.target и выключить все остальные включенные сервисы.
  • systemctl status my-name.(service|target) — узнать статус (запущен/остановлен) у сервиса или target.

Надеюсь, эта статья кому-то поможет при осваивании systemd. Я попытался сделать ее компактной, и если упустил из внимания какие-то дополнительные вопросы, спрашивайте в комментариях!

Источник

Оцените статью