![](https://habrastorage.org/webt/jw/is/oz/jwisoz8445mav6kqiwqr7gwpjns.png)
В этой статье мы покажем, что изменилось за прошедшее время и как нам может помочь в этом вопросе Podman.
Есть много причин для того, чтобы запускать systemd внутри контейнера, такие как:
- Мультисервисные контейнеры – многие хотят вытащить свои мультисервисные приложения из виртуальных машин и запускать их в контейнерах. Лучше бы, конечно, разбить такие приложения на микросервисы, но не все пока это умеют или просто нет времени. Поэтому запуск таких приложений в виде сервисов, запускаемых systemd из юнит-файлов, вполне имеет смысл.
- Юнит-файлы Systemd – большинство приложений, работающих внутри контейнеров, собраны из кода, который до этого запускался на виртуальных или физических машинах. У этих приложений есть юнит-файл, который писался под эти приложения и понимает, как их надо запускать. Так что сервисы все же лучше запускать с помощью поддерживаемых методов, а не взламывая свою собственную init-службу.
- Systemd – это диспетчер процессов. Он осуществляет управление сервисами (завершает работу, перезапускает сервисы или выкашивает зомби-процессы) лучше, чем любой другой инструмент.
При этом есть и много причин для того, чтобы не запускать systemd в контейнерах. Основная заключается в том, что systemd/journald контролирует вывод контейнеров, а инструменты вроде Kubernetes или OpenShift рассчитывают, что контейнеры будут писать лог непосредственно в stdout и stderr. Поэтому, если вы собираетесь управлять контейнерами через средства оркестрации типа указанных выше, то надо серьезно обдумать вопрос использования контейнеров на базе systemd. Кроме того, разработчики Docker и Moby часто были резко против использования systemd в контейнерах.
Пришествие Podman’а
С радостью сообщаем, что ситуация наконец-то сдвинулась с мертвой точки. Команда, отвечающая в Red Hat за запуск контейнеров, решила разработать свой собственный контейнерных движок. Он получил имя Podman и предлагает такой же интерфейс командной строки (CLI) как у Docker’а. И практически все команды Docker точно так же можно использовать в Podman. Мы часто проводим семинары, которые теперь называются Меняем Docker на Podman, и первый же слайд призывает прописать: alias docker=podman.
Многие так и делают.
Мы со своим Podman’ом ни в коей мере не против контейнеров на основе systemd. Ведь Systemd чаще других используется в качестве init-подсистемы Linux, и не давать ей нормально работать в контейнерах значит игнорировать то, как тысячи людей привыкли запускать контейнеры.
Podman знает, что надо делать, чтобы systemd нормально работала в контейнере. Ей нужны такие вещи, как монтирование tmpfs на /run и /tmp. Ей нравится, когда включена «контейнерная» среда, и она ждет прав на запись в свою часть каталога cgroup и в папку /var/log/journald.
При запуске контейнера, в котором первой командой идет init или systemd, Podman автоматически настраивает tmpfs и Cgroups для того, чтобы запуск systemd прошел без проблем. Чтобы заблокировать такой авторежим запуска, используется опция --systemd=false. Обратите внимание, что Podman использует systemd-режим только тогда, когда видит, что надо выполнить команду systemd или init.
Вот выдержка из мануала:
man podman run
…
–systemd=true|false
Запуск контейнера в режиме systemd. По умолчанию включен.
Если внутри контейнера выполняется команда systemd или init, Podman настроит точки монтирования tmpfs в следующих каталогах:
/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal
Также в качестве сигнала остановки по умолчанию будет использоваться SIGRTMIN+3.
Все это позволяет systemd работать в замкнутом контейнере без каких-либо модификаций.
ПРИМЕЧАНИЕ: systemd пытается выполнить запись в файловую систему cgroup. Однако SELinux по умолчанию запрещает контейнерам это делать. Чтобы разрешить запись, включите логический параметр container_manage_cgroup:
setsebool -P container_manage_cgroup true
Теперь посмотрите, как выглядит Dockerfile для запуска systemd в контейнере при использовании Podman’а:
# cat Dockerfile
FROM fedora
RUN dnf -y install httpd; dnf clean all; systemctl enable httpd
EXPOSE 80
CMD [ "/sbin/init" ]
Вот и всё.
Теперь собираем контейнер:
# podman build -t systemd .
Говорим SELinux разрешить systemd модифицировать конфигурацию Cgroups:
# setsebool -P container_manage_cgroup true
Многие, кстати, забывают про этот шаг. К счастью, это достаточно сделать всего один раз и настройка сохраняется после перезагрузки системы.
Теперь просто запускаем контейнер:
# podman run -ti -p 80:80 systemd
systemd 239 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid)
Detected virtualization container-other.
Detected architecture x86-64.
Welcome to Fedora 29 (Container Image)!
Set hostname to <1b51b684bc99>.
Failed to install release agent, ignoring: Read-only file system
File /usr/lib/systemd/system/systemd-journald.service:26 configures an IP firewall (IPAddressDeny=any), but the local system does not support BPF/cgroup based firewalling.
Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)
[ OK ] Listening on initctl Compatibility Named Pipe.
[ OK ] Listening on Journal Socket (/dev/log).
[ OK ] Started Forward Password Requests to Wall Directory Watch.
[ OK ] Started Dispatch Password Requests to Console Directory Watch.
[ OK ] Reached target Slices.
…
[ OK ] Started The Apache HTTP Server.
Всё, сервис запустился и работает:
$ curl localhost
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
…
</html>
ПРИМЕЧАНИЕ: Не пытайтесь повторить это на Docker’е! Там по-прежнему нужны танцы с бубном, чтобы запускать такого рода контейнеры через демона. (Потребуются дополнительные поля и пакеты, чтобы все это бесшовно заработало в Docker, либо надо будет запускать в привилегированном контейнере. Подробности см. в статье.)
Еще пара крутых вещей о Podman и systemd
Podman работает лучше Docker в юнит-файлах systemd
Если контейнеры надо запускать при загрузке системы, то можно просто вставить соответствующие команды Podman в юнит-файл systemd, тот запустит сервис и будет его мониторить. Podman использует стандартную модель ветвления при исполнении (fork-exec). Иначе говоря, контейнерные процессы являются дочерними по отношению к процессу Podman’а, поэтому systemd легко может их мониторить.
Docker использует модель клиент-сервер, и CLI-команды Docker тоже можно размещать прямо в юнит-файле. Однако после того, как Docker-клиент подключается к Docker-демону, он (клиент) становится просто еще одним процессом, обрабатывающим stdin и stdout. В свою очередь, systemd понятия не имеет о связи между Docker-клиентом и контейнером, который работает под управлением Docker-демона, и поэтому в рамках этой модели systemd принципиально не может мониторить сервис.
Активация systemd через сокет
Podman корректно отрабатывает активирование через сокета. Поскольку Podman использует модель fork-exec, он может пробрасывать сокет своим дочерним контейнерным процессам. Docker так не умеет, поскольку использует модель клиент-сервер.
Сервис varlink, который Podman использует для взаимодействия удаленных клиентов с контейнерами, на самом деле активируется через сокет. Пакет cockpit-podman, написанный на Node.js и входящий в состав проекта cockpit, позволяет людям взаимодействовать с контейнерами Podman через веб-интерфейс. Веб-демон, на котором крутится cockpit-podman, посылает сообщения на varlink-сокет, который прослушивается systemd. После чего systemd активирует программу Podman для получения сообщений и начала управления контейнерами. Активация systemd через сокет позволяет обойтись без постоянно работающего демона при реализации удаленных API.
Кроме того, мы разрабатываем еще один клиент для Podman’а под названием podman-remote, который реализует тот же самый Podman CLI, но вызывает varlink для запуска контейнеров. Podman-remote может работать поверх SSH-сеансов, что позволяет безопасно взаимодействовать с контейнерами на различных машинах. Со временем мы планируем задействовать podman-remote для поддержки MacOS и Windows наряду с Linux, чтобы разработчики на этих платформах могли запускать виртуальную машину Linux с работающим Podman varlink и иметь полное ощущение, что контейнеры выполняются на локальной машине.
SD_NOTIFY
Systemd позволяет отложить запуск вспомогательных сервисов до того момента, пока не стартует необходимый им контейнеризованный сервис. Podman может пробросить сокет SD_NOTIFY в контейнеризованный сервис, чтобы это сервис уведомил systemd о своей готовности к работе. И опять же Docker, использующий модель клиент-сервер, так не умеет.
В планах
Мы планируем добавить команду podman generate systemd CONTAINERID, который будет генерировать юнит-файл systemd для управления конкретным заданным контейнером. Это должно работать как в root-, так и в rootless-режимах для непривилегированных контейнеров. Мы даже видел запрос на создания OCI-совместимой среды исполнения systemd-nspawn.
Заключение
Запуск systemd в контейнере – это вполне понятная потребность. И благодаря Podman у нас наконец-то есть среда запуска контейнеров, которая не враждует с systemd, а позволяет легко его использовать.
Комментарии (32)
evg_krsk
26.09.2019 11:28запрос на создания OCI-совместимой среды исполнения systemd-nspawn.
О чём идет речь, не могли бы вы пояснить? Слова все известные, но смысл ускользает.
vrutkovs
26.09.2019 11:44Сейчас и docker и podman являются фактически обертками над runc, который собственно занимается созданием нужных неймспейсов. Разница в том, что в docker runc «впилен» в сам пакет докера, а в podman этот рантайм идет отдельно — и его можно заменять (к примеру, в Fedora 31 можно использовать crun).
Кроме того, общение с runc происходит по стандартному протоколу, который регулируется runtime spec. С недавних пор этот протокол поддерживает systemd-nspawn, а значит его можно использовать вместо runP6i
26.09.2019 13:58docker engine уже давно отделен от containerd, а у containerd, в свою очередь, runc тоже рантайм, который можно заменить, на тот же runv (https://github.com/hyperhq/runv) или любой OCI совместимый.
Если подвпилен
имелось ввиду то, что, runc у докера идет в базе — то да, тут все верноvrutkovs
26.09.2019 17:22А, верно, я был уверен что привязать другой рантайм в докере труднее, а всё просто, спасибо
r-moiseev
26.09.2019 19:23+2Первый аргумент зачем очень странный. Какая нужда запускать сервисы в общем контейнере?
mayorovp
27.09.2019 09:13Ну например, в образ контейнера может быть упаковано коробочное решение. Пользователю же намного проще запустить контейнер, чем запускать три контейнера и настраивать связи между ними.
Когда контейнеры используются разработчиком — то же самое, скачать образ со всем необходимым dev-окружением намного проще, чем скачать три образа и настроить их связи.
shadowlord
27.09.2019 13:08И после таких статей и советов приходит к тебе подрядчик вот с таким решением, когда в одном контейнере упаковано все, а у тебя кубер. И совершенно непонятно, как это запускать без переделки: ни логов нормально не собрать, ни масштабировать невозможно. А подрядчик переделывать не хочет, он ведь разработчик, ему так удобнее и его совершенно не интересует, как это потом должно в эксплуатации работать.
mayorovp
27.09.2019 13:34А вы что заказывали-то? Коробочное решение или что-то, что вы сможете масштабировать?
Если первое — то сами и виноваты. Если второе — ну так пропишите в договоре-то в какой форме вы решение принимать будете.
shadowlord
28.09.2019 01:47Стандартная ситуация, когда заказывает начальство, а мы (эксплуатация) видим только конечный результат, на который повлиять уже не можем, но должны отвечать за бесперебойную работу сервиса под любой нагрузкой.
gecube
27.09.2019 10:19+1а не взламывая свою собственную init-службу.
Извините, это перевод? Что подразумевалось? Или имелось в виду — hacking как "разрабатываю свою init-систему"?
aleksey_c
30.09.2019 11:40Если контейнеры надо запускать при загрузке системы, то можно просто вставить соответствующие команды Podman в юнит-файл systemd, тот запустит сервис и будет его мониторить. Podman использует стандартную модель ветвления при исполнении (fork-exec). Иначе говоря, контейнерные процессы являются дочерними по отношению к процессу Podman’а, поэтому systemd легко может их мониторить.
Docker использует модель клиент-сервер, и CLI-команды Docker тоже можно размещать прямо в юнит-файле. Однако после того, как Docker-клиент подключается к Docker-демону, он (клиент) становится просто еще одним процессом, обрабатывающим stdin и stdout. В свою очередь, systemd понятия не имеет о связи между Docker-клиентом и контейнером, который работает под управлением Docker-демона, и поэтому в рамках этой модели systemd принципиально не может мониторить сервис.
Каким образом нужно запускать Podman контейнер, чтобы его процессы были видны в systemctl status?
Например, предложенный в официальной документации способ запуска приводит к тому, что systemd видит только сам процесс, запустивший контейнер, а именно "/usr/bin/podman start -a NAME". Точно так же ведёт себя Docker, если его запустить с ключом "-a". Как же systemd может мониторить процессы контейнера, если не видит их?gecube
30.09.2019 11:59а зачем? Я проверил — systemd не мониторит, мониторит conmon
root 26061 4.6 0.3 1418228 54512 ? Ssl 11:58 0:00 /usr/bin/podman start -a redis_server root 26140 0.0 0.0 78012 1892 ? Ssl 11:58 0:00 /usr/bin/conmon --api-version 1 -s -c 6e88c46f8c452e058c1bcfda8a77a9e1c36243d24a3149f16e5db1a7cba2f2a8 -u 6e88c46f8c452e058c1b 999 26153 6.6 0.0 40700 4552 ? Ssl 11:58 0:00 \_ redis-server *:6379
проблема когда ты пытаешься одновременно управлять И системд, И подманом — там реально чего-то не то происходит.
Разница с докером именно в том, что докер стартанул, плюнул запрос в АПИ и клиент умер. Тут же подман бежит ровно столько, сколько нужно — пока сервис живет.aleksey_c
30.09.2019 12:19В статье написано, что systemd может мониторить процессы контейнера. Мой вопрос, не о том, зачем это в принципе нужно, а о том, как именно этого добиться.
Если под мониторингом подразумевается, что systemd видит процесс «podman start -a», который запускает контейнер, то это ничем не отличается от docker, процесс которого не завершается и systemd его видит, если запустить «docker start -a».gecube
30.09.2019 13:23Не совсем понятно какую задачу хотите решить.
Ведь нужно не просто мониторить дерево процессов (хотя conmon с этим худо-бедно вроде справляется), а еще смотреть на хелсчеки контейнера — и как они интегрируются с systemd — открытый вопрос (пока не проверял, но есть подозрение, что пока никак).
Про forking — вижу, сами догадались.aleksey_c
30.09.2019 13:41+1Задача — выяснить технические детали реализации в Podman. Более развёрнуто ответил ниже.
vrutkovs
30.09.2019 12:37+1systemd видит только сам процесс, запустивший контейнер, а именно "/usr/bin/podman start -a NAME". Точно так же ведёт себя Docker, если его запустить с ключом "-a".
На первый взгляд — да, но самим контейнером управляет докер-демон — настраивает ему cgroup и другое.
docker start -a
здесь только команда демону запустить контейнер.
В случае podman будет создан новый slice, который непосредственно управляет контейнером. Если его остановить то остановится сам контейнер.
aleksey_c
30.09.2019 12:49+1Возможно, нашел правильный ответ в документации github.com/containers/libpod/blob/master/docs/podman-generate-systemd.1.md
Вся магия в Type=forking и PIDFile=xxx.
[Service]
...
ExecStart=/usr/bin/podman start de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6
Type=forking
PIDFile=/run/user/1000/overlay-containers/de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6/userdata/conmon.pid
...
В данном примере podman запускается без параметра "-a", то есть сам процесс, запустивший контейнер, завершается и systemd видит дерево процессов самого контейнера.
К сожалению, проверить не удалось, так как текущая версия conmon из CentOS 7.7 не создаёт PID файл. Но в любом случае, теперь картинка сложилась.
Кстати, для docker есть wrapper, который позволяет добиться того же результата, но другим способом, через манипуляции с cgroups контейнера github.com/ibuildthecloud/systemd-dockermayorovp
30.09.2019 13:08+1Тут дело даже не в pid-файлах, а в дереве процессов. В случае podman systemd всегда знает запущен контейнер или остановлен. В случае docker systemd знает только запущен или остановлен клиент, а про контейнер не знает ничего.
aleksey_c
30.09.2019 13:34+1Только что проверил. Если запускать podman systemd unit через "/usr/bin/podman start -a NAME" и docker systemd unit через "/usr/bin/docker start -a NAME", то в результате будет одинаковое поведение systemd сервиса.
Да, «под капотом» Podman и Docker контейнеры запустятся совершенно по разному, но с точки зрения systemd, сервис — это один процесс, который подключен к STDIN, STDOUT и STDERR дескрипторам процесса контейнера. Если podman или docker контейнер завершается с ошибкой, то systemd сервис так же завершится с ошибкой. Если podman или docker контейнер завершается успешно, то systemd сервис так же завершится успешно. В этом смысле поведение systemd сервиса одинаковое.
При использовании github.com/containers/libpod/blob/master/docs/podman-generate-systemd.1.md в случае Podman или github.com/ibuildthecloud/systemd-docker в случае Docker, systemd сервисом будет не один процесс, инициировавший запуск контейнера, а всё дерево процессов самого контейнера.
Для чего это нужно — вопрос отдельный. Мне были интересны технические детали реализации в Podman. Как мы выяснили, там это реализовано через создание процессом conmon PID файла, из которого systemd сам узнаёт PID контейнера и дерево процессов контейнера.mayorovp
30.09.2019 13:51+1А в каких именно сценариях поведение оказалось одинаковое? Меня вот интересуют вот эти:
Запустите сервис-контейнер, прибейте демон докера (dockerd кажется, но могу ошибаться), после чего попробуйте определить статус сервиса-контейнера через sysctl.
Запустите сервис-контейнер, прибейте демон докера, после чего остановите сервис-контейнер через sysctl. Когда демон докера запустится снова, в каком состоянии сервис-контейнер окажется?
Просто прибейте процесс docker и дождитесь пока systemd попытается его перезапустить.
А если во всех этих случаях поведение окажется одинаковым — предлагаю рассмотреть как будет работать зависимость сервисов друг от друга при падении демона докера, во всех вариантах (контейнер зависит от контейнера, контейнер зависит от простого демона, простой демон зависит от контейнера).
gecube
30.09.2019 13:53Запустите сервис-контейнер, прибейте демон докера (dockerd кажется, но могу ошибаться), после чего попробуйте определить статус сервиса-контейнера через sysctl.
Можете включить опцию в конфиге демона, которая не убивает контейнеры при смерти docker демона. Но опция достаточно стремная.
--live-restore Enable live restore of docker when containers are still running
https://docs.docker.com/engine/reference/commandline/dockerd/
https://docs.docker.com/config/containers/live-restore/
или я неправильно понял ее?
sysctl
небольшое замечание. Не sysctl, а systemctl
предлагаю рассмотреть как будет работать зависимость сервисов друг от друга при падении демона докера, во всех вариантах
Никак. Потому что ее на уровне докер-демона нет. Он стартует все пачкой, если стоит
restart: always
илиunless-stopped
mayorovp
30.09.2019 14:02+1Можете включить опцию в конфиге демона, которая не убивает контейнеры при смерти docker демона. Но опция достаточно стремная.
Да нет, без неё поведение docker как раз больше похоже на поведение podman в видимой systemd части.
Никак. Потому что ее на уровне докер-демона нет. Он стартует все пачкой, если стоит restart: always или unless-stopped
Она есть на уровне systemd, и отслеживать зависимости — его прямая обязанность. Вот как раз использовать restart: always или unless-stopped в таких сценариях нельзя никак.
gecube
30.09.2019 14:08systemd в состоянии отслеживать, что контейнер готов, а не просто запущен, но без танцов с бубном?
Она есть на уровне systemd, и отслеживать зависимости — его прямая обязанность. Вот как раз использовать restart: always или unless-stopped в таких сценариях нельзя никак.
да, я просто уточняю.
Пока самый внятный способ интеграции docker & systemd — это либо через systemd-docker, либо ручками, но с
run --rm
, чтобы в случае сбоя контейнер удалился (может быть опасно для данных, но главное компоуз в системди не пихать).
gecube
30.09.2019 14:01На opensuse создает
linux-x1:~ # ls /var/run/containers/storage/btrfs-containers/ 6e88c46f8c452e058c1bcfda8a77a9e1c36243d24a3149f16e5db1a7cba2f2a8 b2c87cf4ebb2a2e09ea6de6487360598da76f85f82a61d5f2c497733b6784d0b
linux-x1:~ # zypper if conmon Retrieving repository 'openSUSE-Tumbleweed-Source' metadata .............................................................................................................................[done] Building repository 'openSUSE-Tumbleweed-Source' cache ..................................................................................................................................[done] Loading repository data... Reading installed packages... Information for package conmon: ------------------------------- Repository : openSUSE-Tumbleweed-Oss Name : conmon Version : 2.0.1-1.1 Arch : x86_64 Vendor : openSUSE Installed Size : 72.6 KiB Installed : Yes (automatically) Status : up-to-date Source package : conmon-2.0.1-1.1.src Summary : An OCI container runtime monitor Description : Conmon is a monitoring program and communication tool between a container manager (like podman or CRI-O) and an OCI runtime (like runc or crun) for a single container.
chemtech
А когда мы сможем отказать от docker в kubernetes и заменить его на podman?
vrutkovs
Краткий ответ — никогда
Более развернутый ответ — для того чтобы использовать программу как container engine в kubernetes эта программа должна иметь отдельный сокет для работы по интерфейсу CRI. Чтобы управлять этим сокетом программа должна быть запущена как демон. Потому придется превратить podman обратно в демон, как docker.
Потому существует строгое разделение — podman для работы на пользовательских машинах (где демон только мешает и в большинстве случаев требует привелегий) и CRI-O — легковесный демон для работы с Kubernetes и специально заточенный под него. В итоге обе программы используют общий набор библиотек (libpod, containers/images) для работы.
P6i
Ну так через libpod и cri-o можно запускать поды на базе podman
vrutkovs
Пока нет, они используют общий список имаджей, но фактически информацию о контейнерах хранят в разных местах (планируется объединить)