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

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

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

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

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

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

Заключение

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

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

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