Разработчик запускает свежеустановленный Ubuntu в WSL, вводит привычную команду для старта Docker-демона и получает неприятный сюрприз. На экране появляется сообщение "System has not been booted with systemd as init system (PID 1). Can't operate". Никаких дополнительных пояснений, только категоричный отказ. Для тех, кто провел годы за работой с классическими Linux-дистрибутивами, это выглядит как странный каприз системы.
Ошибка затрагивает миллионы пользователей по всему миру. WSL превратился в незаменимый инструмент для программистов на Windows, а Docker стал стандартом в мире контейнеров. Обе технологии проектировались с учетом специфических требований, которые идут вразрез с традиционным подходом к управлению службами. Чтобы разобраться в ситуации, нужно понять, что происходит на уровне архитектуры процессов.
Первый процесс как фундамент системы
В классической архитектуре Linux первый процесс, запускаемый ядром после загрузки, всегда получает идентификатор 1. PID 1 становится родителем всех остальных процессов в системе и отвечает за их жизненный цикл. Systemd, занимая это место, выполняет роль управляющего центра. Он запускает службы параллельно, отслеживает зависимости между ними, перезапускает упавшие демоны и ведет централизованные логи через journald.
WSL и Docker подходят к вопросу инициализации совершенно иначе. В WSL долгое время использовался собственный процесс /init от Microsoft, который обеспечивал тесную интеграцию между файловыми системами Windows и Linux, но не предоставлял функционал systemd. В контейнерах Docker ситуация еще радикальнее. Там вместо PID 1 работает ваше приложение или shell-процесс вроде /bin/bash. Никакой полноценной системы инициализации просто не существует.
Когда пользователь пытается выполнить systemctl, утилита пытается установить соединение с демоном systemd через системную шину сообщений D-Bus. Если systemd не является процессом с идентификатором 1, это соединение физически невозможно. Система не обманывает, она честно признается в отсутствии нужного механизма. Проверить, какой процесс занимает PID 1, можно простой командой:
ps -p 1 -o comm=
В обычном Ubuntu вы увидите systemd, в WSL без специальной настройки появится init, а в Docker-контейнере будет имя вашего основного процесса.
Философия контейнеров против традиционных служб
Docker проектировался под концепцию "один контейнер - один процесс". Это не прихоть разработчиков, а осознанный выбор архитектуры. Контейнер должен выполнять одну задачу, делать её хорошо и при необходимости масштабироваться горизонтально. Запуск тяжеловесного инициализатора вроде systemd внутри контейнера противоречит этой идее.
Если запустить systemd внутри Docker, потребуется привилегированный режим, доступ к системным ресурсам хоста, монтирование cgroups. Всё это делает контейнер менее безопасным и более похожим на виртуальную машину. В результате убивается главное преимущество контейнеризации - легковесная изоляция.
В WSL ситуация другая. Microsoft разработала собственный уровень трансляции, где процесс /init выполняет специфические задачи вроде монтирования дисков Windows в /mnt/c и настройки сетевого взаимодействия между двумя операционными системами. Поскольку systemd жестко требует быть первым процессом для корректного управления контрольными группами и сигналами, он долгое время не мог работать как дочерний процесс WSL.
Официальная поддержка systemd в WSL
В сентябре 2022 года Microsoft объявила об официальной поддержке systemd в WSL 2. Это стало возможным благодаря переработке процесса загрузки. Теперь WSL запускает systemd как PID 1, а проприетарный процесс /init становится его дочерним элементом, сохраняя функции интеграции.
Для включения требуется WSL версии 0.67.6 или выше. Проверка выполняется через PowerShell:
wsl --version
Если команда выдает ошибку "Invalid command line option", значит установлена старая встроенная версия WSL. Обновление выполняется через Microsoft Store или командой:
wsl --update
После обновления нужно внести изменения в конфигурационный файл дистрибутива. Откройте терминал WSL и отредактируйте /etc/wsl.conf:
sudo nano /etc/wsl.conf
Добавьте следующие строки:
[boot]
systemd=true
Сохраните файл (Ctrl+X, затем Y и Enter). Теперь полностью остановите WSL из PowerShell:
wsl.exe --shutdown
При следующем запуске дистрибутива systemd будет работать как основной инициализатор. Проверить успешность активации можно командой:
systemctl list-unit-files --type=service
Если всё настроено правильно, вы увидите список служб с их статусами. Для дистрибутивов Debian, Ubuntu и Kali также понадобится пакет systemd-sysv:
sudo apt-get update
sudo apt-get install systemd systemd-sysv
Альтернативные команды управления без systemd
В средах, где активация systemd невозможна или нежелательна, основным инструментом становится команда service. Эта утилита существует уже много лет и представляет собой высокоуровневый интерфейс для работы с различными системами инициализации.
Команда service работает как универсальная обертка. В системах с systemd она перенаправляет команды к systemctl. Если система работает на базе старого SysVinit, service обращается к сценариям в /etc/init.d/. Запуск веб-сервера nginx:
sudo service nginx start
Остановка службы:
sudo service nginx stop
Проверка состояния:
sudo service nginx status
Список всех доступных служб:
sudo service --status-all
Если service не работает, можно обратиться напрямую к сценариям инициализации. Директория /etc/init.d/ содержит shell-скрипты для управления демонами:
sudo /etc/init.d/nginx start
sudo /etc/init.d/ssh restart
sudo /etc/init.d/mysql stop
Этот метод считается менее предпочтительным, так как он не гарантирует очистку переменных окружения, но работает практически везде.
Для систем Debian и Ubuntu существует еще одна команда - invoke-rc.d. Она более строго следует политикам системы и проверяет разрешения перед запуском:
sudo invoke-rc.d docker start
Прямой запуск демонов и ручная настройка
Когда не работают ни systemctl, ни service, ни скрипты /etc/init.d/, остается последний вариант. Запуск бинарного файла напрямую. Этот метод требует понимания специфики конкретной службы.
Для SSH-сервера в WSL критически важна директория /run/sshd. Поскольку /run является временной файловой системой, эта директория исчезает после каждой перезагрузки. Правильная последовательность запуска:
sudo mkdir -p /run/sshd
sudo chmod 0755 /run/sshd
sudo /usr/sbin/sshd -D
Флаг -D запускает демон в foreground-режиме, что удобно для отладки. Для фонового запуска используйте:
sudo /usr/sbin/sshd &
Для nginx всё проще. Запуск с конфигурацией по умолчанию:
sudo nginx
Перезагрузка конфигурации без остановки:
sudo nginx -s reload
Плавное завершение:
sudo nginx -s quit
В Docker nginx должен работать в режиме без демонизации:
nginx -g 'daemon off;'
Это предотвращает немедленное завершение контейнера после запуска фонового процесса.
Автозапуск служб в WSL
Отсутствие автоматического запуска служб при включении компьютера раздражает многих пользователей WSL. Существует несколько способов автоматизации, которые работают надежно даже без systemd.
Наиболее правильный подход использует секцию [boot] в файле /etc/wsl.conf. Отредактируйте конфигурацию:
sudo nano /etc/wsl.conf
Добавьте команды запуска:
[boot]
command="service docker start; service ssh start"
Этот метод выполняется один раз при старте WSL и не требует ввода пароля. Другой вариант использует планировщик задач Windows. Создайте задачу, которая при входе пользователя выполняет:
wsl.exe -u root service docker start
Это запускает службы в фоновом режиме без открытия терминала.
Работа с Docker-контейнерами без systemd
В Docker лучше вообще не пытаться использовать systemctl. Контейнеры проектируются для запуска одного процесса в foreground-режиме. Если нужно несколько служб в одном контейнере, используйте специализированные менеджеры процессов.
Supervisord позволяет описывать конфигурации служб в декларативном стиле. Установка в Ubuntu-контейнере:
apt-get update
apt-get install -y supervisor
Создайте конфигурацию в /etc/supervisor/conf.d/services.conf:
[supervisord]
nodaemon=true
[program:nginx]
command=nginx -g 'daemon off;'
autostart=true
autorestart=true
[program:php-fpm]
command=/usr/sbin/php-fpm8.1 -F
autostart=true
autorestart=true
Запуск в Dockerfile:
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
Для правильной обработки сигналов и жатвы зомби-процессов используйте микро-инициализатор tini. Запуск контейнера:
docker run --init myimage
Флаг --init автоматически использует встроенный tini как PID 1. Это решает проблемы с корректным завершением процессов и предотвращает накопление зомби.
Выбор правильного инструмента зависит от задачи. Если нужна полная совместимость с классическими дистрибутивами Linux, включение systemd в WSL решает проблему радикально. Если приоритет отдается скорости и экономии ресурсов, легкие альтернативы вроде service и прямого запуска оказываются эффективнее. Понимание механизмов работы изолированных сред позволяет строить надежные системы, которые не преподносят неприятных сюрпризов в критический момент.