На Хабре про OSTree обычно вспоминают не как про самостоятельную технологию, а как про "то, на чём держатся" Fedora CoreOS / Silverblue / Kinoite и вообще вся тема immutable / atomic desktop / container-optimized OS. Это видно по типовым материалам: обзор CoreOS с объяснением rpm-ostree и layering, обзор Silverblue, новости/разборы релизов Fedora, где rpm-ostree фигурирует как механизм поставки базовой системы. 

При этом намного хуже покрыта практическая сторона "как сделать свой цикл поставки": поднять свой OSTree-репозиторий, выпускать свои refs и раскатывать их на узлы так, чтобы можно было обновиться и откатиться.

Ещё одна причина, почему тема ощущается нами недораскрытой — постоянная путаница терминов. В текстах и обсуждениях часто смешиваются:

  • OSTree (хранилище/формат поставки коммитов дерева ОС),

  • rpm-ostree (инструмент сборки/обновления OSTree-системы из RPM + layering),

  • immutable как философия (/usr "не трогаем руками"),

  • "атомарные обновления" как пользовательское ощущение ("применилось целиком после перезагрузки — и можно откатить"). 

А ещё в комментах легко встретить полярные оценки: от "классная инженерия" до "катастрофа и головная боль" — обычно потому, что люди пытаются использовать image-based как обычный пакетный конструктор (или наоборот). 

Постараемся показать полный замкнутый цикл на примере НАЙС.ОС — не в теории, а на практике:
сервер → OSTree repo (refs+summary) → клиентский образ → проверка → обновление/rollback.
В НАЙС.ОС для этого уже есть специальные скрипты в пакетах rpm-ostree и rpm-ostree-host.


1. Короткий словарь: что есть что (и что с чем не путать)

OSTree — это "Git-подобная доставка дерева ОС"

OSTree хранит снимки файлового дерева (коммиты). Это не "набор пакетов", а именно результат: вот такое /usr, вот такие файлы, вот такая версия. Плюс метаданные и ссылки-ветки.

Три базовых термина:

  • commit — конкретная версия дерева (снимок состояния).

  • ref — "ветка/канал": имя, которое указывает на текущий commit (например niceos/5.2/x86_64/minimal).

  • remote — источник, откуда клиент подтягивает refs/commits (обычно HTTP(S)).

Почему сравнение с Git вообще уместно: вы не "чините систему пакетами по месту", вы переключаетесь между версиями дерева, как между коммитами/ветками.

rpm-ostree — это "как собрать и обслуживать OSTree-систему из RPM"

rpm-ostree — прослойка, которая:

  • берёт RPM-репозитории,

  • по декларации (treefile JSON) собирает из них дерево,

  • коммитит результат в OSTree repo,

  • а на клиенте умеет делать upgrade/rebase/rollback по этой модели.

Отдельный важный термин — client-side layering: когда вы “наслаиваете” дополнительные пакеты поверх базового образа, и это тоже становится новым коммитом/деплоем на клиенте. 

"Immutable" — это не религия, а договор

В большинстве rpm-ostree-подходов смысл примерно такой:

  • базовый слой (часто /usr) — не правим руками, он приходит как версия;

  • изменения и данные живут в местах, которые для этого предназначены (/etc/var);

  • системные изменения применяются транзакционно: подготовили новую версию → переключили → перезагрузили.

Если пытаться жить "как в обычном mutable Linux" (править /usr, ставить и удалять что попало на каждом сервере уникально) — будет боль.

2. Где тут "атомарность"

Слово "атомарно" в этой теме означает не "магия, которая никогда не ломается", а более приземлённое:

  1. Обновление собирается заранее (на сервере или на клиенте, в зависимости от сценария).

  2. На узле появляется новый deployment (новая версия рядом со старой).

  3. В момент применения вы не "заменяете файлы на лету", а переключаетесь на новую версию при перезагрузке.

  4. Если не взлетело — rollback: выбираете предыдущий deploy и опять перезагрузка.

