Запускаємо systemd у контейнері

Ми давно стежимо за темою використання systemd у контейнерах. Ще у 2014 році наш інженер з безпеки Деніел Уолш (Daniel Walsh) написав статтю Running systemd within Docker Container, а ще через пару років – іншу, яка називалася Running systemd in non-privileged container, В якій він констатував, що ситуація не дуже й покращилася. Зокрема, він писав, що «на жаль, і через два роки, якщо погуглити «Docker system», то насамперед спливає все та ж його стара стаття. Значить, настав час щось міняти». Крім того, ми вже якось розповідали про конфлікт між розробниками Docker і systemd.

Запускаємо systemd у контейнері

У цій статті ми покажемо, що змінилося за минулий час і як нам може допомогти Podman.

Є багато причин для того, щоб запускати systemd всередині контейнера, такі як:

  1. Мультисервісні контейнери - багато хто хоче витягнути свої мультисервісні програми з віртуальних машин і запускати їх у контейнерах. Краще б, звичайно, розбити такі програми на мікросервіси, але не всі поки що це вміють або просто немає часу. Тому запуск таких додатків у вигляді сервісів, що запускаються systemd з юніт-файлів, цілком має сенс.
  2. Юніт-файли Systemd – більшість програм, що працюють усередині контейнерів, зібрані з коду, який раніше запускався на віртуальних або фізичних машинах. Ці програми мають юніт-файл, який писався під ці програми і розуміє, як їх треба запускати. Так що сервіси все ж таки краще запускати за допомогою підтримуваних методів, а не зламуючи свою власну init-службу.
  3. Systemd – це диспетчер процесів. Він здійснює управління сервісами (завершує роботу, перезапускає сервіси або викошує зомбі-процеси) краще ніж будь-який інший інструмент.

При цьому є багато причин для того, щоб не запускати systemd в контейнерах. Основна полягає в тому, що systemd/journald контролює виведення контейнерів, а інструменти начебто Кубернетес або OpenShift розраховують, що контейнери будуть писати лог безпосередньо в stdout та stderr. Тому, якщо ви збираєтеся керувати контейнерами через засоби оркестрації типу зазначених вище, треба серйозно обміркувати питання використання контейнерів на базі systemd. Крім того, розробники Docker та Moby часто були різко проти використання systemd у контейнерах.

Наступ Podman'а

З радістю повідомляємо, що ситуація нарешті зрушила з мертвої точки. Команда, яка відповідає у Red Hat за запуск контейнерів, вирішила розробити свій власний контейнерний двигун. Він отримав ім'я Подман і пропонує такий самий інтерфейс командного рядка (CLI) як у Docker'а. І практично всі команди Docker так само можна використовувати в Podman. Ми часто проводимо семінари, які тепер називаються Змінюємо Docker на Podman, і перший слайд закликає прописати: alias docker=podman.

Багато хто так і робить.

Ми зі своїм Podman'ом ні в якому разі не проти контейнерів на основі systemd. Адже Systemd найчастіше використовується як init-підсистеми Linux, і не давати їй нормально працювати в контейнерах означає ігнорувати те, як тисячі людей звикли запускати контейнери.

Podman знає, що треба робити, щоб системаd нормально працювала в контейнері. Їй потрібні такі речі, як монтування 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

Додати коментар або відгук