Вопрос или проблема
Где происходит цикл упорядочивания 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 не может разрешить.
Решение
Чтобы избежать этой проблемы, я рекомендую следующие изменения в ваших юнит-файлах:
- Удалите директиву
After=rustyvxcan.service
из юнит-файла сокета. - Поскольку сокет сам по себе будет управлять запуском сервиса, вам не нужно вручную управлять зависимостями между ними.
- Убедитесь, что исполняемый файл сервиса не пытается привязать сокет, так как это будет делаться 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
.