Запуск rootless-контейнеров в Podman как systemd-юнит

В одной из прошлых статей мы рассказывали об альтернативе Docker – Podman. Одна из ключевых особенностей Podman – возможность запуска контейнеров, не требующих прав суперпользователя для своей работы, так называемых rootless-контейнеров. В этой статье мы рассмотрим, как запускать контейнеры и поды от имени пользователя, не имеющего root-права. В качестве хоста будет использоваться виртуальный сервер с операционной системой Ubuntu 24.04.

Когда стоит использовать rootless-контейнеры

Основная причина использовать контейнеры, не имеющие прав суперпользователя (rootless containers), – повышение уровня безопасности системы. Как и обычные пользователи, rootless-контейнеры не могут получить доступ к ресурсам, требующим root-права – если приложение в контейнере будет взломано, вредоносный процесс не получит привилегий в системе.

Запуск данных контейнера (podman container) будет невозможен в следующих ситуациях:

  • если контейнеру требуется записывать данные в файловую систему хоста (контейнер может записывать данные только туда, куда у него есть права как у обычного пользователя);
  • если контейнеру необходим для работы порт меньше, чем 1024.
Обратите внимание!
Ограничение на использование портов может быть снято путем внесения изменений в конфигурацию ядра, однако в этом случае все приложения, не обладающие привилегиями, смогут использовать порты меньше, чем 1024.

Установка Podman

Инструкция по установке Podman на популярные дистрибутивы доступна в нашей статье.

Проверьте, что Podman установлен в системе:

podman -v

Также необходимо установить специальную утилиту, обеспечивающую сетевой доступ для rootless-контейнеров. На данный момент инструмент Podman поддерживает две такие утилиты: slirp4netns и pasta. В нашем примере будем использовать slirp4netns.

Для установки на Ubuntu используйте следующую команду:

sudo apt install slirp4netns

Также установим пакет systemd-container, содержащий утилиту machinectl. Мы будем использовать ее для подключения к пользователям без пароля.

sudo apt install systemd-container

Теперь мы можем перейти к созданию непривилегированного пользователя и развертыванию контейнера.

Запуск rootless-контейнера

Создание пользователя

Создадим нового пользователя для запуска контейнера с веб-сервером Nginx. Для максимальной безопасности мы рекомендуем создавать нового пользователя для каждого контейнера или пода.

useradd -m nginx

Параметр -m создает домашнюю директорию для пользователя – она необходима для корректной работы контейнера.

Далее необходимо разрешить запуск сервисов пользователям, не вошедшим в систему:

sudo loginctl enable-linger nginx

Теперь подключитесь к пользователю:

sudo machinectl shell nginx@

Далее вы можете запустить контейнер при помощи podman run, однако мы рекомендуем запускать контейнер как сервис systemd.

Создание Quadlet

Чтобы запустить контейнер как systemd-сервис, мы будем использовать Quadlet. Данный механизм позволяет описывать контейнеры и поды в виде файлов .container и .pod, на основании которых генерируется systemd-юнит. Юниты systemd бывают различных типов, каждый из них выполняет определенную задачу, при этом имя каждого файла юнита включает соответствующее расширение (к примеру, nginx.service).

Такой подход позволяет разворачивать контейнеры без необходимости прописывать команду для запуска в директиву ExecStart.

Для rootless-контейнеров quadlet-файлы могут быть расположены в следующих директориях:

  • ~/.config/containers/systemd/
  • /etc/containers/systemd/users/ – путь для всех пользователей
  • /etc/containers/systemd/users/$UID – путь для пользователя с соответствующим UID. Посмотреть UID можно следующей командой: echo $UID

В нашем примере мы будем размещать quadlet-файлы в поддиректориях .config домашней директории, которые сперва необходимо создать и затем перейти в них:

mkdir -p ~/.config/containers/systemd && cd ~/.config/containers/systemd/

Теперь, пользуясь любым текстовым редактором, создайте и откройте файл nginx.container.

Для написания quadlet удобно использовать таблицу из официальной документации, в которой опции файла .container сопоставлены с опциями команды podman run

Вы также можете использовать утилиту podlet для генерации quadlet. Инструкции приведены в соответствующем разделе.

Quadlet для непривилегированного изображения nginx будет выглядеть следующим образом:

[Container]

ContainerName=nginx

Image=docker.io/nginxinc/nginx-unprivileged

PublishPort=8080:8080

Далее настроим параметр, предписывающий перезагружать сервис в случае его завершения:

[Service]

Restart=always

TimeoutStartSec=90

Параметр TimeoutStartSec задает максимальное время, по истечении которого systemd считает старт неудачным.

Если параметр Restart имеет значение always, в случае неудачного запуска сервис будет перезагружен.

