Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Systemd — это уже не просто init-система. Это целая экосистема для управления сервисами, логами, сетью, таймерами, точками монтирования и многим другим. Большинство администраторов использует 20% его возможностей, не подозревая об остальных 80%. Эта статья закроет пробелы.


Архитектура systemd: что за чем стоит

systemd
├── systemd-journald   — централизованное логирование
├── systemd-networkd   — управление сетью
├── systemd-resolved   — DNS resolver
├── systemd-timesyncd  — синхронизация времени
├── systemd-logind     — управление сессиями
├── systemd-udevd      — управление устройствами
└── systemd-tmpfilesd  — управление временными файлами

Всё взаимодействует через D-Bus и сокеты. Это важно понимать для отладки.


Unit-файлы: анатомия сервиса

Базовая структура

[Unit]
Description=MyApp Web Service
Documentation=https://myapp.example.com/docs
After=network.target mysql.service redis.service
Requires=mysql.service
Wants=redis.service

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/php /var/www/myapp/server.php
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Секция [Unit]: зависимости и порядок

Разница между After/Before и Requires/Wants критически важна:

# ПОРЯДОК ЗАПУСКА (не зависимость!)
After=network.target    # запускаемся после network.target
Before=nginx.service    # запускаемся до nginx

# ЗАВИСИМОСТИ (и порядок)
Requires=mysql.service  # жёсткая: если mysql упал — мы тоже падаем
Wants=redis.service     # мягкая: пробуем запустить redis, но не критично
BindsTo=device.mount    # как Requires, но реагирует на остановку устройства
PartOf=app.service      # stop/restart parent = stop/restart нас

# Конфликты
Conflicts=apache2.service  # не запускаться вместе с apache2

Типы сервисов — Type=

Type=simple     # процесс запущен = сервис готов (дефолт)
Type=exec       # как simple, но ждёт exec() (systemd 240+)
Type=forking    # главный процесс форкает дочерний и завершается (nginx, apache)
Type=oneshot    # выполняется и завершается (для скриптов)
Type=notify     # процесс сигнализирует systemd о готовности через sd_notify()
Type=dbus       # готовность через D-Bus
Type=idle       # запускается после других, когда нет других задач

Для PHP-FPM используем Type=notify — он поддерживает sd_notify:

[Service]
Type=notify
ExecStart=/usr/sbin/php-fpm8.2 --nodaemonize

Продвинутые настройки сервиса

Переменные окружения и секреты

[Service]
# Прямо в unit-файле (не для секретов!)
Environment=APP_ENV=production
Environment=LOG_LEVEL=info

# Из файла (безопаснее)
EnvironmentFile=/etc/myapp/environment
EnvironmentFile=-/etc/myapp/local.env  # дефис = не ошибка если нет файла

# Systemd credentials (systemd 250+ — самый безопасный способ)
LoadCredential=db-password:/etc/credstore/db-password.cred
# Доступно в: /run/credentials/myapp.service/db-password

Файл /etc/myapp/environment:

DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp
DB_USER=myapp
DB_PASSWORD=supersecret
REDIS_HOST=127.0.0.1
chmod 600 /etc/myapp/environment
chown root:www-data /etc/myapp/environment

Изоляция и безопасность сервиса

[Service]
# Запуск от непривилегированного пользователя
User=www-data
Group=www-data

# Динамический пользователь (создаётся на время жизни сервиса)
DynamicUser=yes

# Файловая система
PrivateTmp=yes              # отдельный /tmp
PrivateDevices=yes          # нет доступа к /dev (кроме базовых)
ProtectHome=yes             # нет доступа к /home /root /run/user
ProtectSystem=strict        # / только для чтения
ReadWritePaths=/var/www/myapp/storage /var/log/myapp

# Сеть
PrivateNetwork=no           # нужна для веб-сервиса
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Процессы
NoNewPrivileges=yes         # нет setuid/setgid после старта
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

# Системные вызовы (белый список)
SystemCallFilter=@system-service  # разрешённые для сервисов
SystemCallFilter=~@privileged     # запрещаем привилегированные
SystemCallErrorNumber=EPERM

# Capabilities
CapabilityBoundingSet=CAP_NET_BIND_SERVICE  # только для порта <1024
AmbientCapabilities=CAP_NET_BIND_SERVICE

# Ограничение ресурсов
LimitNOFILE=65535       # открытые файловые дескрипторы
LimitNPROC=512          # процессы
MemoryLimit=2G          # максимум памяти
CPUQuota=50%            # не более 50% CPU

Политики перезапуска

[Service]
Restart=on-failure      # только при ненулевом коде возврата
Restart=always          # всегда (кроме systemctl stop)
Restart=unless-stopped  # всегда, даже при ненулевом коде

RestartSec=5s           # пауза перед перезапуском

# Ограничение перезапусков (не более 3 за 30 секунд)
StartLimitIntervalSec=30s
StartLimitBurst=3
StartLimitAction=none   # что делать при достижении лимита: none|reboot|poweroff

# Watchdog — если нет heartbeat за N секунд — перезапуск
WatchdogSec=30s

systemctl: продвинутое использование

# Основные команды
systemctl start myapp
systemctl stop myapp
systemctl restart myapp
systemctl reload myapp       # отправить SIGHUP (graceful reload)
systemctl status myapp

# Включить/выключить автозапуск
systemctl enable myapp
systemctl disable myapp
systemctl enable --now myapp  # включить и сразу запустить

# Маскировка (нельзя запустить даже вручную)
systemctl mask cups
systemctl unmask cups

# Информация о юните
systemctl cat myapp           # показать содержимое unit-файла
systemctl show myapp          # все свойства (машиночитаемо)
systemctl show myapp -p Restart,RestartSec  # конкретные свойства

# Зависимости
systemctl list-dependencies myapp
systemctl list-dependencies --reverse myapp  # кто зависит от нас

# Все юниты
systemctl list-units --type=service
systemctl list-units --state=failed
systemctl list-unit-files

# Применение изменений без потери сервиса (systemd 230+)
systemctl daemon-reload
systemctl try-reload-or-restart myapp

systemd Timers: замена cron

Таймеры systemd мощнее cron: есть логирование, управление зависимостями, можно запустить вручную.

Создаём пару service + timer

/etc/systemd/system/backup.service:

[Unit]
Description=Daily Database Backup
After=network.target mysql.service

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-database.sh
StandardOutput=journal
StandardError=journal

/etc/systemd/system/backup.timer:

[Unit]
Description=Daily Database Backup Timer
Requires=backup.service

[Timer]
# Каждый день в 2:30
OnCalendar=*-*-* 02:30:00

# После загрузки системы (если пропустили)
Persistent=true

# Случайная задержка до 5 минут (чтобы не все запускались одновременно)
RandomizedDelaySec=5min

[Install]
WantedBy=timers.target
systemctl enable --now backup.timer
systemctl list-timers  # все таймеры и когда запустятся
systemctl start backup.service  # запустить вручную без ожидания таймера

Синтаксис OnCalendar

OnCalendar=Mon-Fri *-*-* 09:00:00   # каждый будний день в 9:00
OnCalendar=weekly                    # каждую неделю (понедельник 0:00)
OnCalendar=monthly                   # первый день месяца
OnCalendar=*-*-* *:00/15:00          # каждые 15 минут
OnCalendar=2024-03-*                 # каждый день в марте 2024
OnCalendar=Sat,Sun 12:00:00          # субботу и воскресенье в полдень

Проверка синтаксиса:

systemd-analyze calendar "Mon-Fri *-*-* 09:00:00"

journald: работа с логами

Базовые команды

# Логи конкретного сервиса
journalctl -u nginx
journalctl -u nginx -n 100  # последние 100 строк
journalctl -u nginx -f      # следим в реальном времени (как tail -f)

# По времени
journalctl --since "2024-01-15 10:00" --until "2024-01-15 11:00"
journalctl --since "1 hour ago"
journalctl --since yesterday

# По приоритету
journalctl -p err          # только ошибки
journalctl -p warning..err # от warning до err
# 0=emerg 1=alert 2=crit 3=err 4=warning 5=notice 6=info 7=debug

# Форматы вывода
journalctl -u myapp -o json     # JSON
journalctl -u myapp -o json-pretty  # читаемый JSON
journalctl -u myapp -o short-precise  # с микросекундами

# Загрузки системы
journalctl --list-boots
journalctl -b 0    # текущая загрузка
journalctl -b -1   # предыдущая загрузка
journalctl -b -1 -p err  # ошибки при предыдущей загрузке

Настройка journald

/etc/systemd/journald.conf:

