Юниты ищутся в (по возрастанию приоритета):
/lib/systemd/system/ — пакетные (не редактировать);
/etc/systemd/system/ — локальные переопределения (редактировать здесь).
Структура .service:
[Unit]
Description=My App
After=network-online.target
[Service]
ExecStart=/usr/bin/myapp
Restart=on-failure
User=myapp
[Install]
WantedBy=multi-user.target
Drop-in: дополнение юнита
Юнит из пакета правят через drop-in: обновление пакета
перезапишет сам файл юнита, а drop-in останется. Drop-in — файл .conf в
каталоге /etc/systemd/system/<юнит>.d/. systemd читает юнит, затем
drop-in'ы по алфавиту, и их строки дополняют или заменяют строки юнита.
sudo systemctl edit nginx
sudo mkdir -p /etc/systemd/system/demo.service.d
printf '[Service]\nMemoryMax=256M\n' \
| sudo tee /etc/systemd/system/demo.service.d/memory.conf
sudo systemctl daemon-reload
systemctl cat demo
systemctl show demo -p DropInPaths
systemctl edit сам делает daemon-reload. После ручной правки без
daemon-reload systemd работает со старой версией юнита. Ключ вроде
ExecStart= в drop-in'е добавляет вторую команду; чтобы заменить первую,
перед новой строкой ставят пустую ExecStart=.
Ключ в чужой секции systemd пропускает: LimitNOFILE= под [Unit] не
действует, служба получает лимит по умолчанию. Предупреждение видно в
журнале после daemon-reload и в systemd-analyze verify — код у него
всё равно 0:
systemd-analyze verify /etc/systemd/system/demo.service
/etc/systemd/system/demo.service:3: Unknown key name 'LimitNOFILE' in
section 'Unit', ignoring.
Активация сокетом
Юнит .socket держит порт вместо службы: systemd слушает сам и запускает
одноимённую службу при первом соединении. Учебная пара —
demo-sock.socket и demo-sock.service:
[Socket]
ListenStream=8085
[Install]
WantedBy=sockets.target
[Unit]
Requires=demo-sock.socket
After=demo-sock.socket
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/demo-sock.py
ListenStream=8085 — только порт, без адреса: сокет слушает все адреса,
IPv4 и IPv6. До первого запроса порт держит процесс systemd, служба
inactive:
student@ubuntu:~$ sudo ss -tlnp | grep 8085
LISTEN 0 4096 *:8085 *:* users:(("systemd",pid=1,fd=71))
student@ubuntu:~$ systemctl list-sockets | grep -E 'LISTEN|demo'
LISTEN UNIT ACTIVATES
[::]:8085 demo-sock.socket demo-sock.service
student@ubuntu:~$ curl -s http://127.0.0.1:8085/
hello
student@ubuntu:~$ sudo ss -tlnp | grep 8085
LISTEN 0 4096 *:8085 *:*
users:(("python3",pid=2769,fd=3),("systemd",pid=1,fd=71))
Адрес меняют drop-in'ом. ListenStream= — список: пустая строка
сбрасывает адреса из юнита, без неё новый адрес добавится к старому.
sudo mkdir -p /etc/systemd/system/demo-sock.socket.d
printf '[Socket]\nListenStream=\nListenStream=127.0.0.1:8085\n' \
| sudo tee /etc/systemd/system/demo-sock.socket.d/local.conf
sudo systemctl daemon-reload
sudo systemctl restart demo-sock.socket
sudo ss -tlnp | grep 8085
restart сокета перезапускает и службу с Requires= на него:
MainPID службы сменился (2769 → 2838). Остановить одну службу мало —
следующее соединение запустит её снова, а systemctl stop предупредит:
Stopping 'demo-sock.service', but its triggering units are still active:
demo-sock.socket
Закрыть порт совсем — sudo systemctl disable --now demo-sock.socket.