Выберите продукт

Debian/Ubuntu: как исправить left-over process in control group у systemd

Предупреждение systemd о left-over process in control group означает, что после остановки сервиса в его cgroup остались процессы. Разберём, как быстро найти причину, проверить unit-файл и настроить корректное завершение службы.
Debian/Ubuntu: как исправить left-over process in control group у systemd

Сообщение вида Found left-over process ... in control group while starting unit в Debian или Ubuntu — это не безобидный warning. Обычно оно означает, что после остановки сервиса в его cgroup остались процессы, которые systemd всё ещё считает частью unit.

На практике это приводит к неприятным эффектам: сервис формально остановлен, но его воркеры продолжают жить, повторный запуск проходит нестабильно, а во время деплоя появляются труднообъяснимые сбои. Особенно часто проблема всплывает у приложений со своими воркерами, shell-обёртками и старой daemon-моделью с fork в фон.

Ниже разберём, что именно означает это состояние, как быстро найти виновника и какие параметры unit-файла реально влияют на остановку: Type, KillMode, TimeoutStopSec, RemainAfterExit и PIDFile.

Что означает left-over process in control group

Для systemd сервис — это не только один главный PID, а вся группа процессов внутри соответствующей cgroup. Когда вы выполняете systemctl stop, менеджер ожидает, что unit завершится полностью, а не только его родительский процесс.

Если после остановки внутри cgroup остаются живые процессы, при следующем старте systemd может сообщить о left-over process. Проще говоря, сервис как будто закрыли, но часть его потомков продолжила работу.

Чаще всего это не баг systemd, а расхождение между реальной моделью процессов приложения и тем, как сервис описан в unit-файле.

Типовые причины обычно такие:

  • daemon форкается, но в unit указан неподходящий Type;
  • в ExecStart используется shell-обёртка с запуском в фоне;
  • приложение завершает только master-процесс, а воркеры остаются жить;
  • включён неподходящий KillMode;
  • ExecStop формально отрабатывает, но не завершает реальный сервис;
  • используется RemainAfterExit=yes там, где нужен обычный долгоживущий процесс;
  • приложение долго останавливается и не укладывается в TimeoutStopSec.

Как быстро подтвердить проблему

Сначала стоит посмотреть, что сам systemd знает о unit и какие процессы к нему привязаны:

systemctl status your-service.service

Дальше полезно сразу вывести ключевые свойства сервиса:

systemctl show your-service.service -p Type -p MainPID -p ControlPID -p KillMode -p TimeoutStopUSec -p RemainAfterExit -p ExecStart -p ExecStop -p PIDFile

После этого проверьте журнал unit. Обычно именно там видно, как шла остановка, был ли таймаут и переходил ли systemd к принудительному завершению:

journalctl -u your-service.service -b --no-pager
journalctl -u your-service.service -n 100 --no-pager

Если хотите поймать момент остановки вживую, в одном окне откройте журнал, а в другом выполните остановку:

journalctl -fu your-service.service
systemctl stop your-service.service

В логах ищите строки про отправку SIGTERM, истечение TimeoutStopSec, принудительный SIGKILL и, собственно, предупреждение про left-over process.

Просмотр логов systemd и диагностика остановки сервиса через journalctl

Если вы запускаете приложения с несколькими воркерами или фоновыми задачами на выделенном сервере, удобнее сразу проверять такие кейсы на VDS: там проще воспроизводить stop/start, смотреть cgroup и править unit-файлы без ограничений типичного shared-окружения.

Как увидеть зависшие процессы внутри cgroup

Следующий шаг — понять, кто именно остаётся жить после остановки. Обычного ps часто недостаточно, потому что для systemd важна не только иерархия PID, но и принадлежность к cgroup.

Для начала можно посмотреть дерево процессов unit:

systemd-cgls /system.slice/your-service.service

Либо узнать путь к control group у самого сервиса:

systemctl show your-service.service -p ControlGroup

После этого удобно сравнить вывод с обычными инструментами:

ps -eo pid,ppid,pgid,sid,stat,cmd --forest
pgrep -a -f 'your-service|worker|gunicorn|java|node'

На этом этапе нужно ответить на один вопрос: кто именно остаётся после systemctl stop — основной демон, shell-посредник, worker, процесс двойного fork или что-то, стартующее в фоне через &.

