Запускаем systemd в docker контейнере
На просторах интернета вы можете найти некоторое количество руководств, описывающих, как запустить systemd в docker контейнере. Однако нашей лаборатории не удалось выявить что-то действительно стоящее, так как некоторые из них предлагают вам запускать контейнер в привилигированном режиме (серьезно?), некоторые предлагают поудалять в полуручном режиме все юниты из /var/lib/systemd/system и /etc/systemd/system, которые при первом же апдейте будут восстановлены пакетным менеджером... Вообщем держите наш рецеп по запуску systemd в docker контейнере.
1. Устанавливаем правильный default.target
Вместо того, чтобы удалять файлы юнитов из каталогов /usr/lib/systemd/system и /etc/systemd/system, которые при обновлении пакетов будут успешно восстановлены пакетным менеджером, мы создадим свой собвственный default.target. Напомним, default.target - это юнит типа ".target", который сам по себе ничего не делает, а лишь является поинтером - своего рода состоянием системы. Выставляя в качестве зависимостей к таргету другие юниты, вы заставляете systemd загружать группу сервисов, точек монтировая, таймеров и т.д. А default таргет является конечной точкой загрузки системы. Весь процесс можно представить ввиде елочки, где вершиной является default.target. Systemd начинает строить дерево зависимостей сверху вниз, начиная с default.target, а стартует юниты само-собой снизу. О таргетах мы, кстати, уже писали тут.
Не трудно догадаться, что default.target, который назначен в вашем образе операционной системы будет пытаться стартануть контейнер как настоящую операционную систему: монтировать файловые системы, создавать сокеты, запускать терминалы (что, кстати, приводит к захвату контейнером основной системы)... Поэтому-то люди и пытаются удалить юниты, которые это делают. А мы просто меняем default.target не приводя систему в неконсистентное состояние.
Итак, создадим файл с названием container.target следующего содержания:
$ cat container.target
[Unit]
Description=Container target - special target for running systemd in docker containers
Как вы видите, наш таргет не делает ничего. А как же dbus, спросите вы, как же journald? Известны случаи, когда люди получают вот такое сообщение об ошибке, если не запущен dbus:
Failed to get D-Bus connection: Operation not permitted
Ответ таков: во-первых, systemd сам себе dbus. Да-да, systemd является еще и dbus сервером, но особым "только для своих". Утилита systemctl не использует тот самый dbus, а подключается к dbus, встроенному в systemd. Если попытаться повысить уровень лога systemd до дебага, то среди прочего будет сообщение
Successfully created private D-Bus server
Это значит, systemd готов принимать сообщения от утилиты systemctl. Кстати, если кто не знал, сокет подключения ко встроенному в systemd dbus-серверу находится тут: /run/systemd/private.
Во-вторых, если ваш сервис нуждается в нормальном dbus, journald или еще-какой-либо зависимости, то пишите ваши юнит-файлы правильно - всегда перечисляйте все зависимости в "Requires=" или "Wants=". Это поможет избежать ситуации, когда сервису не хватает чего-то для нормальной работы.
Итак, создадим Dockerfile и соберем контейнер на примере CentOS
$ cat Dockerfile
FROM centos
ENV container=docker
COPY container.target /etc/systemd/system/container.target
RUN ln -sf /etc/systemd/system/container.target /etc/systemd/system/default.target
ENTRYPOINT ["/sbin/init"]
CMD ["--log-level=info"]
STOPSIGNAL SIGRTMIN+3
$ docker build --tag centos:systemd .
Маленькое пояснение по поводу "ENV container=docker" и "STOPSIGNAL SIGRTMIN+3". Первая команда делает возможным для systemd определить, что он запущен внутри контейнера и автоматически делать или наоборот не далеть некторые вещи. А стопсигнал позволяет правильно завершать работу. Docker обычно посылает процессу, запущенному внутри контейнера, SIGTERM при вызове команды "docker stop". И большинство процессов воспринимают этот сигнал в качестве требования корректно завершить работу. Systemd же для этих целей использует SIGTMIN+3 и не реагирует на SIGTERM
2. Монтируем необходимые файловые системы
Вместо того, чтобы как многие рекомендуют, запускать docker контейнер с systemd в привилигированном режиме или с расширенными привилегиями типа CAP_SYS_ADMIN, мы дадим systemd все, что ему нужно для работы в непривилигированном режиме, а именно специальные ядерные файловые системы, которые в случае с реальным компьютером systemd пытается монтировать в процессе загрузки. В их числе /sys/fs/cgroup, /sys/fs/fuse и /var/run
$ docker run \
--detach \
--name=centos-systemd \
--mount type=bind,source=/sys/fs/cgroup,target=/sys/fs/cgroup \
--mount type=bind,source=/sys/fs/fuse,target=/sys/fs/fuse \
--mount type=tmpfs,destination=/run \
--mount type=tmpfs,destination=/run/lock centos:systemd
Что плохого вас тут может поджидать... На некоторых системах вы можете встретить вот такое сообщение
Failed to determine whether /sys is a mount point: Operation not permitted
Failed to determine whether /proc is a mount point: Operation not permitted
Failed to determine whether /dev is a mount point: Operation not permitted
Failed to determine whether /dev/shm is a mount point: Operation not permitted
Failed to determine whether /run is a mount point: Operation not permitted
Failed to mount tmpfs at /run/lock: Operation not permitted
Failed to determine whether /sys/fs/cgroup is a mount point: Operation not permitted
Failed to determine whether /sys/fs/cgroup/systemd is a mount point: Operation not permitted
[!!!!!!] Failed to mount API filesystems, freezing.
Freezing execution.
Страшного ничего в этом нет. Это просто означает, что вы стали жертвой стечения обстоятельств. С некоторой версии докер стал блокировать системный вызов "name_to_handle_at", который используется systemd для определения примонтированных файловых систем. И решений у этой проблемы ровно три. Во-первых, убедитесь, что вы используете самую свежую версии образа операционной системы и дополнительно выясните, нет ли в ней еще отдельного обновления для systemd - разработчики systemd нашли способ обхода проблемы, поэтому, если ваш дистрибьютор оперативно доставляет обновления в систему, то это скорее всего поможет. Если нет, тогда либо выключаем для контейнера фильтрацию системных вызовов путем добавления дополнительного ключа запуска
--security-opt seccomp:unconfined
Любо, если вы немного параноик и все еще хотите оставить фильтрацию включенной, тогда скачиваем профиль seccomp по-умолчанию отсюда, листаем до "syscalls", далее "names", находим длиннющий список разнообразных системных вызовов и в любое его место вставляем "name_to_handle_at". Файл в формате json - если вставляете в середину, запятая нужна, если в конец, то нет. Далее запускаем контейнер с этим профилем - нам понадобится добавить ключ запуска, всё тот же "--security-opt seccomp:/${PATH_TO_NEW_SECCOMP_PROFILE}", только всемто "unconfined" как в прошлом примере, мы передаем путь до новго файла профиля seccomp.
Всё! Имеем docker контейнер с systemd внутри, работающей утилитой systemctl и даже возможностью произвести shutdown
П.с. вы можете найти уже готовые образы для запуска systemd в docker контейнере в нашем репозитории на Docker Hub
Комментарии
Mike Clise (не проверено)
вт, 03/05/2019 - 01:43
Постоянная ссылка (Permalink)
Thank you for extensive info
serg (не проверено)
сб, 05/11/2019 - 17:50
Постоянная ссылка (Permalink)
Спасибо, есть интересные
Добавить комментарий