Зачем не править unit напрямую
Допустим nginx из пакета пришёл с /usr/lib/systemd/system/nginx.service,
и тебе нужно увеличить LimitNOFILE. Вариантов было исторически три:
- Править файл прямо в
/usr/lib/systemd/system/- плохо, обновление пакета его перезапишет. - Скопировать целиком в
/etc/systemd/system/nginx.serviceи править -/etc/имеет приоритет, но: а) ты тащишь весь файл, б) при обновлении пакета новые директивы из upstream'а не появятся у тебя. - Drop-in - единственный правильный вариант: маленький файл с дельтой, базовый unit остаётся апдейтабельным.
Как это работает
Для unit'а nginx.service systemd при загрузке подмешивает все .conf
файлы из этих каталогов:
/usr/lib/systemd/system/nginx.service.d/*.conf ← от пакетов (vendor)
/run/systemd/system/nginx.service.d/*.conf ← runtime
/etc/systemd/system/nginx.service.d/*.conf ← твои локальные (highest)
Файлы внутри одного каталога мерджатся в алфавитном порядке имени.
Конвенция: 10-foo.conf, 20-bar.conf - числовой префикс задаёт порядок.
Плюс есть «общие» drop-in каталоги по типу юнитов:
service.d/- для всех.servicetimer.d/- для всех.timer
Поэтому в systemctl status ты часто видишь:
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf, 50-keep-warm.conf
Это общесистемные drop-in'ы которые дистро применяет ко всем сервисам.
systemctl edit - самый правильный способ создать
Не создавай drop-in руками - есть команда:
sudo systemctl edit nginx.service
Что произойдёт:
- Откроется
$EDITORс пустым буфером с инструкцией. - После сохранения systemd создаст файл
/etc/systemd/system/nginx.service.d/override.conf. - Автоматически выполнит
daemon-reload.
ВАЖНО: писать там нужно только то что меняешь, с обязательным
[Section]-заголовком:
[Service]
LimitNOFILE=65536
Environment="DEBUG=1"
Не писать заново Description=, ExecStart= и прочее - оно подтянется
из оригинала.
Аддитивные vs override-able директивы
Директивы systemd делятся на два класса:
List-style (аддитивные). Wants=, Requires=, After=, ExecStartPre=,
Environment=, EnvironmentFile=. По умолчанию drop-in добавляет к
оригинальному списку, не заменяет.
Single-value (override). Type=, User=, Restart=, LimitNOFILE=.
Drop-in просто перезаписывает.
Чтобы очистить и переопределить список - нужна пустая строка-сброс ПЕРЕД новым значением:
[Service]
ExecStart= # очистить весь оригинальный ExecStart
ExecStart=/usr/local/bin/nginx-wrapper # задать новый
Без первой пустой строки nginx запустится дважды - оригинальный ExecStart + новый. Это самая частая ошибка.
То же для Environment=, Wants= и прочих list-style:
[Unit]
After= # очистить
After=postgres.service redis.service
Проверить что в итоге получилось
systemctl cat nginx.service
Это покажет итоговый unit с применёнными drop-in'ами в правильном
порядке. Все строки помечены источником через # /path/to/file поверх каждой
секции:
# /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network-online.target
[Service]
Type=forking
ExecStart=/usr/sbin/nginx
...
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536
Дальше - systemd-analyze verify для проверки синтаксиса:
systemd-analyze verify nginx.service
systemd-delta - что у меня переопределено
Команда показывает все юниты в системе у которых есть drop-in'ы или полные override'ы:
systemd-delta --type=extended # с drop-in'ами
systemd-delta --type=overridden # юниты с полным override
systemd-delta --type=masked # masked-юниты (→ /dev/null)
Полезно при наследовании сервера от кого-то - увидеть что админ-предшественник накрутил.
systemctl revert - откатить
Снести все drop-in'ы и /etc/-override'ы для юнита, оставить чистый vendor:
sudo systemctl revert nginx.service
Удалит /etc/systemd/system/nginx.service.d/ и сам /etc/systemd/system/nginx.service
если он там был как полный override.
Типичные кейсы
Увеличить лимиты:
[Service]
LimitNOFILE=1048576
TasksMax=infinity
Поменять User:
[Service]
User=
User=newuser
(Single-value - пустая строка не нужна, но лучше писать для читаемости.)
Добавить переменную окружения:
[Service]
Environment="GOMAXPROCS=4"
Environment="OTEL_ENDPOINT=http://collector:4318"
Перенести логи в отдельный файл вместо journald:
[Service]
StandardOutput=append:/var/log/myapp.log
StandardError=inherit
Не забывать daemon-reload если правил не через systemctl edit:
sudo systemctl daemon-reload
sudo systemctl restart nginx