Если тема cgroup у вас встречается часто, полезно отдельно разобраться, как systemd группирует процессы и почему это важно для сервисов с воркерами. По теме пригодится материал про cgroup и slices в systemd.

Типовые причины проблемы

Неверный Type у сервиса

Если приложение работает на переднем плане, обычно подходит Type=simple или Type=exec. Если демон сам делает fork и родитель завершается, чаще нужен Type=forking и корректный PIDFile.

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

Shell-обёртка и запуск в фоне

Одна из самых частых причин — конструкция вроде /bin/sh -c внутри ExecStart, особенно если внутри ещё есть фоновый запуск через &. В таком варианте systemd управляет уже не приложением напрямую, а дополнительным промежуточным процессом.

Лучший сценарий для systemd — когда ExecStart указывает прямо на основной бинарник, без shell и без ручного ухода в background.

Неподходящий KillMode

Параметр KillMode определяет, кого systemd будет завершать при остановке сервиса. Именно из-за него сервис может либо останавливаться полностью, либо оставлять после себя живые процессы.

  • control-group — завершать все процессы unit в cgroup;
  • mixed — сначала основной процесс, затем остальные;
  • process — только главный процесс;
  • none — systemd никого не завершает сам.

Если у сервиса есть дочерние процессы, режимы process и особенно none часто становятся прямой причиной lingering processes.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Слишком маленький TimeoutStopSec

Некоторые сервисы не могут завершиться мгновенно: им нужно дождаться окончания активных запросов, закрыть соединения, остановить worker-пулы, записать буферы и закрыть файловые дескрипторы.

Если TimeoutStopSec слишком мал, systemd сначала отправит мягкий сигнал, а потом перейдёт к жёсткому завершению. В логах это хорошо видно по переходу от normal stop к timeout и дальнейшему kill.

Некорректный ExecStop

Иногда в unit добавляют ExecStop, который выглядит красиво, но по факту ничего не останавливает. Например, команда лишь отправляет запрос в локальный сокет, пишет сообщение в лог и завершается успешно. Для systemd шаг остановки уже выполнен, но процессы при этом продолжают жить.

Если приложение штатно обрабатывает SIGTERM, лишний ExecStop чаще мешает, чем помогает.

Неудачный RemainAfterExit

RemainAfterExit=yes полезен для Type=oneshot, но для обычных демонов этот параметр часто скрывает проблему с моделью запуска. Unit считается активным, хотя реальный процесс давно ушёл в фон и больше нормально не контролируется.

Пошаговая схема диагностики

Если нужен короткий и рабочий алгоритм, действуйте так:

  1. Посмотрите systemctl status и убедитесь, какие PID видит unit.
  2. Откройте журнал journalctl -u и найдите момент остановки.
  3. Проверьте Type, KillMode, TimeoutStopSec, RemainAfterExit и PIDFile.
  4. Посмотрите дерево процессов через systemd-cgls.
  5. Уберите shell-обёртки и фоновые запуски из ExecStart.
  6. После правок перезагрузите конфигурацию и повторно выполните stop/start.

Минимальный набор команд для такой проверки:

systemctl cat your-service.service
systemctl show your-service.service -p Type -p KillMode -p TimeoutStopUSec -p RemainAfterExit -p GuessMainPID -p PIDFile
journalctl -u your-service.service -b --no-pager
systemd-cgls /system.slice/your-service.service
systemctl stop your-service.service
systemctl start your-service.service

Если сервис связан с фоновой обработкой задач, очередями или несколькими worker-процессами, дополнительно полезно свериться с практикой управления такими процессами через systemd и supervisor для воркеров.

Какие настройки чаще всего помогают

Уберите shell и запуск в фоне

Плохой пример:

ExecStart=/bin/sh -c 'myapp --config /etc/myapp.conf &'

Нормальный вариант:

[Service]
Type=simple
ExecStart=/usr/local/bin/myapp --config /etc/myapp.conf

Если приложение умеет работать на переднем плане, это почти всегда лучший путь.

Исправьте KillMode

Если после остановки остаются worker-процессы, часто помогает явное завершение всей cgroup:

[Service]
KillMode=control-group

Если приложению важно сначала корректно завершить master, а уже потом дочерние процессы, попробуйте:

[Service]
KillMode=mixed

Дайте сервису время на остановку

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

[Service]
TimeoutStopSec=45s

Подбирайте значение по логам и по реальному поведению приложения, а не наугад.

Редактирование override-файла systemd для корректной остановки сервиса

Уберите лишний RemainAfterExit

Если это не oneshot-unit, чаще всего параметр должен быть выключен:

[Service]
RemainAfterExit=no

Для forking-сервисов укажите PIDFile

Если сервис работает по старой daemon-модели, systemd должен знать PID главного процесса:

[Service]
Type=forking
PIDFile=/run/myapp/myapp.pid

Без корректного PIDFile менеджер может неверно определить MainPID, а дальше ошибиться и при остановке.

Пример override для проблемного сервиса

Если нужно быстро проверить гипотезу без редактирования пакетного unit-файла, удобнее использовать override:

systemctl edit your-service.service

Пример минимального override:

[Service]
KillMode=control-group
TimeoutStopSec=60s
Restart=no

Параметр Restart=no полезен именно на этапе диагностики. Иначе автоперезапуск может скрыть реальную картину остановки.

После внесения правок примените изменения и заново воспроизведите проблему:

systemctl daemon-reload
systemctl restart your-service.service
systemctl stop your-service.service
journalctl -u your-service.service -n 50 --no-pager

Если вы регулярно разворачиваете свои приложения и сервисы вручную, такие проверки особенно удобно держать под рукой на виртуальном хостинге для простых сайтов и на VDS для сервисов, где нужен полный контроль над systemd и процессами.

Чего делать не стоит

  • Не ставьте KillMode=none, если у вас нет очень ясной причины и полного контроля над завершением всех процессов.
  • Не маскируйте проблему бесконечным увеличением TimeoutStopSec, если сервис вообще не реагирует на сигналы.
  • Не запускайте долгоживущие процессы через shell с фоновым &, если можно указать бинарник напрямую.
  • Не редактируйте unit из пакета напрямую, если можно использовать override.
  • Не делайте выводы только по ps, игнорируя cgroup-представление.
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Короткий чек-лист

  1. Проверьте systemctl status и journalctl -u.
  2. Посмотрите, нет ли shell-обёртки в ExecStart.
  3. Убедитесь, что Type соответствует реальному поведению демона.
  4. Проверьте KillMode; для многопроцессных сервисов обычно подходят control-group или mixed.
  5. Дайте приложению адекватный TimeoutStopSec.
  6. Уберите лишний RemainAfterExit.
  7. Для forking-daemon проверьте PIDFile.
  8. После правок повторите stop/start и ещё раз посмотрите журнал.

Вывод

Предупреждение left-over process in control group почти всегда указывает на рассинхрон между тем, как приложение создаёт и завершает процессы, и тем, как это описано в unit-файле. В большинстве случаев причина находится быстро: достаточно проверить журнал, посмотреть cgroup и перепроверить несколько ключевых параметров сервиса.

Если свести всё к одному правилу, оно простое: запускайте основной процесс напрямую, правильно выбирайте Type, не злоупотребляйте RemainAfterExit и управляйте остановкой всей группы через подходящий KillMode и разумный TimeoutStopSec. Тогда и systemctl stop будет предсказуемым, и lingering processes перестанут ломать перезапуски.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: failed to create shim task и OCI runtime create failed в Docker — как найти причину и быстро исправить OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: failed to create shim task и OCI runtime create failed в Docker — как найти причину и быстро исправить

Если контейнер в Debian или Ubuntu не запускается и Docker пишет failed to create shim task или OCI runtime create failed, причину ...
Debian/Ubuntu: как исправить overlayfs: no space left on device в Docker и containerd OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить overlayfs: no space left on device в Docker и containerd

Ошибка overlayfs: no space left on device в Docker и containerd не всегда означает, что на сервере закончились гигабайты. На Debia ...
Debian/Ubuntu: SSH Broken pipe и Connection reset by peer — причины и пошаговая диагностика OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: SSH Broken pipe и Connection reset by peer — причины и пошаговая диагностика

Если SSH на Debian или Ubuntu обрывается с Broken pipe, Connection reset by peer или зависает после простоя, причина часто не в са ...