Тест "атомарности/rollback" для ostree-режима:

  • посмотреть deployments,

  • сделать upgrade → перезагрузка,

  • сделать rollback → перезагрузка. 

То есть "атомарность" здесь — это договор: у тебя всегда есть известные состояния системы, и переход между ними контролируемый.


3. Чем это отличается от “обычных обновлений пакетами”

Классическая пакетная модель (dnf/apt/yum и т.д.)

  • У вас тысячи пакетов, их зависимости, скрипты пост-установки, конфиги, сервисы.

  • Два одинаковых сервера могут “разъехаться” по состоянию просто из-за порядка действий, разных репозиториев, ручных доустановок и т.д.

  • Откат часто превращается в отдельный проект: “а что мы поменяли, а какие конфиги трогали, а какой пакет по��янул другой пакет…”.

OSTree/rpm-ostree модель

  • Есть ref → ожидаемый результат. Это прям мощная штука для одинаковых узлов.

  • Обновление становится ближе к релизному процессу: “вот версия N, вот версия N+1”.

  • Возврат назад обычно проще концептуально: "верни предыдущий deploy".

Компромиссы

  1. Нужно принять дисциплину: база — как артефакт, а не как песочница.

  2. Не всё удобно делать "на каждом сервере руками": лучше выпускать новый ref или использовать управляемые механизмы (layering, контейнеры, конфиги в /etc, данные в /var).

  3. Желательно продумать профили (BIOS/UEFI, virtio/sata), иначе вы упрётесь в банальное "не совпало имя root-диска" и будете считать, что "OSTree это ад", хотя это просто инженерная гигиена.

4. Кому подходит (и кому нет)

Подходит почти идеально

  • Много одинаковых серверов/ВМ (десятки/сотни) — и вы хотите, чтобы “одинаковость” была не мечтой, а свойством процесса.

  • Edge / удалённые площадки, где вы цените предсказуемость и быстрый откат больше, чем свободу ручного тюнинга.

  • Инфраструктурные роли, где важна стабильная база: NAT/VPN/IDS/логирование/агенты наблюдаемости.

  • Контуры с аудитом: проще отвечать на вопрос "что именно развёрнуто на узле", когда это версия/ref, а не набор исторических событий.

Не подходит (или потребует аккуратной перестройки процессов)

  • Если у вас "каждый сервер уникален": уникальные ручные донастройки базовой системы, пакеты ставятся/сносятся хаотично.

  • Если вы ожидаете, что "обновление = поставили пару пакетов и всё", и при этом не хотите менять подход.

  • Если вы не готовы держать релизную дисциплину refs (stable/testing, кто и когда выпускает новые коммиты, как тестируем перед раскаткой).


5. Что будет дальше

В следующих частях посмотрим полный цикл на примере НАЙС.ОС:

  • Часть 2 (сервер): как поднять свой OSTree-репозиторий, собрать ref из treefile и опубликовать repo так, чтобы клиенты могли делать pull (скрипт mkostreerepo).

  • Часть 3 (клиент): как собрать загрузочный клиентский образ (RAW) с deploy нужного ref, проверить, обновиться и откатиться (скрипт mk-ostree-host.sh).

Часть 2. Сервер: поднимаем свой OSTree-репозиторий в НАЙС.ОС (и делаем его пригодным для клиентов)

В первой части мы договорились: OSTree/rpm-ostree — это не "обновления пакетами", а поставка версии системы как артефакта (commit/ref), с нормальным откатом. Теперь переходим к практическому "как сделать свой канал поставки", то есть сервер OSTree.

Сразу важный тезис: OSTree-сервер — это не обязательно "сложный сервис". В 90% случаев это просто каталог OSTree-репозитория, который вы публикуете по HTTP(S), и обновляете в нём summary.

1) Что считается “сервером OSTree” в реальности

У вас есть рабочая директория (назовём её REPOPATH), внутри которой:

  • REPOPATH/repo — данные OSTree-репозитория (это то, что будет видеть клиент),

  • REPOPATH/cache — кэш сборки (внутреннее, клиенту не нужно).

И главное правило публикации:

Клиентам отдаём только repo/cache/ не трогаем.


2) Инструмент НАЙС.ОС: mkostreerepo 

На серверной стороне в НАЙС.ОС задача "создать/обновить OSTree repo" решается скриптом:

/usr/bin/rpm-ostree-server/mkostreerepo

Поставляется пакетом:

# rpm-ostree-2025.10-1.niceosc5.x86_64

Смысл скрипта очень практичный:

  • гарантирует структуру repo/ + cache/,

  • готовит treefile (если не задан — умеет сгенерировать минимальный),

  • при необходимости генерирует .repo файлы для RPM-источников,

  • запускает rpm-ostree compose tree,

  • обновляет ostree summary.

3) Быстрый старт: репозиторий "с нуля" за одну команду

Выбираем место, где будет жить репозиторий. Пример:

sudo /usr/bin/rpm-ostree-server/mkostreerepo -r=/srv/ostree/niceos

Что произойдёт "под капотом" (важно понимать, чтобы потом диагностировать):

  • если /srv/ostree/niceos не существует — создаст;

  • создаст /srv/ostree/niceos/repo и /srv/ostree/niceos/cache;

  • если niceos-base.json отсутствует — сгенерирует минимальный treefile (niceos-base.json);

  • если не указан режим -c и .repo файлов нет — нагенерирует дефолтные niceos.repo / niceos-updates.repo / niceos-extras.repo в этот же каталог;

  • выполнит сборку и коммит в OSTree-репо;

  • обновит summary.

Что должно получиться в конце:

ls -la /srv/ostree/niceos
# repo/  cache/  niceos-base.json  niceos.repo  niceos-updates.repo  niceos-extras.repo ...

4) Нормальный прод-путь: ваш treefile JSON (управляемый состав системы)

Если вы хотите контролировать состав, ref и профиль системы — используйте свой treefile (JSON). Скрипт умеет его принять и скопировать внутрь каталога репо.

Пример:

sudo /usr/bin/rpm-ostree-server/mkostreerepo \
  -r=/srv/ostree/niceos \
  -p=/srv/ostree/treefiles/niceos-minimal.json

Практический смысл treefile (без философии):

  • вы фиксируете ref (имя канала),

  • фиксируете набор RPM-реп, из которых собирается дерево,

  • фиксируете packages/units.

Рекомендация из жизни: ref лучше сразу стандартизировать, например:

  • niceos/5.2/x86_64/minimal

  • niceos/5.2/x86_64/base

  • niceos/5.2/x86_64/ndr-sensor

И тогда у вас автоматически появляется дисциплина “каналы и профили”, а не “каша из артефактов”.


5) Режим -c/--customrepo: аккуратно, там интерактив (грабля №1 для CI)

Флаг -c означает: "репофайлы будут кастомные, дефолтные не генерировать".

Но в текущем поведении скрипта есть интерактивный вопрос (Y/N). В CI это прямой путь к зависанию пайплайна: сборка ждёт ввода.

Если вам нужен неинтерактивный выпуск, обычно делают так:

  • не используют -c, а контролируют .repo через окружение/шаблоны,

  • либо заранее кладут все нужные .repo файлы, и скрипт уже не пытается ничего создавать,

  • либо правят скрипт (вводят флаг “assume-yes/no” без read).


6) Проверяем репозиторий: refs, история и summary (грабля №2 — забыли summary)

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

1) refs существуют:

ostree refs --repo=/srv/ostree/niceos/repo

2) по нужному ref есть история:

ostree log --repo=/srv/ostree/niceos/repo niceos/5.2/$(uname -m)/minimal | head -n 80

3) summary актуален:

ostree summary -v --repo=/srv/ostree/niceos/repo

Почему summary важен: клиенты часто ориентируются на него при обнаружении refs и корректной работе с репозиторием. Если summary не обновлять — симптомы на клиенте будут выглядеть как "ref не виден / pull странный / кажется сломано".

Хорошая новость: mkostreerepo делает summary --update сам. Плохая новость: если вы потом руками что-то меняли и забыли обновить — клиент первым делом споткнётся именно здесь.


7) Публикация по HTTP(S): отдаём repo/, не отдаём cache/

Дальше вам нужно просто сделать repo/ доступным по сети.

Схема URL обычно такая:

  • сервер: /srv/ostree/niceos/repo

  • клиент видит: http://OSTREE_SERVER/ostree/niceos/repo

Минимальный пример для nginx (смысловой, без наворотов):

server {
  listen 80;
  server_name ostree.example.local;

  location /ostree/niceos/repo/ {
    alias /srv/ostree/niceos/repo/;
    autoindex off;
    # Важно: это статика. Никаких php/fastcgi.
  }
}

Проверка “жив ли сервер” с машины клиента:

curl -I "http://ostree.example.local/ostree/niceos/repo/" || true

8) Безопасность

  • RPM-репозитории: если у вас gpgcheck=1, убедитесь, что ключи на сервере сборки на месте, иначе compose будет падать “не из-за OSTree”, а из-за подписи пакетов.

  • OSTree remote: в лаборатории иногда отключают gpg-verify, но для взрослой поставки лучше иметь подпись и доверенные ключи (плюс HTTPS или закрытый контур).


Мини-чеклист "сервер готов"

  •  REPOPATH/repo существует и содержит объекты OSTree

  •  ostree refs показывает ваш ref

  •  ostree summary -v отрабатывает без ошибок

  •  repo/ доступен по HTTP(S) из сети клиентов

  •  сборка воспроизводима: treefile/.repo лежат и контролируются


В третьей части соберём клиентский загрузочный образ в НАЙС.ОС через mk-ostree-host.sh, подключим его к серверу, сделаем deploy нужного ref и покажем "проверка → обновление → откат" на практике (и разберём главную боль: root-dev и почему VM иногда не грузится).

Часть 3. Клиент: делаем загрузочный образ, проверяем deploy, обновляемся и откатываемся (на примере НАЙС.ОС)

Во второй части мы подняли серверную сторону: есть OSTree-репозиторий (repo/), есть ref, есть summary, и всё это публикуется по HTTP(S). Теперь превращаем это в реальную пользу: делаем клиентский образ, который загружается, знает свой ref, умеет обновляться и (главное) откатываться.

В НАЙС.ОС для этого уже есть готовый скрипт — mk-ostree-host.sh, который не просто делает deploy, а собирает полноценный дисковый RAW-образ с разметкой, /boot, GRUB2, kargs и fstab.


1) Инструмент НАЙС.ОС: mk-ostree-host.sh и откуда он берётся

Скрипт лежит здесь:

/usr/bin/rpm-ostree-host/mk-ostree-host.sh

Пакетная принадлежность такая:

rpm -qf /usr/bin/rpm-ostree-host/mk-ostree-host.sh
# rpm-ostree-host-2025.10-1.niceosc5.x86_64

2) Что именно делает скрипт (смысловой разбор, чтобы понимать грабли)

mk-ostree-host.sh делает весь цикл сборки образа:

  1. Создаёт файл-диск (IMG_NAME.raw) нужного размера (FILE_SIZE ГБ).

  2. Подключает его как loop-устройство (/dev/loopX).

  3. Делает GPT-разметку под BIOS-режим:

    • p1: bios_grub (тип ef02) — чтобы grub2-install мог жить на GPT в BIOS режиме

    • p2: /boot (ext4, ~300 MiB)

    • p3: / (ext4, всё остальное)

  4. Через kpartx создаёт /dev/mapper/…p2 и …p3.

  5. Форматирует p2/p3 в ext4.

  6. Монтирует p3 в MOUNT_POINT, p2 — в MOUNT_POINT/boot.

  7. Делает OSTree-часть:

    • init repo

    • init-fs sysroot

    • remote add http://IP_ADDR (важно: часто без gpg-verify в стендах)

    • pull нужного REPO_REF

    • ostree admin deploy

  8. Подмонтирует внутрь деплоя системные fs (/dev/proc/sys/run/bootsysroot), чтобы внутри chroot всё работало корректно.

  9. Ставит GRUB2 в образ, генерирует конфиги, настраивает kernel args.

  10. Записывает fstab и ставит временный пароль root (root:changeme).

  11. Аккуратно размонтирует и снимает loop/mapper (даже при ошибках — через trap cleanup).

