Стартиране на systemd в контейнер

От няколко години следим темата за използването на systemd в контейнери. Още през 2014 година нашият инженер по безопасност Даниел Уолш написа статия Стартиране на systemd в Docker контейнер, а след няколко години – друга, озаглавена Стартиране на systemd в непривилегиран контейнер, в която той констатира, че ситуацията не се е подобрила особено. В частност, той пише, че "за съжаление, и две години по-късно, ако потърсите 'Docker system', ще излезе все същата стара статия. Значи, време е за промяна." Освен това, вече веднъж разказвахме за конфликтите между разработчиците на Docker и systemd.

Стартиране на systemd в контейнер

В тази статия ще покажем какво се е променило през последното време и как Podman може да ни помогне в този въпрос.

Има много причини да стартирате systemd в контейнер, като например:

  1. Мултисервизни контейнери – много хора искат да извадят мултисервизните си приложения от виртуални машини и да ги стартират в контейнери. По-добре би било, разбира се, да се разделят такива приложения на микросервизи, но не всички все още умеят това или просто нямат време. Затова стартирането на такива приложения като услуги, стартирани от systemd от юнит файлове, има смисъл.
  2. Юнит файлове на Systemd – повечето приложения, работещи в контейнери, са изградени от код, който преди това е работил на виртуални или физически машини. Тези приложения имат юнит файл, който е написан за тях и разбира как да ги стартира. Така че, услугите все пак е по-добре да се стартират с помощта на поддържани методи, а не да се хаква собствената им init услуга.
  3. 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  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, която ще генерира unit файл на systemd за управление на конкретно зададен контейнер. Това трябва да работи както в root, така и в rootless режими за непривилегировани контейнери. Дори сме видели запитване за създаване на OCI-съвместима среда за изпълнение на systemd-nspawn.

Заключение

Стартирането на systemd в контейнер е напълно разбираема нужда. И благодарение на Podman накрая имаме среда за стартиране на контейнери, която не е в конфликт с systemd и позволява лесното му използване.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster