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

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

    И заканчивать его:
    systemctl stop apache2.service
    systemctl stop mysqld.service
    /root/scripts/mysqld-tmpfs stop
    

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

    Чего я хотел?


    Я хотел достичь 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. В какой-то момент в консоли можно набрать:
    systemctl isolate my-another.target
    

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

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

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


    В итоге у меня получился вот такой mysqld-tmpfs.service файл:
    Description=Mount a MySQL database into tmpfs.
    # Мой /root/scripts/tmpfs скрипт может работать как при включенном, так и при выключенном mysql сервисе. Но если бы mysql сервис нужен был включенным, к примеру, то я бы добавил эти строки:
    #Requires=mysqld.service
    #After=mysqld.service
    
    [Service]
    # Даем знать systemd, что этот сервис представляет из себя лишь 1 процесс. Man page хорошо описывает доступные опции.
    Type=oneshot
    # Выполнить эту команду при запуске сервиса.
    ExecStart=/root/scripts/mysqld-tmpfs start
    # Выполнить эту команду при остановке сервиса.
    ExecStop=/root/scripts/mysqld-tmpfs stop
    # Даем знать systemd, что сервис нужно считать запущенным, даже если основной процесс прекратил свою работу. Как раз то, что мне нужно: мой процесс выполнит монтировку и после этого прекратит свою работу, но должен считаться активным, т.к. монтировка осталась в системе.
    RemainAfterExit=yes
    

    И вот такой programming.target файл:
    [Unit]
    Description=Working/Programming target
    Requires=mysqld.service
    Requires=apache2.service
    # Сюда я могу дописывать новые сервисы “Requires=another.service”, если они мне понадобятся в повседневной работе.
    


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


    При остановке 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

    Результат


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

    Когда нужно выполнить тесты на своем проекте:
    systemctl start mysqld-tmpfs.service
    

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

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


    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. Я попытался сделать ее компактной, и если упустил из внимания какие-то дополнительные вопросы, спрашивайте в комментариях!
    • +17
    • 36,1k
    • 9
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 9
    • +1
      Хорошая статья для новичков, спасибо.
      • +2
        Мне кажется, в вашем кейсе можно было бы написать просто баш скрипт.
        • 0
          Да, но хотелось все-таки познакомиться с новой игрушкой — systemd, чтобы в случае чего уже иметь полезный опыт. Ну и в конечном итоге я нашел очень мало информации по .targest… или у меня мозг как-то плохо воспринимал доки на эту тему, поэтому я на практике немного разобрался с ними и в итоге написал эту статью с надеждой сэкономить кому-нибудь то время, которое я потратил на переваривание доков по targets.
        • –2
          Правильней и проще было бы использовать docker.
          • –1
            Я с докером еще не работал, только слышал о нем. Ну и init process, мне кажется, именно для таких вещей как диспетчинг сервисов и был создан. Закончу со своим домашним линуксом и пойду докер осваивать :)
            • +1
              Чего?! Это неправильней и не проще.
            • +1
              Мне было лень писать 2 команды (запуск apache и запуск mysql), т.к. я знал, что обе программы всегда будут выключаться и включаться синхронно. Хотелось выполнять эту операцию одной командой.

              systemctl start apache2.service mysqld.service
              Ну это так, к слову. А ещё можно было настроить активацию на сокетах и вообще не пришлось бы тыкать systemctl лишний раз — нужные сервисы сами бы включились по требованию.
              • 0
                На сокетах было бы очень интересно реализовать. Правда в моем конкретном случае не думаю, что меня оно устроило бы, т.к. у меня в браузере с прошлой сессии могла остаться вкладка на localhost и она бы «дернула» запуск рабочих сервисов вопреки моему желанию.
              • 0
                Не обязательно делать target, вполне можно было сделать сервис с опцией PartOf и Wants.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое