Хабр, привет!

С мая 2025 года стала доступна новая версия Red Hat Enterprise Linux — RHEL 10. Одним из её главных новинок стала поддержка bootc-контейнеров — загружаемых контейнеров, которые можно запускать не только в привычном виде qcow, vmdk и raw-образов виртуальных машин, но и прямо на любимом Bare Metal.

Некоторые аналитики считают технологию крайне перспективной. В нашей практике мы с ней не сталкивались, но это только пока.  Для нас это отличный повод посмотреть на нее поближе.

Спойлер: bootc чем-то напоминает CoreOS и то, как Machine Config из OpenShift взаимодействует с ним. Но bootc применяется уже в отрыве от «родительского» окружения в «реальном мире». 

План такой:

  1. Предисловие – несколько слов о bootc

  2. Собираем лабу для теста (Что нам потребуется?)

  3. Тестируем

  4. Пища для размышлений

Предисловие

Сам проект bootс существует два года, тем не менее мы уверены, что он заставит нас взглянуть на старые добрые контейнеры и их использование по-другому, о чем расскажем ниже. На github-страничке проекта описание подразумевает, что у вас уже есть бэкграунд в этой области:

The original Docker container model of using "layers" to model applications has been extremely successful. This project aims to apply the same technique for bootable host systems - using standard OCI/Docker containers as a transport and delivery format for base operating system updates.

The container image includes a Linux kernel (in e.g. /usr/lib/modules), which is used to boot. At runtime on a target system, the base userspace is not itself running in a "container" by default. For example, assuming systemd is in use, systemd acts as pid1 as usual - there's no "outer" process

 Если же перевести это все на простой язык, то bootc позволяет создавать загружаемые контейнеры, помещая ядро Linux внутрь:

При этом получившийся образ  может быть сконвертирован как диск для виртуальной машины или образ для установки на BareMatel. Также bootc управляет жизненным циклом развернутого из нашего образа сервера и может обновлять его или откатывать назад используемую версию. В данном обзоре мы посмотрим на то, как выполняется установка и обновление.

Схема работы с bootc-контейнерами выглядит следующим образом:

Лирическое отступление: За идеей bootc, на наш взгляд, стоит желание управлять сервером (доставка обновлений и установленные пакеты) как Infrastructure as Code (IaC). Еще немного приготовлений с настройкой Matchbox — и мы получим среду, где BareMetal-серверы развертываются в большей степени автоматически, доcтавка обновлений происходит через обновление самого контейнера, не затрагивая данные приложения. Тем не менее, при таком сценарии у нас остаются приятные дополнения в виде слоеной (layered) файловой системы, однородности устанавливаемых серверов и так далее.

Похожие идеи применяются в Flatcar (CoreOS) и Talos – в них не используются контейнеры для доставки, но есть попытка максимально абстрагироваться от администрирования ОС и заточить ее под выполнение одной функции. Такой подход помогает переосмыслить роль ОС и способы взаимодействия с ней.

И мы попробуем воссоздать данную инфраструктуру из говна и веток с минимальными затратами.

Что нам потребуется

  1. Инсталляционный (Kickstart) сервер. Мы не будем настраивать PXE и полную автоматизацию, ограничимся раздачей профилей через Apache плюс установим podman для сборки образа. Разумеется, можно настроить Matchbox, но это усложнит обзор.

  2. Registry для хранения нашего собранного образа. В нашем случае он будет установлен как docker/podman-контейнер на инсталляционном сервере.

  3. Образ (image) для создания bootc-контейнера.

  4. Профиль для Kickstart.

  5. Клиентский сервер (в моем случае это просто виртуальная машина VMWare).

Инсталляционный сервер

В качестве инсталляционного сервера используем RHEL10. Начнем с подготовки.

a)      Устанавливаем на него Apache:

# dnf install apache -y

# systemctl enable httpd

# systemctl start httpd

b)      Разрешаем в firewall доступ к httpd:

# firewall-cmd --permanent --add-service=http --add-service=https

# firewall-cmd --reload

c)      Устанавливаем podman:

# dnf install podman -y

Registry

Теперь подготовим контейнер с registry.

Логинимся в docker.io и получаем образ registry:

# podman login docker.io

# podman pull registry:latest

# podman image ls | grep registry

Теперь запускаем контейнер с registry:

# podman run -p 5000:5000 -d docker.io/library/registry

# firewall-cmd --permanent --add-port=5000/tcp

# firewall-cmd --reload

Проверяем, что registry доступен с других хостов:

Сборка образа (image) контейнера

Теперь нам нужно собрать образ контейнера, который будет использоваться при инсталляции. Делается это старым добрым методом через docker-файл. Например:

FROM registry.redhat.io/rhel9/rhel-bootc:9.6-1747275992

#Устанавливаем компоненты нашего Web-сервера

RUN dnf module enable -y php:8.2 nginx:1.22 && dnf install -y httpd mariadb mariadb-server php-fpm php-mysqlnd && dnf clean all

#Запускаем сервисы автоматически при загрузке

RUN systemctl enable httpd mariadb php-fpm

#Создаем домашнюю страницу

RUN echo 'Welcome to RHEL' >> /var/www/html/index.html

У нас есть выбор контейнеров для использования. К примеру, по запросу rhel-bootc в catalog.redhat.com находятся образы как с RHEL9, так и с RHEL10.

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

Вывод получился какой-то неполный. Пробуем по-другому:

Так-то лучше!

Я для своего docker-файла выбрал RHEL9.6. О чем говорит строка:

FROM registry.redhat.io/rhel9/rhel-bootc:9.6-1747275992

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

Проверяем, что образ появился в списке. Обратите внимание, какой тяжелый получился образ:

# podman image ls

Отправляем образ в registry, чтобы в будущем выполнить из него инсталляцию.

# podman push 10.31.135.242:5000/lamp-bootc:9.6-1747275992

Kickstart-профиль

В рамках маленькой обзорной статьи приведем свой конфигурационный файл с небольшим комментарием. Помните, что всегда есть ассистент на странице RedHat: https://access.redhat.com/labs/kickstartconfig/

Скрытый текст
# cat rhel9-image.cfg

text

network --bootproto=dhcp --device=link --activate

# Basic partitioning

clearpart --all --initlabel --disklabel=gpt

reqpart --add-boot

part / --grow --fstype xfs

# Here's where we reference the container image to install - notice the kickstart

# Здесь нет секции %packages

ostreecontainer --url 10.31.135.242:5000/lamp-bootc:9.6-1747275992

firewall --disabled

services --enabled=sshd

%pre

cat <<EOF > /etc/containers/registries.conf.d/registry-lab.conf

[[registry]]

location = "10.31.135.242"

insecure = true

blocked = false

EOF

%end

# optionally add a user

user --name=mike --groups=wheel --plaintext --password=q1q1q1

# if desired, inject a SSH key for root

rootpw --iscrypted locked

reboot

Здесь нас интересует строка, где мы указываем расположение имиджа:

ostreecontainer --url 10.31.135.242:5000/lamp-bootc:9.6-1747275992

и секция:

%pre
cat <<EOF > /etc/containers/registries.conf.d/registry-lab.conf
[[registry]]
location = "10.31.135.242"
insecure = true
blocked = false
EOF
%end

Установка

Установка выполняется классическим способом.

  • Загружаемся с образа RHEL10.

  • При появлении меню выбираем установку и нажимаем «e».

  • Редактируем строку linux, добавляя inst.ks=http://<ваш сервер>/<профиль>

Процесс пошел:

Установка завершена. Теперь мы можем проверить, какой образ был для нее использован:

[root@localhost ~]# bootc status

● Booted image: 10.31.135.242:5000/lamp-bootc:9.6-1747275992

        Digest: sha256:4d676117deee8cecef57f93ff7005e89d5e56a408b9fffdf95f359809734e2ae

       Version: 9.6 (2025-08-03 13:41:59.522050324 UTC)

Файл-приветcтвие на месте:

[root@localhost ~]# curl http://localhost:80

Welcome to RHEL

Перейдем к обновлению нашей системы. Подготовим docker-файл для образа: берем за основу наш старый файл и вносим в него изменения:

FROM registry.redhat.io/rhel9/rhel-bootc:9.6-1747275992

#install the lamp components
RUN dnf module enable -y php:8.2 nginx:1.22 && dnf install -y httpd mariadb mariadb-server php-fpm php-mysqlnd telnet&& dnf clean all

#start the services automatically on boot
RUN systemctl enable httpd mariadb php-fpm

#create an awe inspiring home page!
RUN echo 'Welcome to RHEL'  >> /var/www/html/index.html

Вывод следующей команды очень длинный, поэтому пропустим его для простоты:

# podman build -f /root/docker/rhel-bootc -t 10.31.135.242:5000/lamp-bootc: 9.6-1747275992

Копируем его в registry:

# podman push 10.31.135.242:5000/lamp-bootc:9.6-1747275992

Внутри нашей виртуальной машины создаем конфигурационный файл:

# cat 1-my.conf 
[[registry]]
location = "10.31.135.242"
insecure = true
blocked = false

