Где происходит цикл упорядочивания Systemd?

Вопрос или проблема

Где происходит цикл упорядочивания Systemd?

У меня есть два юнита Systemd: сервис и сокет. Желаемое поведение заключается в том, что когда я запускаю сокет, сервис сначала должен запуститься, а затем сокет. Когда любой из них останавливается, оба должны остановиться. Сервис связывается с сокетом до запуска юнита сокета, поэтому я указал его как уведомляющий сервис, чтобы он не сообщал о запуске, пока не установит привязку к сокету.

Вот файлы для двух юнитов.

#rustyvxcan.service
[Unit]
Description=Сервис плагина Docker VXCAN
#PartOf=rustyvxcan.socket
    
Before=docker.service
After=network.target
    
[Service]
Type=notify
ExecStartPre=/usr/bin/mkdir -p /run/docker/plugins
ExecStart=/home/braedon/.cargo/bin/rustycan4docker
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
#rustyvxcan.socket
[Unit]
Description=Сетевой плагин для vxcan
Before=docker.service
AssertPathExists=/run/docker/plugins
Requires=rustyvxcan.service
After=rustyvxcan.service

[Socket]
ListenStream=/run/docker/plugins/rustyvxcan.sock
RemoveOnStop=True

[Install]
WantedBy=sockets.target

Сообщение об ошибке выглядит следующим образом.

19 сен 16:50:38 localhost systemd[1]: rustyvxcan.service: Найден цикл порядка на rustyvxcan.socket/start
19 сен 16:50:38 localhost systemd[1]: rustyvxcan.service: Найдена зависимость от rustyvxcan.service/start
19 сен 16:50:38 localhost systemd[1]: rustyvxcan.service: Не удалось разорвать цикл, начиная с rustyvxcan.service/start
19 сен 16:55:09 localhost systemd[1]: rustyvxcan.socket: Найден цикл порядка на rustyvxcan.service/start
19 сен 16:55:09 localhost systemd[1]: rustyvxcan.socket: Найдена зависимость от rustyvxcan.socket/start
19 сен 16:55:09 localhost systemd[1]: rustyvxcan.socket: Не удалось разорвать цикл, начиная с rustyvxcan.socket/start

Из доступных ресурсов я понимаю, что цикл происходит только с этими двумя файлами, и я могу устранить ошибку, убрав строку After из .socket файла, но тогда они запускаются одновременно, и исполняемый файл сервиса не работает. Я, вероятно, мог бы решить эту проблему, добавив задержку в сокет, однако я не вижу достаточно информации о зависимостях, чтобы вызвать цикл.

Я пытался очистить /{lib,etc}/systemd/system от файлов сервиса и сокета и переустановить те, что указаны здесь, просто чтобы убедиться, что более старые версии не работают, и, конечно, регулярно выполнял systemctl daemon-reload, модифицируя его. Но я не могу понять, почему указание after нарушает граф зависимости, даже после удаления зависимостей из файла сервиса.

Где происходит цикл, который я упускаю?

Обычно имеет смысл только включать/запускать сокет. Сокет сам позаботится о запуске сервиса только когда данные будут получены. Вы боретесь с After=, Requires=, Assert*= и ExecStartPre=mkdir..., что вызывает ваши проблемы. Я бы сделал так:

#rustyvxcan.service
[Unit]
Description=Сервис плагина Docker VXCAN
PartOf=rustyvxcan.socket
# Альтернатива PartOf=: Просто exit() на EOF (read() возвращает 0).

[Service]
Type=notify
ExecStart=/home/braedon/.cargo/bin/rustycan4docker
ExecReload=/bin/kill -HUP $MAINPID
#rustyvxcan.socket
[Unit]
Description=Сетевой плагин для vxcan
Before=docker.service

[Socket]
ListenStream=/run/docker/plugins/rustyvxcan.sock
RemoveOnStop=True

[Install]
WantedBy=sockets.target

Вместо ExecStartPre=mkdir я бы использовал конфигурацию tmpfiles.d, чтобы гарантировать, что systemd создаст этот путь.

# /etc/tmpfiles.d/rustyvxcan.conf
#Type Path                  Mode User Group Age         Argument
d     /run/docker/plugins   mode user group cleanup-age -

Код, который я пишу, вызывает ошибку о том, что адрес сокета уже используется, если сокет запускается первым.