Чтобы quadlet запускался автоматически при старте системы, добавим дополнительный раздел:

[Install]

WantedBy=default.target

Если этот раздел добавлен, включать сервис командой systemctl enable не требуется. Готовый файл будет выглядеть следующим образом:

[Container]

ContainerName=nginx

Image=docker.io/nginxinc/nginx-unprivileged

PublishPort=8080:8080

[Service]

Restart=always

TimeoutStartSec=90

[Install]

WantedBy=default.target

Теперь обновите системные демоны:

systemctl --user daemon-reload

И выполните запуск сервиса:

systemctl --user start nginx
Обратите внимание!
Так как на основе quadlet будет сформирован сервис с расширением .service, указывать расширение файла при запуске контейнера Podman не нужно.

Проверим, что сервис работает:

$ systemctl --user status nginx

● nginx.service

     Loaded: loaded (/home/nginx/.config/containers/systemd/nginx.container; generated)

     Active: active (running) 23min ago

Веб-сервер по порту 8080 также доступен:

nginx, podman

Запуск rootless-пода

Обратите внимание!
Podman позволяет запускать файлы формата .pod, начиная с версии 5.0.0. Проверить версию podman вы можете командой podman -v.

Попробуем запустить под путем создания юнита systemd на примере Jitsi Meet. Процесс аналогичен созданию quadlet для единичного контейнера, но появляется файл пода с расширением .pod.

Сперва сформируем quadlet для пода, указав в нем название пода, сетевые настройки для всех контейнеров (в самих контейнерах указывать порты для работы не потребуется).

jitsi.pod:

[Pod]

PodName=jitsi

Network=slirp4netns:port_handler=slirp4netns

PublishPort=5086:80

PublishPort=10000:10000/udp

PublishPort=5087:5280

PublishPort=50878:5280

AddHost=xmpp.meet.jitsi:127.0.0.1

AddHost=jvb.meet.jitsi:127.0.0.1

AddHost=meet.jitsi:127.0.0.1

Таблица директив для файла .pod также доступна в официальной документации.

Теперь создадим файл .container для каждого контейнера в поде.

jitsi-jicofo.container:

jitsi-jicofo.container:


[Container]

ContainerName=jitsi-jicofo

Pod=jitsi.pod

Image=docker.io/jitsi/jicofo:stable

AutoUpdate=registry

Volume=/home/jitsi/.config/containers/systemd/jicofo:/config:Z

EnvironmentFile=/home/jitsi/.config/containers/systemd/.env

[Service]

Restart=always

TimeoutStartSec=90

[Install]

WantedBy=default.target

jitsi-jvb.container:

[Container]

ContainerName=jitsi-jvb

Pod=jitsi.pod

Image=docker.io/jitsi/jvb:stable

AutoUpdate=registry

Volume=/home/jitsi/.config/containers/systemd/jvb:/config:Z

EnvironmentFile=/home/jitsi/.config/containers/systemd/.env

[Service]

Restart=always

TimeoutStartSec=90

[Install]

WantedBy=default.target

jitsi-prosody.container:

[Container]

ContainerName=jitsi-prosody

Pod=jitsi.pod

Image=docker.io/jitsi/prosody:stable

AutoUpdate=registry

Volume=/home/jitsi/.config/containers/systemd/config:/config:Z

Volume=/home/jitsi/.config/containers/systemd/prosody-plugins-custom:/prosody-plugins-custom:Z

EnvironmentFile=/home/jitsi/.config/containers/systemd/.env

[Service]

Restart=always

TimeoutStartSec=90

[Install]

WantedBy=default.target

jitsi-web.container:

[Container]

ContainerName=jitsi-web

Pod=jitsi.pod

Image=docker.io/jitsi/web:stable

AutoUpdate=registry

Volume=/home/jitsi/.config/containers/systemd/web:/config:Z

Volume=/home/jitsi/.config/containers/systemd/transcripts:/usr/share/jitsi-meet/transcripts:Z

EnvironmentFile=/home/jitsi/.config/containers/systemd/.env

[Service]

Restart=always

TimeoutStartSec=90

[Install]

WantedBy=default.target

Обновите системные демоны:

systemctl --user daemon-reload

Запустите под. Так как на основе quadlet-файла будет сформирован сервис с расширением .service, укажите имя пода без расширения:

systemctl --user start jitsi-pod

Проверим статус сервиса:

$ systemctl --user status jitsi-pod

● jitsi-pod.service

     Loaded: loaded (/home/jitsi/.config/containers/systemd/jitsi.pod; generated)

    Drop-In: /usr/lib/systemd/user/service.d

             └─10-timeout-abort.conf

     Active: active (running) since 2min 32s ago

Генерация Quadlet с помощью Podlet