Перед обновлением создаем тестовый файл и проверяем, что данные в контейнере сохраняются:

[root@localhost ~]# touch /root/TEST

Выполняем проверку наличия обновления:

# bootc upgrade --check
Update available for: docker://10.31.135.242:5000/lamp-bootc:9.6-1747275992
  Version: 9.6
  Digest: sha256:0a3cf8823bac6be7e8fd82ebf63d4c03061390a88d657b6cb602d7180e2f09e5
Total new layers: 70    Size: 1.1 GB
Removed layers:   1     Size: 278 bytes
Added layers:     2     Size: 448 bytes  Deploying: done (5 seconds)                                                                                                                                                              Queued for next boot: 10.31.135.242:5000/lamp-bootc:9.6-1747275992
  Version: 9.6
  Digest: sha256:c79144af6c55371c2a8daebf14d0acf0c6263e61175fca6f815fba9fbbfff4b6
Total new layers: 70    Size: 1.1 GB
Removed layers:   1     Size: 278 bytes
Added layers:     2     Size: 445 bytes

Обновляем:

# bootc upgrade 
layers already present: 69; layers needed: 1 (232 bytes)
Fetched layers: 232 B in 1 second (308 B/s)                                                                                                                                                
  Deploying: done (5 seconds)                                                                                                                                                              Queued for next boot: 10.31.135.242:5000/lamp-bootc:9.6-1747275992
  Version: 9.6
  Digest: sha256:0a3cf8823bac6be7e8fd82ebf63d4c03061390a88d657b6cb602d7180e2f09e5
Total new layers: 70    Size: 1.1 GB
Removed layers:   1     Size: 278 bytes
Added layers:     2     Size: 448 bytes

И теперь перезагружаемся. После завершения загрузки проверяем, что используется новый образ:

# bootc status
● Booted image: 10.31.135.242:5000/lamp-bootc:9.6-1747275992
        Digest: sha256:0a3cf8823bac6be7e8fd82ebf63d4c03061390a88d657b6cb602d7180e2f09e5
       Version: 9.6 (2025-08-03 15:00:15.652220473 UTC)

  Rollback image: 10.31.135.242:5000/lamp-bootc:9.6-1747275992
          Digest: sha256:4d676117deee8cecef57f93ff7005e89d5e56a408b9fffdf95f359809734e2ae
         Version: 9.6 (2025-08-03 13:41:59.522050324 UTC)

А на месте ли наш тестовый файл?

# ls -la /root/
total 20
drwx------.  3 root root  125 Aug  3 10:50 .
drwxr-xr-x. 25 root root 4096 Aug  4 05:16 ..
-rw-------.  1 root root  853 Aug  4 05:16 .bash_history
-rw-------.  1 root root   20 Aug  3 10:12 .lesshst
drwx------.  2 root root    6 Aug  3 10:05 .ssh
-rw-r--r--.  1 root root    0 Aug  3 10:23 TEST
-rw-------.  1 root root 1214 Aug  3 10:05 anaconda-ks.cfg
-rw-------.  1 root root  749 Aug  3 10:05 original-ks.cfg

Все ОК.

Выводы:

  1. С bootc контейнеры заметно «потяжелели»

  2. Метод управления обновлениям сервера (даже bare metal) изменился. Нужно время чтобы сказать стало ли лучше и насколько

  3. Composefs на физическом сервере приносит свои плюсы

  4. Граница между контейнерами и физическими серверами размывается

  5. Решение проблем, возникающих при работе с аппаратной частью, станет еще интереснее

Получив общее представление о bootc, дальше можно углубиться в документацию:

https://bootc-dev.github.io/bootc/bootc-images.html

https://docs.fedoraproject.org/en-US/bootc/community/

Скажем по секрету: углубляться есть куда — внутри интереснее, чем снаружи:

[root@localhost ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        4.0M     0  4.0M   0% /dev
tmpfs           7.7G     0  7.7G   0% /dev/shm
tmpfs           3.1G  8.7M  3.1G   1% /run
/dev/sda3        19G  2.6G   17G  14% /sysroot
composefs       6.9M  6.9M     0 100% /
tmpfs           7.7G     0  7.7G   0% /tmp
/dev/sda2       960M  192M  769M  20% /boot
tmpfs           1.6G     0  1.6G   0% /run/user/1000

Но об этом — в следующих сериях?

Комментарии (2)


  1. dekanovich
    21.10.2025 20:07

    Ммммм, пятый пункт радует. У меня всегда будет работа -- сношания с ОС будет еще больше :)))


    1. MikeGavrilov
      21.10.2025 20:07

      Процесс становится более изощренным, но суть процесса не меняется :)