Это звучит так, будто вы пытаетесь самостоятельно вызвать bind(). На самом деле сокет systemd сделает это за вас. Вот почему ваш сокет не может установить привязку, когда вы принуждаете сервис запуститься первым.

В случае ListenStream= (TCP-соединение) systemd вызовет socket(), bind() и listen() за вас.

Если у вас Accept=no (по умолчанию), когда данные будут получены на сокете, systemd запустит ваш процесс и передаст дескриптор сокета в переменной окружения $LISTEN_FDS (см. sd_listen_fds(3) для подробностей). Этот дескриптор — это дескриптор файла слушающего сокета. Вам нужно вызвать accept(), чтобы сгенерировать сокет соединения для каждого входящего соединения.

С Accept=yes ваш сервис должен быть экземпляром (например, foo.socket и [email protected]). В этом случае systemd вызывает accept() за вас и запускает ваш ExecStart= один раз для каждого соединения, передавая дескриптор сокета соединения. Вы можете read(), select(), poll() или write() в этот дескриптор сразу.

Я склонен использовать Accept=yes, StandardInput=socket и StandardOutput=socket. Тогда мне не нужно беспокоиться о $LISTEN_FDS, TCP и UDP. Я просто читаю из stdin и записываю в stdout. В этом случае все, что я хочу записать в журнал, записывается в stderr, который идет в журнал.


Если вы хотите сами вызывать socket(), bind(), listen() и accept(), то нет необходимости в юните systemd.socket.

Ответ или решение

Судя по предоставленной информации и коду юнит-файлов, вы сталкиваетесь с циклом зависимостей в системе systemd из-за конфигурации, связанной с вашим сервисом и сокетом. Давайте разберемся по порядку, чтобы выявить проблемы и улучшить способ, которым ваши юниты взаимодействуют друг с другом.

Проблема цикла зависимостей

Судя по ошибке:

rustyvxcan.service: Found ordering cycle on rustyvxcan.socket/start
rustyvxcan.service: Found dependency on rustyvxcan.service/start
rustyvxcan.service: Unable to break cycle starting with rustyvxcan.service/start

Цикл возникает из-за того, что у вас есть взаимозависимости между rustyvxcan.service и rustyvxcan.socket:

  • rustyvxcan.socket требует, чтобы rustyvxcan.service был активирован до его старта (через After=rustyvxcan.service).
  • При этом, rustyvxcan.service предполагает, что сокет уже запущен и доступен для использования (поскольку он привязывается к нему).

Это приводит к тому, что оба юнита ждут друг друга, создавая цикл, который systemd не может разрешить.

Решение

Чтобы избежать этой проблемы, я рекомендую следующие изменения в ваших юнит-файлах:

  1. Удалите директиву After=rustyvxcan.service из юнит-файла сокета.
  2. Поскольку сокет сам по себе будет управлять запуском сервиса, вам не нужно вручную управлять зависимостями между ними.
  3. Убедитесь, что исполняемый файл сервиса не пытается привязать сокет, так как это будет делаться systemd.

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

rustyvxcan.service:

[Unit]
Description=Docker VXCAN plugin Service
PartOf=rustyvxcan.socket

[Service]
Type=notify
ExecStart=/home/braedon/.cargo/bin/rustycan4docker
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

rustyvxcan.socket:

[Unit]
Description=A network plugin for vxcan
Before=docker.service

[Socket]
ListenStream=/run/docker/plugins/rustyvxcan.sock
RemoveOnStop=True

[Install]
WantedBy=sockets.target

Дополнительные рекомендации

  • Вместо использования ExecStartPre=mkdir, вы можете создать конфигурацию в /etc/tmpfiles.d/, чтобы systemd создавал нужный каталог автоматически:

    /etc/tmpfiles.d/rustyvxcan.conf:

    d /run/docker/plugins 0700 root root -
  • Убедитесь, что ваш сервис правильно обрабатывает принятые соединения. Если вы хотите, чтобы systemd сам запускал ваш сервис по событию (например, получению данных на сокете), используйте директиву Accept=yes в секции [Socket] и настройте соответствующим образом StandardInput и StandardOutput.

Заключение

С реализацией вышеизложенных изменений ваша система должна корректно запускать сервис и сокет без зависимостей и циклов. Убедитесь, что ваша служба не пытается привязать сокет вручную, так как это будет делать systemd. В будущем, если возникнут еще сложности, проверьте журнал systemd для диагностики: journalctl -xe.

Оцените материал
Добавить комментарий

Капча загружается...