Устранение неполадок служб Systemd: пошаговое руководство

Диагностируйте сбои служб systemd с помощью проверки статуса, журналов journalctl, анализа файлов модулей, исправления зависимостей и отладки окружения.

Устранение неполадок служб Systemd: пошаговое руководство

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

Примеры ниже используют вымышленную службу mywebapp.service, но тот же подход применим к помощникам баз данных, потребителям очередей, заданиям резервного копирования, экспортерам и внутренним демонам.

Первая линия обороны: systemctl status

Когда служба не запускается, первая команда, которую вы должны выполнить, — systemctl status <имя_службы>. Эта команда предоставляет снимок текущего состояния службы, включая то, активна ли она, загружена ли, и, что важно, фрагмент ее недавних журналов. Часто этого достаточно, чтобы быстро определить проблему.

Допустим, ваша служба веб-приложения mywebapp.service не запускается:

systemctl status mywebapp.service

Пример интерпретации вывода:

● mywebapp.service - My Web Application
     Loaded: loaded (/etc/systemd/system/mywebapp.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Mon 2023-10-26 10:30:05 UTC; 10s ago
    Process: 12345 ExecStart=/usr/local/bin/mywebapp-start.sh (code=exited, status=1/FAILURE)
   Main PID: 12345 (code=exited, status=1/FAILURE)
        CPU: 10ms

Oct 26 10:30:05 hostname systemd[1]: Started My Web Application.
Oct 26 10:30:05 hostname mywebapp-start.sh[12345]: Error: Port 8080 already in use
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Main process exited, code=exited, status=1/FAILURE
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Failed with result 'exit-code'.

Из этого вывода мы сразу видим:

  • Служба mywebapp.service находится в состоянии failed.
  • Она завершилась с Result: exit-code, что означает, что команда ExecStart завершилась с ненулевым статусом.
  • Строка Process показывает, что команда mywebapp-start.sh завершилась с status=1/FAILURE.
  • Ключевой момент: строки журнала указывают: Error: Port 8080 already in use. Это явный признак проблемы.

Эта команда — ваш первый диагностический инструмент, часто указывающий непосредственно на причину или сужающий область поиска.

Глубокое погружение с journalctl

В то время как systemctl status предоставляет краткую сводку, journalctl — ваша основная команда для детального логирования. Она запрашивает журнал systemd, который собирает логи со всех частей системы, включая службы.

Базовый просмотр журнала

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

journalctl -u mywebapp.service

Это покажет все записи журнала, связанные с mywebapp.service. Если служба многократно выходит из строя, вы увидите записи каждой неудачной попытки.

Фильтрация и запросы на основе времени

Чтобы сузить результаты, особенно после недавнего сбоя, можно использовать флаги, такие как --since и --priority:

  • Показать логи с определенного времени:
    journalctl -u mywebapp.service --since "10 minutes ago"
    journalctl -u mywebapp.service --since "2023-10-26 10:00:00"
    
  • Показать только сообщения уровня ошибки и выше:
    journalctl -u mywebapp.service -p err
    
  • Комбинировать с -xe для расширенного объяснения и подробного вывода:
    journalctl -u mywebapp.service -xe --since "5 minutes ago"
    
    -x может добавить пояснительный текст для некоторых сообщений systemd. Относитесь к этим объяснениям как к подсказкам, а не как к замене журналов, специфичных для модуля.

Понимание сообщений журнала

Ищите ключевые слова, такие как Error, Failed, Warning или специфичные для приложения сообщения, которые указывают, что пошло не так. Обращайте внимание на временные метки, чтобы понять последовательность событий, приведших к сбою.

Совет: Если ваш скрипт ExecStart службы выводит данные в стандартный вывод или стандартную ошибку, эти сообщения обычно захватываются journalctl. Убедитесь, что ваши скрипты записывают информативные сообщения об ошибках.

Проверка файла модуля: чертеж вашей службы

Каждая служба systemd определяется файлом модуля (например, mywebapp.service). Неправильные конфигурации в этом файле являются частой причиной сбоев при запуске. Вам нужно понять, что служба пытается сделать.

Получение файла модуля

Чтобы просмотреть активный файл модуля для вашей службы:

systemctl cat mywebapp.service

Эта команда показывает точный файл модуля, который использует systemd, включая любые переопределения.

Ключевые директивы для проверки

Сосредоточьтесь на разделе [Service] для проблем, связанных с выполнением, и [Unit] для зависимостей.

  • ExecStart: Это команда, которую systemd выполняет для запуска вашей службы. Проверьте, правильный ли путь, и сама команда исполняема и успешно выполняется при ручном вызове (например, от указанного пользователя User).
    ExecStart=/usr/local/bin/mywebapp-start.sh
    
  • Type: Определяет тип запуска процесса. Распространенные типы включают:
    • simple (по умолчанию): ExecStart является основным процессом.
    • forking: ExecStart порождает дочерний процесс, а родительский завершается. Systemd ждет завершения родительского процесса.
    • oneshot: ExecStart запускается и завершается; systemd считает службу активной, пока выполняется команда.
    • notify: Служба отправляет уведомление systemd, когда готова.
    • Неправильный Type может привести к тому, что systemd подумает, что служба завершилась ошибкой, когда она на самом деле запустилась, или наоборот.
  • User / Group: Пользователь и группа, от имени которых будет работать служба. Проблемы с разрешениями часто возникают из-за того, что служба пытается получить доступ к файлам или ресурсам, на которые у нее нет прав от этого пользователя.
    User=mywebappuser
    Group=mywebappgroup
    
  • WorkingDirectory: Каталог, из которого будет выполняться служба. Относительные пути в ExecStart или других командах зависят от этого.
  • Restart: Определяет, когда служба должна быть перезапущена. Если установлено значение on-failure или always, сбойная служба может постоянно перезапускаться, что затрудняет обнаружение первоначального сбоя.
  • TimeoutStartSec / TimeoutStopSec: Как долго systemd ждет запуска или остановки службы. Если службе требуется больше времени для инициализации, чем TimeoutStartSec, systemd убьет ее и сообщит об ошибке.

Распространенные проблемы файлов модулей

  • Неправильные пути: Опечатка в ExecStart или других путях к файлам.
  • Отсутствующие переменные окружения (Environment): Службам часто требуются определенные переменные окружения (например, PATH), которые могут отсутствовать в чистом окружении systemd (см. ниже).
  • Разрешения: Указанный User не имеет прав на выполнение скрипта или прав чтения/записи для необходимых файлов данных.
  • Синтаксические ошибки: Простые опечатки в самом файле модуля.

Чтобы проверить ExecStart вручную:

Переключитесь на пользователя службы и попробуйте выполнить команду напрямую:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Это часто воспроизводит ошибку, видимую в journalctl, непосредственно в вашем терминале, что упрощает отладку.

Управление зависимостями: когда службы не могут запуститься в одиночку

Службы часто полагаются на другие службы или компоненты системы, которые должны быть активны, прежде чем они смогут запуститься сами. Systemd использует директивы Wants, Requires, After и Before для управления этими зависимостями.

Определение зависимостей

Используйте systemctl list-dependencies <имя_службы>, чтобы увидеть, что службе явно требуется или что она хочет запустить.

systemctl list-dependencies mywebapp.service

Распространенные директивы в разделе [Unit]:

  • After=: Указывает, что эта служба должна запускаться после перечисленных модулей. Если перечисленный модуль выйдет из строя, эта служба все равно попытается запуститься (если также не используется Requires=).
  • Requires=: Указывает, что этой службе требуются перечисленные модули. Если какой-либо из требуемых модулей не запустится, эта служба не запустится.
  • Wants=: Более слабая форма Requires=. Если желаемый модуль выходит из строя, эта служба все равно попытается запуститься.

Пример:

[Unit]
Description=My Web Application
After=network.target mysql.service
Requires=mysql.service

Здесь mywebapp.service упорядочен после network.target и mysql.service, и ему требуется, чтобы mysql.service был успешно запущен. Если mysql.service выйдет из строя, mywebapp.service не запустится.

Разрешение конфликтов зависимостей

Если служба выходит из строя из-за проблемы с зависимостью, journalctl обычно укажет, какая зависимость не была удовлетворена. Например, может быть указано Dependency failed for My Web Application с последующими подробностями о сбое mysql.service.

Шаги для разрешения:

  1. Проверьте зависимую службу: Выполните systemctl status <зависимая_служба> (например, systemctl status mysql.service) и journalctl -u <зависимая_служба>, чтобы сначала устранить ее неисправность.
  2. Проверьте директивы After= и Requires=: Убедитесь, что они правильно отражают желаемый порядок запуска и строгость. Иногда службе нужно дождаться открытия определенного порта, а не просто завершения задания запуска другого модуля. Для узких проверок может помочь ExecStartPre=. Для сетевых демонов часто более надежны активация сокетов или логика повторных попыток на уровне приложения.

Переменные окружения и пути: скрытые ловушки

Службы systemd работают в очень чистом и минимальном окружении. Это часто приводит к проблемам, когда команды, которые отлично работают в оболочке пользователя, не выполняются при запуске systemd, потому что отсутствуют важные переменные окружения (например, PATH).

Чистое окружение Systemd

Когда systemd запускает службу, он не наследует полное окружение пользователя, инициировавшего systemctl start. Переменная PATH, например, часто урезана, что означает, что такие команды, как python или node, могут не быть найдены, если они не находятся в стандартных местах, таких как /usr/bin или /bin.

Симптом: ExecStart=/usr/local/bin/myscript.sh завершается с ошибкой python: command not found, node: command not found, ошибкой отсутствия библиотеки или сообщением приложения о том, что требуемая настройка пуста.

Исправление: Сделайте окружение службы явным.

[Service]
WorkingDirectory=/opt/mywebapp
Environment="APP_ENV=production"
Environment="PATH=/opt/mywebapp/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/mywebapp/venv/bin/gunicorn app:app

Для многих переменных используйте файл окружения:

[Service]
EnvironmentFile=/etc/mywebapp/mywebapp.env
ExecStart=/opt/mywebapp/bin/server

Держите этот файл простым. EnvironmentFile= — это не Bash-скрипт. Используйте строки KEY=value, а не export KEY=value, подстановку команд или условные конструкции оболочки. Также установите ограничительные права, если файл содержит секреты:

sudo chown root:mywebapp /etc/mywebapp/mywebapp.env
sudo chmod 0640 /etc/mywebapp/mywebapp.env

Разрешения: воспроизведите сбой от имени пользователя службы

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

Проверьте настроенного пользователя:

systemctl show mywebapp.service -p User -p Group

Затем выполните ту же команду от имени этого пользователя:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Если приложению нужен рабочий каталог, укажите его:

sudo -u mywebappuser bash -lc 'cd /opt/mywebapp && /usr/local/bin/mywebapp-start.sh'

Смотрите дальше исполняемого файла. Пользователю службы может потребоваться доступ на чтение к /etc/mywebapp/config.yml, доступ на запись к /var/lib/mywebapp, доступ на выполнение к каждой родительской директории или разрешение на создание Unix-сокета в /run/mywebapp. Быстрая проверка может сэкономить много догадок:

sudo -u mywebappuser test -r /etc/mywebapp/config.yml
sudo -u mywebappuser test -w /var/lib/mywebapp
namei -l /var/lib/mywebapp/uploads

Если служба выходит из строя только при привязке к низкому порту, такому как 80 или 443, не запускайте ее сразу от root. Обратный прокси, активация сокета или целевая возможность могут быть безопаснее в зависимости от службы.

Лимиты запуска и циклы перезапуска

Служба, которая многократно падает, может остановиться с сообщением типа start request repeated too quickly. Это означает, что сработал лимит скорости systemd. Первоначальный сбой произошел раньше, поэтому не сосредотачивайтесь только на сообщении о лимите скорости.

Используйте:

journalctl -u mywebapp.service --since "30 minutes ago"
systemctl show mywebapp.service -p NRestarts -p Restart -p StartLimitBurst -p StartLimitIntervalUSec

После устранения коренной причины сбросьте состояние сбоя:

sudo systemctl reset-failed mywebapp.service
sudo systemctl start mywebapp.service

Будьте осторожны с Restart=always. Это полезно для отказоустойчивых демонов, но во время отладки может завалить журнал и скрыть первую четкую ошибку. Вы можете временно остановить модуль, просмотреть журналы и запустить его вручную после того, как измените что-то одно.

Проверьте модуль перед перезагрузкой

Прежде чем перезапускать службу после редактирования файла модуля, проверьте файл и перезагрузите systemd:

sudo systemd-analyze verify /etc/systemd/system/mywebapp.service
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service

Если у службы есть переопределения drop-in, проверьте объединенную версию:

systemctl cat mywebapp.service
systemctl show mywebapp.service -p FragmentPath -p DropInPaths -p ExecStart

Это ловит неловкие случаи: вы отредактировали файл в /usr/lib/systemd/system, но drop-in в /etc/systemd/system/mywebapp.service.d/override.conf все еще изменяет ExecStart; или вы исправили скопированный файл модуля, который не является тем, который загрузил systemd.

Практический порядок действий

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

  1. Выполните systemctl status mywebapp.service --no-pager.
  2. Прочитайте journalctl -u mywebapp.service --since "15 minutes ago".
  3. Проверьте systemctl cat mywebapp.service.
  4. Проверьте команду, пользователя, рабочий каталог, окружение и зависимости.
  5. Воспроизведите команду от имени пользователя службы.
  6. Внесите одно изменение.
  7. Выполните systemctl daemon-reload, если модуль изменился.
  8. Перезапустите и снова проверьте журнал.

Этот порядок держит расследование обоснованным. Если в журнале сказано Permission denied, исправьте разрешения. Если сказано No such file or directory, проверьте пути с точки зрения systemd. Если сказано Dependency failed, сначала отладьте зависимость. Если сказано, что процесс завершился со статусом 0/SUCCESS, но служба не работает, проверьте Type= и то, демонизируется ли приложение или завершается немедленно.

Цель — не запомнить каждую директиву systemd. Это — продолжать сопоставлять сообщение об ошибке с тем слоем, который его породил.