Перейти к содержанию

Каталог .d рядом с юнитом: как systemd склеивает конфигурацию сервиса и где ломается ExecStart

Опубликовано
  • Админы
Drop-in systemd
Каталог .d рядом с юнитом — единственная правка, которую не сотрёт пакетный менеджер

Знакомая ситуация: в юнит сервиса аккуратно вписана нужная строка, всё работает, а после очередного апдейта сервис ведёт себя так, будто правки не было. Так и есть — файл переписан установщиком. У Xray это /etc/systemd/system/xray.service, и разработчики честно оставили предупреждение внутри своего же вспомогательного файла: «Or all changes you made will be lost!». С юнитом Docker, приезжающим из пакета, произойдёт ровно то же самое.

Копировать чужой юнит к себе и жить с копией — плохая стратегия: апстрим будет чинить свой файл, а у вас останется снимок двухлетней давности, о расхождении вы узнаете случайно. В systemd для этого предусмотрен отдельный механизм — drop-in, крошечный .conf, который меняет только интересующие вас строки.

Из чего складывается конфигурация сервиса

Разобрав основной файл foo.service, менеджер идёт в одноимённый каталог foo.service.d/ и дочитывает оттуда всё, что оканчивается на .conf. Таких каталогов система знает три, и вес у них разный — от пакетного к пользовательскому: /usr/lib/systemd/system/, затем /run/systemd/system/, затем /etc/systemd/system/. Формулировка из systemd.unit(5): «Drop-in files in /etc/ take precedence over those in /run/ which in turn take precedence over those in /usr/lib/».

А вот дальше начинается то, что регулярно упускают. Каталог не решает, кто применится позже: сортировка идёт по имени файла — «Multiple drop-in files with different names are applied in lexicographic order, regardless of which of the directories they reside in». Отсюда практическое следствие: файл override.conf из-под systemctl edit окажется поверх установочного 10-donot_touch_single_conf.conf, потому что в ASCII цифра меньше буквы.

Порядок поиска каталогов и слияния drop-in
Схема по документации systemd.unit(5)

ℹ️ Справка

Именами дело не ограничивается. Есть верхнеуровневый service.d/, который цепляется ко всем сервисам разом. Инстанс шаблона читает сначала foo@bar.service.d/, потом foo@.service.d/. А если в имени юнита есть дефисы, к списку добавляются укороченные каталоги — например foo-.service.d/.

Случай первый: прокси для докерового демона

Файл ~/.docker/config.json демону не указ — он смотрит на переменные окружения собственного процесса. Поэтому в документации Docker описан именно drop-in:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1,.corp"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Убедиться в результате помогает systemctl show --property=Environment docker — ожидаемый ответ содержит все три пары в одной строке. Ничего не вывелось? Либо промахнулись каталогом, либо забыли про daemon-reload. Rootless-инсталляции работают по тому же принципу, но каталог лежит в домашней директории — ~/.config/systemd/user/docker.service.d/, а sudo меняется на флаг --user.

Случай второй: другая команда запуска и пустая строка перед ней

На этом месте спотыкаются чаще всего. Директива ExecStart= относится к накапливающимся: ваша строка не вытеснит исходную, а встанет рядом с ней. При этом в systemd.service(5) записано жёсткое условие — «Unless Type= is oneshot, exactly one command must be given». Для обычного сервиса второй команды достаточно, чтобы юнит отказался загружаться.

Лечится это обнулением списка перед новым значением, что документация подтверждает прямо: «If the empty string is assigned to this option, the list of commands to start is reset». Выглядит так:

[Service]
ExecStart=
ExecStart=/usr/local/bin/xray run -confdir /usr/local/etc/xray/

Ровно этот приём использует и сам установщик Xray в своём drop-in — сперва пустое присваивание, следом рабочая команда. Правило универсально: ExecStartPre=, Environment=, After= ведут себя точно так же.

⛔ Так делать нельзя

Каталог /usr/lib/systemd/system/ отдан пакетному менеджеру — редактировать лежащие там юниты бессмысленно. Второй антипаттерн мягче, но вреднее вдолгую: тянуть весь юнит целиком через systemctl edit --full ради одной директивы. Полная копия перестаёт получать апстримные исправления, и обнаружится это на первом же релизе, который что-то поменял внутри.

Случай третий: лимиты и рестарт — обнуление не требуется

Такие директивы одиночные, побеждает последнее присвоенное значение.

sudo systemctl edit --drop-in=90-limits.conf xray.service
[Service]
LimitNOFILE=1048576
Restart=always
RestartSec=5

Отдельная команда перезагрузки после systemctl edit не нужна: выход из редактора уже делает действие, «equivalent to systemctl daemon-reload». Смотреть при этом стоит не в файл, а на то, что получил менеджер: systemctl show -p LimitNOFILE -p Restart xray.service. Кстати, применительно к Xray дескрипторы трогать смысла мало — установщик и так задаёт LimitNOFILE=1000000 вместе с LimitNPROC=10000; куда чаще правят Restart=on-failure, переводя его в always.

✅ Как правильно

После каждой правки — три команды:

  • systemctl cat xray.service — основной файл плюс все drop-in с путями, в порядке применения;
  • systemd-analyze verify xray.service — синтаксис до того, как сервис не поднимется;
  • systemd-delta — общесистемная картина: что чем перекрыто.

⚠️ Осторожно

Команда systemctl revert xray.service возвращает вендорское состояние, но вместе с этим уносит весь каталог xray.service.d/ — включая файлы, которые положили не вы. Применительно к Xray это потеря 10-donot_touch_*.conf с путём к конфигу. Чтобы убрать только собственную правку, достаточно удалить свой файл и выполнить daemon-reload.

Есть ещё режим, о котором вспоминают редко, — флаг --runtime. Изменение записывается в /run/systemd/system/ и живёт до перезагрузки. Пригодится, когда сервис нужно на полчаса перевести в Restart=no и при этом ничего после себя не оставить.


Источники

💬 Вопрос к сообществу

Как у вас организованы правки юнитов — отдельные drop-in или всё же копия файла целиком? И случалось ли, что обновление пакета молча вернуло вендорский .service, а вы узнали об этом по упавшему сервису?

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.