Из этого важный вывод: скрипт создаёт не “файловую систему”, а полноценный загрузочный диск.


3) Параметры запуска, которые реально важны

Скрипт требует:

  • FILE_SIZE — размер диска (ГБ)

  • IMG_NAME — имя образа без расширения

  • IP_ADDR — адрес OSTree-сервера (куда клиент будет ходить за ref)

  • REPO_REF — ref, который вы хотите развернуть

  • MOUNT_POINT — временная точка монтирования при сборке

  • --root-dev — целевое устройство корня на той машине/VM, где образ будет грузиться

И вот тут начинается главная инженерная правда:

Грабля №1: --root-dev (sda3 vs vda3) — причина "не грузится" №1

Во время сборки корень выглядит как:

  • /dev/mapper/loop0p3 (или похожее)

Но после запуска VM это устройство исчезает. Внутри VM вы увидите:

  • /dev/sda3 (если диск подключили как SATA/SCSI),

  • /dev/vda3 (если virtio в KVM/QEMU),

  • и т.д.

Поэтому скрипт:

  • сначала выставляет root= на текущий mapper,

  • потом заменяет root= в загрузочных конфигурациях на --root-dev, чтобы при реальной загрузке ОС нашла корень.

Если --root-dev неверный — вы получите kernel panic “cannot mount root”.


4) Практика: собрать клиентский RAW-образ

4.1 Сценарий “BIOS + SATA/SCSI” (обычно /dev/sda3)

sudo /usr/bin/rpm-ostree-host/mk-ostree-host.sh \
  -s 20 \
  -n niceos-5.2-minimal \
  -i 10.0.0.10 \
  -r niceos/5.2/x86_64/minimal \
  -m /mnt/niceos-root \
  --root-dev /dev/sda3

4.2 Сценарий “BIOS + virtio” (обычно /dev/vda3)

sudo /usr/bin/rpm-ostree-host/mk-ostree-host.sh \
  -s 20 \
  -n niceos-5.2-virtio \
  -i 10.0.0.10 \
  -r niceos/5.2/x86_64/minimal \
  -m /mnt/niceos-root \
  --root-dev /dev/vda3

4.3 Где лог

sudo tail -n 200 "/var/log/mk-ostree-host.sh-$(date +%Y-%m-%d).log"

5) Как загрузить образ в виртуалке

Частая практика: конвертировать raw → qcow2, если вы на KVM/QEMU.

qemu-img convert -f raw -O qcow2 niceos-5.2-virtio.raw niceos-5.2-virtio.qcow2

И запустить (пример):

qemu-system-x86_64 \
  -m 2048 -smp 2 \
  -drive file=niceos-5.2-virtio.qcow2,if=virtio,format=qcow2 \
  -net nic -net user \
  -serial mon:stdio

Важно: если вы используете if=virtio, то root-диск станет /dev/vda, значит корень — /dev/vda3. Это должно совпасть с --root-dev.


6) Первая загрузка: контроль результата (5 минут, которые экономят часы)

После старта VM:

6.1 Сменить пароль root

Скрипт обычно ставит временный пароль (root:changeme). Меняем сразу:

passwd

6.2 Проверить deploy

ostree admin status

Смысл проверки:

  • вы видите список deployments,

  • какой активный,

  • какие ещё доступны (в том числе для rollback).

6.3 Проверить /boot и entries

mount | egrep ' on / | on /boot '
ls -la /boot/loader/entries/

6.4 Проверить fstab

cat /etc/fstab

Если там /dev/sda3, а у вас virtio (/dev/vda3) — это сигнал, что образ собран под другой профиль (или требует правки).


7) Обновление и откат (концепт + минимум действий)