Чтобы не переписывать compose в quadlet вручную, вы можете использовать утилиту podlet – она позволяет генерировать quadlet как из строки, так и из файлов compose. Рассмотрим варианты использования podlet на примере веб-сервера Caddy.

Установка podlet

Для установки podlet загрузите последний релиз для вашей платформы со страницы загрузок:

wget https://github.com/containers/podlet/releases/latest/download/podlet-x86_64-unknown-linux-gnu.tar.xz

Распакуйте загруженный архив и перейдите в директорию с файлами:

tar -xvf podlet-x86_64-unknown-linux-gnu.tar.xz && cd podlet-x86_64-unknown-linux-gnu

Дайте файлу права на исполнение и скопируйте в директорию /usr/bin:

sudo chmod +x podlet && sudo cp podlet /usr/bin/

Проверим работу утилиты:

$ podlet -V

podlet 0.3.0

Теперь можно вернуться назад и удалить загруженный архив и распакованную директорию:

cd .. && rm -rf podlet-x86_64-unknown-linux-gnu*

Выполните podlet --help, чтобы посмотреть полный список команд.

Генерация quadlet из compose

Множество контейнерных приложений разворачиваются с использованием docker compose – как правило, пример такого файла указан в официальной документации приложения.

Для примера сгенерируем quadlet из compose.yaml файла Caddy, приведенного в официальной документации:

name: caddy

services:

  caddy:

    image: docker.io/library/caddy:latest

    ports:

      - 8000:80

      - 8443:443

    volumes:

      - ./Caddyfile:/etc/caddy/Caddyfile:Z

      - caddy-data:/data

volumes:

  caddy-data:

podlet compose ищет в текущей директории файлы со следующими названиями:

  • compose.yaml
  • compose.yml
  • docker-compose.yaml
  • docker-compose.yml

В результате выполнения мы получим готовый quadlet:

$ podlet compose

# caddy.container

[Container]

Image=docker.io/library/caddy:latest

PublishPort=8000:80

PublishPort=8443:443

Volume=./Caddyfile:/etc/caddy/Caddyfile:Z

Volume=caddy-data:/data

Запишем его в файл caddy.container:

podlet compose > caddy.container

При добавлении параметра --pod будет сформирован pod-файл:

$ podlet compose --pod

# caddy-caddy.container

[Container]

Image=docker.io/library/caddy:latest

Pod=caddy.pod

Volume=./Caddyfile:/etc/caddy/Caddyfile:Z

Volume=caddy-data:/data

---

# caddy.pod

[Pod]

PublishPort=8000:80

PublishPort=8443:443

Вы также можете указать конкретный файл compose в команде:

$ podlet compose compose.yaml

Генерация quadlet через терминал

Для генерации quadlet из строки используйте следующую конструкцию:

$ podlet --file . --install --description Caddy \

  podman run \

  --restart always \

  -p 8000:80 \

  -p 8443:443 \

  -v ./Caddyfile:/etc/caddy/Caddyfile:Z \

  -v caddy_data:/data \

  docker.io/library/caddy:latest

В результате в текущей директории будет сформирован файл caddy.container:

Wrote to file: ./caddy.container

Обновление образа контейнера

Автоматическое обновление

Обратите внимание!
После обновления изображения контейнер будет перезапущен. Мы рекомендуем обновлять контейнеры Podman вручную – инструкции приведены в соответствующем разделе.

Для автоматического обновления запустите контейнер со следующей директивой:

AutoUpdate=registry

Затем активируйте сервис podman-auto-update для выбранного пользователя:

systemctl --user enable --now podman-auto-update.service

Сервис podman-auto-update.timer будет запускать этот сервис раз в сутки в полночь. Вы также можете запустить обновление вручную командой systemctl --user start podman-auto-update.service.

Ручное обновление

Перед началом обновления остановите контейнер:

systemctl --user stop nginx

Для просмотра используемого контейнерами образа можно применять следующую команду:

$ podman ps -a --format "{{.Image}}"

docker.io/nginxinc/nginx-unprivileged:latest

Выполните pull актуальной версии образа:

podman pull docker.io/nginxinc/nginx-unprivileged:latest

Чтобы выполнить pull образов для нескольких контейнеров, удобно использовать следующую команду:

podman ps -a --format "{{.Image}}" | sort -u | xargs -n1 podman pull

После загрузки образов перезапустите контейнер либо под и проверьте статус сервиса:

$ systemctl --user restart nginx
$ systemctl --user status nginx

Если возникнут вопросы, напишите нам, пожалуйста, тикет из панели управления аккаунта (раздел “Помощь и поддержка”), а если вы захотите обсудить эту статью, программирование или наши продукты с коллегами по цеху и сотрудниками облачной IT-платформы Beget – ждем вас в нашем сообществе в Telegram.

2
1039