Выполнение команд в systemd unit
Systemd Unit - выполнение команд из юнита
Для чего это нужно?
Например у нас стоит следующая задача: написать Systemd Service Unit, который будет запускать приложение /usr/local/bin/node_exporter
и нам необходимо, чтобы при этом соблюдались следующие условия:
- node_exporter запускаться после готовности network.target;
- запускаться от имени пользователя prometheus;
- хранит PID файл в /var/run/node_exporter.pid;
Самым сложным вопросом в данной условиях является выполнение команды которая позволит нам получит PID процесса и записать его в требуемый pid файл: /var/run/node_exporter.pid
Давайте рассмотри содержимое Systemd Service Unit и разберёмся, что же в нём особенное.
[Unit]
Description=Prometheus Node Exporter
Wants=network-online.target
After=network.target
[Service]
Type=simple
User=prometheus
ExecStart=/usr/local/bin/node_exporter
ExecStartPost=+sh -c 'pidof -s node_exporter > /var/run/node_exporter.pid'
Restart=always
PIDFile=/var/run/node_exporter.pid
[Install]
WantedBy=multi-user.target
Итак, разделы [Unit] и [Install] ничего особенного для нас не представляют, однако давайте для лучшего понимания рассмотрим и их в порядке от наименее интересных нам в рамках данной темы к наиболее интересным:
- [Unit]
- Description=Prometheus Node Exporte #Эта директива может использоваться для описания имени и основных функций;
- Wants=network-online.target #В этой директиве перечислены все юниты, от которых зависит этот юнит (служба или устройство);
- After=network.target #Юниты, перечисленные в этой директиве, будут запущены до запуска текущего;
- [Install]
- WantedBy=multi-user.target #Определяет на каком загрузочном таргете запускать этот юнит.
- [Service]
- Type=simple # systemd запустит эту службу незамедлительно. Процесс при этом не должен разветвляться (fork).
- User=prometheus #. Указывает имя пользователя, от которого будет запускаться сервис;
- ExecStart=/usr/local/bin/node_exporter # Указывает путь к исполняемому файлу;
- ExecStartPost=+sh -c ‘pidof -s node_exporter > /var/run/node_exporter.pid’ # Набор команд выполняемых сразу после запуска основного процесса. Данная строка нам наиболее интересна и подробнейшим образом мы её рассмотрим ниже;
- Restart=always # Указывает на то, что служба будет стремиться перезагрузиться в случае ошибок и других непредвиденных обстоятельств;
- PIDFile=/var/run/node_exporter.pid # Ввиду того, что для типа “Simple” pid-файл не создается автоматически, мы создаем его командой приведённой выше, чтобы systemd мог отслеживать основной процесс;
Разбор полётов
Итак, давайте разбираться в том, что мы имеем в строке:
ExecStartPost=+sh -c 'pidof -s node_exporter > /var/run/node_exporter.pid'
ExecStartPost=
- директива которая определяет, какой набор команд будет выполнен сразу после того, как стартует основной сервис.+
- директива, которая повышает привилегии на исполнение команды;sh -c
- указывает на то, что командной оболочке нужно считывать команды из операндаcommand_string
(из строки), вместо стандартного ввода.'pidof -s node_exporter > /var/run/node_exporter.pid'
- и есть наша командная строка, которая при помощиpidof -s node_exporter
считывае PID процессаnode_exporter
и по средствам перенаправления вывода>
отправляет PID в заданный нами файл/var/run/node_exporter.pid
Почему именно так?
Такой подход необходим ввиду того, что файл /var/run/node_exporter.pid
имеет права на запись только для владельца, а владельцем его является root
, при этом наш пользователь prometheus
привилегий суперпользователя не имеет.
Казалось бы: мы же указываем директиву +
и она повышает уровень привилегий до необходимого… Да именно, так и происходит. Однако уровень привилегий повышается только для части команды, а именно: pidof -s node_exporter
, при этом данной команде такой уровень привилегий и не нужен, а нужен он именно для перенаправления в файл > /var/run/node_exporter.pid
.
Именно для того, чтобы уровень привилегий был повышен для всей цепочки команд, мы и завернули его в строку и отправили в шелл.