Смысл rpm-ostree/ostree-подхода:

  • обновление готовит новый deploy,

  • переключение обычно происходит после перезагрузки,

  • откат — это переключение на предыдущий deploy (тоже обычно с перезагрузкой).

Практический ритуал проверки “оно действительно атомарное”:

  1. посмотреть текущий статус deployments

  2. обновиться

  3. перезагрузиться

  4. если плохо — rollback

  5. перезагрузиться

Да, звучит слишком просто — в этом и фишка: возврат — не ручной разбор пакетов.


8) Типовые ошибки на клиенте и как быстро понять причину

Ошибка A: "не грузится / cannot mount root"

  • почти всегда: неверный --root-dev (sda3 vs vda3)
    Решение: пересобрать образ с правильным --root-dev (или чинить loader/fstab вручную, но это уже аварийный ремонт).

Ошибка B: "ref не тянется"

  • нет доступа до сервера,

  • сервер не публикует repo/,

  • summary не обновлён,

  • ref указан неверно.
    Диагностика:

  • curl -I http://SERVER/.../repo/

  • на сервере ostree refsostree summary -v

Ошибка C: "UEFI, а образ BIOS"

Скрипт, ориентирован на BIOS GPT + bios_grub. Если VM/железо в UEFI-only — нужен отдельный профиль разметки с ESP (FAT32, ef00) и grub2-efi.


Мини-чеклист "клиент готов"

  •  Образ загрузился в нужной VM/на железе

  •  Пароль root сменён

  •  ostree admin status показывает ожидаемый deploy/ref

  •  /boot смонтирован, есть /boot/loader/entries/…

  •  fstab соответствует реальному устройству (sda vs vda)

  •  Вы понимаете и умеете сделать rollback (проверено на тесте)


На этом цикл закрыт: сервер выпускает ref → клиентский образ развёрнут из ref → у вас есть обновление и откат как управляемая операция.

Есть ещё один слой смысла, который для НАЙС.ОС — не "приятный бонус", а почти идеальная посадка этой модели на философию системы.

9) Почему OSTree/rpm-ostree особенно хорошо ложится на НАЙС.ОС

Во многих дистрибутивах OSTree выглядит как "надстройка поверх традиционного комбайна": огромная базовая ОС, на которую пытаются натянуть image-based обновления.

У НАЙС.ОС логика обратная и более современная:

  • база минималистична и существует как стабильный фундамент;

  • приложения подразумеваются в контейнерах (Docker/Kubernetes), а не как "зоопарк сервисов, установленных на хост руками".

обновляем хост как версию, приложения в контейнерах
обновляем хост как версию, приложения в контейнерах

И вот тут OSTree идеально совпадает с моделью эксплуатации:

хост = стабильная платформа,
приложения = контейнеры,
обновление хоста = версия/коммит,
откат хоста = переключение deploy,
а контейнерный стек живёт своей жизнью поверх предсказуемой базы.

Это не "комбайн системы из прошлого", где на хосте одновременно крутится всё подряд и обновления превращаются в квест. Это подход "хост как платформенный слой", который и предполагает: не шаманить руками на /usr, а держать базу контролируемой и одинаковой, а прикладной слой — переносимым и оркестрируемым.

10) Почему это снижает эксплуатационную боль именно в контейнерной инфраструктуре

Если вы реально эксплуатируете Docker/Kubernetes, у вас обычно болит не "как поставить 100 пакетов на хост", а:

  • чтобы хосты одного пула были одинаковыми;

  • чтобы обновления не ломали неожиданно сеть/iptables/драйверы/ядро;

  • чтобы можно было быстро откатить "плохой апдейт ноды" без ручной археологии.

OSTree-модель отвечает на это прямолинейно:

  • узлы пула сидят на одном ref → одинаковый базовый слой;

  • обновление готовится как новый deploy → не портит текущий на лету;

  • rollback возвращает предыдущий deploy → быстро возвращает ноду в рабочее состояние.

И да — НАЙС.ОС включена в реестр российского ПО, что в проектах импортозамещения часто упрощает формальную часть внедрения.