[Journal]
# Максимальный размер хранилища
SystemMaxUse=2G
SystemKeepFree=500M

# Максимальный размер одного файла журнала
SystemMaxFileSize=200M

# Хранить журналы N дней
MaxRetentionSec=1month

# Сжатие
Compress=yes

# Пересылка в syslog (если нужен rsyslog)
ForwardToSyslog=no
ForwardToWall=no

# Максимальный размер сообщения
LineMax=48K

Структурированное логирование из приложения

# Systemd-cat
echo "Application started" | systemd-cat -t myapp -p info

# С полями для фильтрации
systemd-cat -t myapp <<< "MESSAGE=User login SUCCESSFUL
USER_ID=42
REMOTE_IP=192.168.1.100"

# Фильтрация по custom полям
journalctl SYSLOG_IDENTIFIER=myapp
journalctl _SYSTEMD_UNIT=myapp.service USER_ID=42

Drop-in конфиги: расширяем без изменения оригинала

Никогда не редактируйте файлы в /lib/systemd/system/ — они перезапишутся при обновлении. Используйте drop-in:

# Создаём директорию
mkdir -p /etc/systemd/system/nginx.service.d/

# Создаём override
cat > /etc/systemd/system/nginx.service.d/override.conf <<EOF
[Service]
# Добавляем переменные окружения к nginx
Environment=APP_ENV=production

# Увеличиваем лимит файловых дескрипторов
LimitNOFILE=65535

# Рестарт при падении (дефолт nginx — no)
Restart=on-failure
RestartSec=5s
EOF

# Или через systemctl edit (открывает редактор автоматически)
systemctl edit nginx

systemctl daemon-reload

cgroups v2 через systemd

Современные версии systemd используют cgroups v2 для изоляции ресурсов:

# Статистика ресурсов для сервиса
systemctl status myapp
# или детальнее
systemd-cgtop

# Запустить с ограничениями на лету (transient unit)
systemd-run --unit=my-temp-task --slice=user.slice \
    -p MemoryMax=512M -p CPUQuota=25% \
    /usr/bin/python3 heavy_script.py

# Ограничить группу пользователя
systemctl set-property user-1000.slice MemoryMax=4G

Анализ времени загрузки

# Общее время загрузки
systemd-analyze

# Детально по сервисам
systemd-analyze blame

# Визуализация в SVG
systemd-analyze plot > boot.svg

# Критический путь загрузки
systemd-analyze critical-chain

# Проверка unit-файла на ошибки
systemd-analyze verify /etc/systemd/system/myapp.service

Полезные паттерны для PHP-приложений

Сервис PHP-FPM с worker isolation

[Service]
# Мягкий рестарт (дождаться завершения текущих запросов)
ExecReload=/bin/kill -USR2 $MAINPID

# Watchdog через sd_notify
WatchdogSec=60s
NotifyAccess=main

# Логи в journald напрямую
StandardOutput=journal
StandardError=journal
SyslogIdentifier=php-fpm

# Автоматический сбор core dump
LimitCORE=infinity
Slice=web.slice  # группировка в slice для общих лимитов

Slice для группировки сервисов

# /etc/systemd/system/web.slice
[Unit]
Description=Web Services Slice
Before=slices.target

[Slice]
MemoryMax=8G
CPUQuota=200%

Все сервисы в web.slice суммарно не превысят 8GB RAM и 200% CPU (2 ядра).


Отладка: когда что-то пошло не так

# Быстрая диагностика
systemctl --failed              # упавшие сервисы
journalctl -p err -b            # ошибки с последней загрузки
journalctl -xe                  # последние записи с расшифровкой

# Подробный запуск в debug-режиме
SYSTEMD_LOG_LEVEL=debug systemd-analyze verify myapp.service

# Проверка прав и доступов
systemd-run --unit=debug-shell -p PrivateTmp=yes bash
# Запускает оболочку с теми же ограничениями, что и сервис

# Strace сервиса через systemd
systemctl set-environment STRACE_OPTS="-f -e trace=network"
systemctl restart myapp

Systemd — мощный инструмент, который при правильном использовании превращает управление сервисами из искусства в инженерию. Используйте drop-in конфиги, настраивайте изоляцию, пишите таймеры вместо cron — и ваша система станет предсказуемой и управляемой.

User Feedback

Create an account or sign in to leave a review

There are no reviews to display.

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.