Введение
Если вы администрируете больше одного сервера, вы наверняка встречали
классическую драму: "обновили пакеты - и стало иначе".
Вроде бы обновление - рутинная операция, но на практике это часто лотерея из
мелких изменений: уехали зависимости, поменялись версии библиотек, подтянулись
новые настройки по умолчанию в фалах конфигурации, где-то пересобралось ядро, а
вишенкой сверху стало то, что два "одинаковых" узла после одинаковых команд
через пару недель оказываются уже не совсем одинаковыми.
Главная проблема не в том, что пакетные менеджеры плохие. Проблема в том, что
модель "обновление = набор независимых изменений" плохо сочетается с
требованиями инфраструктуры: предсказуемостью, быстрым восстановлением и
возможностью чётко ответить на вопрос: какая именно версия системы сейчас работает.
OSTree предлагает другой вариант: система поставляется как версия (коммит) и
переключается целиком. Не "вот вам 200 пакетов, удачи", а "вот вам собранное
состояние базовой системы, которое можно развернуть, загрузить и при
необходимости откатить".
Эта часть статьи - обзорная: без глубокой внутренней анатомии, но с теми
понятиями, которые реально нужны, чтобы решить, стоит ли вам это встраивать в
процесс. Практика на примере НАЙС.ОС будет во 2-3 части.
Что такое OSTree и в чём "атомарность"
OSTree - это механизм хранения и доставки "снимков" файловой системы (точнее,
базовой части системы) в виде коммитов. Коммит - это зафиксированное состояние
дерева файлов, которое можно получить из репозитория и развернуть на узле.
В OSTree есть несколько базовых сущностей, и их полезно выучить один раз:
Коммит - это версия системы, Ref - это "ветка/канал", которая указывает на
коммит. Например, ref может обозначать "НАЙС.ОС 5.2 minimal для x86_64". Remote
- это источник, откуда узел получает refs и коммиты (сервер/репозиторий,
опубликованный по сети). Администратор обычно работает не с "хешами коммитов",
а с ref. Ref - это человеческий контракт: стабильный идентификатор канала поставки.
Суть атомарности проявляется в терминах "deploy" и "rollback".
Deploy - это развёртывание выбранного коммита в sysroot так, чтобы система
могла загрузиться в этой версии. При deploy новая версия раскладывается как
отдельная, не разрушая текущую рабочую версию "в процессе".
Rollback - это откат на предыдущий deploy. Важно, что откат здесь - не
"откатываем 17 пакетов и надеемся", а переключаемся на ранее развёрнутую версию
системы. Поэтому rollback может быть быстрым и предсказуемым.
Отсюда и типичная фраза "две версии рядом": текущая рабочая и новая
подготовленная. Это нормальная и полезная конструкция: она позволяет
обновляться без превращения узла в полу-состояние "половина старого / половина нового".
В OSTree-модели существует sysroot - место, где живут:
* данные OSTree (репозиторий, метаданные),
* развёрнутые деплои,
* элементы, необходимые для загрузки.
На практике администратору достаточно понимать "где искать правду", когда что-то пошло не так:
* в sysroot живёт структура OSTree;
* в /boot/loader обычно находятся записи загрузки (entries), которые соответствуют деплоям.
Это не руководство по ремонту загрузчика - это ориентиры, чтобы не искать проблему "в воздухе".
OSTree отвечает за "версионирование и доставку состояния".
Но откуда берутся эти состояния (коммиты)?
Если базовый мир у вас RPM, то роль сборщика выполняет "rpm-ostree". Он берёт
декларацию состава системы (treefile, обычно JSON), тянет пакеты из указанных
репозиториев, собирает файловое дерево и коммитит результат в OSTree-репозиторий.
Treefile - это ключ к дисциплине: вы не обновляете систему "как получится", вы
выпускаете версию по описанию "как должно быть".
Чем OSTree отличается от "обычных обновлений"
Классическая модель: много пакетов, много зависимостей, много вариантов результата.
Обычный пакетный апдейт - это операция над множеством объектов. Даже если
команда одна, фактически вы получаете:
* разные комбинации зависимостей в зависимости от времени,
* разные обновления в разных репозиториях,
* разные настройки по умолчанию в конфигурации,
* и самое неприятное - разные результаты на узлах, где "состояние до
обновления" уже слегка различалось.
Из-за этого "обновление" перестаёт быть полностью повторяемой операцией.
В OSTree-модели вы говорите: "узел должен быть на ref X".
Ref X указывает на конкретный коммит. Коммит - это конкретное состояние.
Два узла, которые взяли один и тот же ref/коммит, получают одинаковую базу.
Это не означает, что у них будут одинаковые данные и конфиги приложения. Но
означает, что "скелет" ОС и системные компоненты соответствуют выпуску.
Подобный подход снижает "случайные поломки" так как обновление перестаёт быть
постепенным разрушением текущей установки.
Новая версия готовится отдельно, а переключение происходит на границе (обычно
на перезагрузке/переключении деплоя).
И главное: версия проверяется как единое целое на стороне сборки. Если у вас
нормальный процесс - вы тестируете выпуск (коммит) до того, как он пошёл в production.
OSTree - это не "поставил и забыл". Это другой стиль:
* Плюс: одинаковость и управляемость.
* Минус: вы не поощряете "а давай на этом сервере руками доустановим ещё 15
пакетов" как основной процесс.
Если вам нужен мир, где каждый сервер - снежинка, OSTree будет мешать.
Если вам нужен мир, где серверы - клоны с контролируемой базой, OSTree отлично ложится.
Кому OSTree подходит, а кому - нет
Подходит:
1. Много одинаковых серверов/ВМ
Когда вы хотите, чтобы "узлы одной роли" реально были одинаковыми, а не "примерно похожими".
2. Edge и удалённые площадки
Где откат должен быть быстрым и не требовать "разбора пакетов по косточкам".
3. Регламентированные контуры и аудит
Где нужно уметь показать, что конкретный узел работает на конкретной версии
системы, с подтверждаемым составом.
4. Инфраструктурные роли
NAT, VPN, IDS/сенсоры, логирование, базовые сервисы наблюдаемости - где важна
стабильная база и меньше сюрпризов от апдейтов.
Не подходит или требует аккуратности:
1. Постоянная уникальная ручная донастройка базовой системы на каждом узле
Если вы регулярно меняете именно "базу" руками (не конфиги, а состав системы),
вы будете воевать с моделью.
2. Ожидание "пакетного конструктора" без выпуска образа/коммита
OSTree раскрывается, когда есть выпуск (коммит) и канал (ref). Без дисциплины
выпусков вы теряете смысл.
3. Нет готовности вести refs и релизный процесс
Нужно определить, что такое stable/testing, как именуются refs, кто публикует, как тестируется.
4. Смешивание BIOS/UEFI и разных виртуальных контроллеров без профилей
OSTree не отменяет реальность загрузки: root-устройство, fstab, режим BIOS/UEFI
- всё это нужно учитывать. Если вы хотите "один образ на все случаи", придётся
делать профили сборки и правила.
Атомарные обновления в OSTree: какой подход лучше?
В НАЙС.ОС практический процесс обычно делится на две независимые роли:
1. Сервер OSTree - это машина, где:
* из RPM-репозиториев собирают дерево ОС по treefile (JSON),
* результат коммитят в OSTree-репозиторий,
* обновляют summary,
* публикуют каталог OSTree-репозитория по HTTP(S) для клиентов.
2. Клиент - это узел (или процесс сборки образа), который:
* добавляет remote на этот сервер,
* делает pull нужного ref,
* делает deploy,
* дальше загружается в выбранный deploy.
Для НАЙС.ОС у вас уже есть две "точки автоматизации", которые отлично ложатся на этот поток:
* mkostreerepo - поднимает/обновляет OSTree-репозиторий на сервере (rpm-ostree
compose tree + summary).
* mk-ostree-host.sh - готовит дисковый образ и выполняет deploy нужного ref (это будет в 3 части).
Эта часть статьи - про сервер: как создать репозиторий, как проверить, что он
корректный, и что именно публиковать клиентам.
Серверная часть: создание OSTree-репозитория в НАЙС.ОС
Требования и предпосылки
Серверу нужны:
* rpm-ostree и ostree (если rpm-ostree отсутствует, mkostreerepo пытается
поставить его через tdnf);
* доступ к RPM-репозиториям (откуда rpm-ostree будет собирать дерево);
* место на диске: репозиторий растёт, плюс есть кэш compose.
mkostreerepo организует рабочий каталог так:
* REPOPATH/repо - данные OSTree (это то, что публикуем);
* REPOPATH/cache - кэш сборки (это внутреннее, клиентам не нужно).
Три сценария использования mkostreerepo:
1. Быстрый старт "с нуля" (без своего JSON)
Если вы не указали JSON, скрипт проверит наличие REPOPATH/niceos-base.json.
Если файла нет - он сгенерирует минимальный treefile (niceos-base.json) и продолжит.
Пример:
sudo /usr/bin/rpm-ostree-server/mkostreerepo -r=/srv/ostree/niceos
Что произойдёт:
* создастся каталог /srv/ostree/niceos (если его нет);
* создадутся подкаталоги repo и cache;
* при необходимости будет создан niceos-base.json;
* будут сгенерированы дефолтные *.repo (если их нет и не включён custom режим);
* выполнится rpm-ostree compose tree, появится коммит и ref;
* обновится summary.
2. Управляемый вариант "своё дерево" (с treefile JSON)
Это нормальный путь для продакшена: вы фиксируете состав системы как декларацию.
Пример:
sudo /usr/bin/rpm-ostree-server/mkostreerepo \
-r=/srv/ostree/niceos \
-p=/srv/ostree/niceos/treefiles/niceos-minimal.json
Поведение скрипта:
* проверит, что JSON существует,
* скопирует его в REPOPATH (и будет использовать уже копию внутри каталога репо),
* продолжит сборку.
3. "Custom repo files" (-c) - осторожно, там интерактив
Флаг -c/--customrepo говорит "репофайлы будут кастомные, дефолтные не генерировать".
Но дальше в скрипте есть интерактивный вопрос (Y/N), который может повесить CI/автоматизацию.
Практический смысл:
* если вы хотите полностью контролировать *.repo и не допускать автогенерации - используйте -c,
* но учтите, что для неинтерактивной сборки нужно либо патчить этот участок,
либо заранее обеспечить все нужные *.repo и выбрать режим без вопросов.
Пример (который может уйти в интерактив):
sudo /usr/bin/rpm-ostree-server/mkostreerepo \
-c \
-r=/srv/ostree/niceos \
-p=/srv/ostree/niceos/niceos-base.json
В treefile (JSON) на уровне процесса важны три вещи:
1. ref - имя канала поставки. Это "публичная API-точка" для клиентов.
2. repos - какие RPM-репозитории используются для сборки.
3. packages/units - состав системы и важные сервисы.
Минимальный фрагмент логики (по смыслу):
{
"osname": "niceos",
"releasever": "5.2",
"ref": "niceos/5.2/x86_64/minimal",
"repos": ["niceos", "niceos-updates", "niceos-extras"],
"packages": ["systemd","openssh","rpm-ostree"],
"units": ["sshd.service"]
}
Замечание по дисциплине:
ref лучше держать стандартизированным: версия -> архитектура -> профиль.
Это упрощает каналы stable/testing и жизнь тем, кто будет обслуживать систему через год.
Публикация repo по HTTP(S) и контроль (refs/summary)
Клиентам нужен только каталог OSTree-репозитория: REPOPATH/repo.
Каталог cache публиковать не нужно (и не стоит): это внутренний кэш сборки.
Summary критичен так как mkostreerepo после compose делает:
* ostree summary --repo=... --update
* ostree summary -v --repo=...
Summary - это метаданные, которые помогают клиентам быстро обнаруживать refs и
корректно работать с репозиторием.
Если summary не обновлён, вы получаете странные симптомы на клиентах: "ref не
виден", "pull ведёт себя не так", "не сходится список refs".
Базовые проверки на сервере
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
4. Проверить структуру каталога:
ls -la /srv/ostree/niceos
ls -la /srv/ostree/niceos/repo | head -n 50
ls -la /srv/ostree/niceos/cache | head -n 50
Идея публикации по HTTP(S): веб-сервер должен отдавать /srv/ostree/niceos/repo по URL вида:
http://OSTREE_SERVER/ostree/repo/
Дальше клиент на своей стороне добавляет remote и делает pull/deploy (это будет в 3 части).
Проверка доступности с клиента (просто "жив ли HTTP"):
curl -I "http://OSTREE_SERVER/ostree/repo/" || true
Мини-чеклист "сервер готов"
1. REPOPATH существует, внутри есть repo/ и cache/.
2. ostree refs показывает нужные refs (и они соответствуют вашей схеме именования).
3. ostree summary -v отрабатывает без ошибок.
4. repo/ доступен по HTTP(S) из сети клиентов.
5. В release-процессе есть правило: после каждого compose summary обновляется
(и это реально выполняется).
Нюансы и типовые грабли на сервере
Репозитории RPM и ключи
В дефолтных *.repo, которые mkostreerepo может сгенерировать, включён:
* gpgcheck=1
* gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-NICEOS
Это правильно по смыслу, но означает простую вещь:
на сервере сборки должны быть ключи и доверие, иначе compose упадёт не "из-за
OSTree", а из-за проверки подписей RPM.
Customrepo в CI
Флаг -c может вызвать интерактив (Y/N). Для CI это неприятно:
пайплайн повиснет, пока кто-то не придёт "нажать кнопку".
Если вы хотите автоматизацию, обычно лучше:
* не использовать интерактивный режим,
* либо заранее подготовить *.repo и доработать скрипт под non-interactive.
Разделение прав доступа
Репозиторий - это точка поставки системы.
Нормальная практика:
* запись в repo/ только у сборочного пользователя/процесса,
* веб-сервер имеет только чтение,
* доступ снаружи ограничен (HTTPS или закрытый контур).
Клиентская часть: развёртывание системы/образа в НАЙС.ОС
На клиентской стороне OSTree даёт приятную вещь: вы перестаёте "устанавливать
систему пакетами" как набор мелких действий.
Вы либо:
1. разворачиваете систему прямо в sysroot (условная "установка" через pull/deploy),
либо
2. готовите дисковый образ, в котором уже есть нужный deploy и настроен загрузчик.
Для практики (особенно под виртуализацию) второй вариант удобнее: один раз
собрали образ - дальше тиражируете как шаблон.
В НАЙС.ОС под это уже есть скрипт: mk-ostree-host.sh, который делает образ
диска и разворачивает в него выбранный ref.
Пример "как развернуть" через mk-ostree-host.sh (RAW-образ)
Скрипт выполняет полный цикл подготовки загрузочного дискового образа:
1. Создаёт sparse-файл *.raw заданного размера (будущий диск).
2. Подключает его как loop-устройство.
3. Размечает GPT под BIOS-режим:
* p1: BIOS boot (для GRUB2),
* p2: /boot,
* p3: / (корень).
4. Создаёт device-mapper устройства для разделов (kpartx).
5. Форматирует p2 и p3 в ext4.
6. Монтирует p3 как sysroot, p2 как /boot.
7. Инициализирует OSTree sysroot, добавляет remote, делает pull нужного ref и deploy.
8. Подмонтирует внутрь деплоя служебные FS (/dev, /proc, /sys, /run, /boot).
9. Устанавливает GRUB2 в образ, генерирует конфиги, выставляет kernel args.
10. Подменяет root= так, чтобы система загрузилась на целевой машине.
11. Прописывает /etc/fstab и задаёт временный пароль root.
12. Аккуратно размонтирует всё и снимает loop/devicemapper (даже при ошибках).
Скрипт принимает:
* FILE_SIZE - размер образа в ГБ.
* IMG_NAME - имя (получится IMG_NAME.raw).
* IP_ADDR - адрес OSTree-сервера (remote будет вида http://IP_ADDR).
* REPO_REF - ref, который нужно развернуть (например niceos/5.2/x86_64/minimal).
* MOUNT_POINT - временная точка монтирования sysroot при сборке.
* root-dev - важный параметр: какой диск "увидит" ОС при старте (обычно /dev/sda3 или /dev/vda3).
Во время сборки образа корень выглядит как что-то вроде:
/dev/mapper/loop0p3
Но при запуске VM/на железе это устройство не существует.
Там корневой раздел будет, например:
* /dev/sda3 (классический SATA/SCSI контроллер),
* /dev/vda3 (virtio в KVM/QEMU),
* либо другие варианты в зависимости от платформы.
Поэтому скрипт делает важный трюк:
сначала настраивает root= на "текущее" устройство сборки, а затем заменяет его
на целевое root-dev, чтобы загрузчик в реальной машине искал корень там, где он
действительно будет.
Если root-dev выбран неверно - типичная ошибка: система падает на старте с "cannot mount root".
Пример запуска (BIOS + SATA: /dev/sda3)
sudo ./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
Пример запуска (BIOS + virtio: /dev/vda3)
sudo ./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
Скрипт пишет лог в /var/log/mk-ostree-host.sh-YYYY-MM-DD.log
Проверка:
sudo tail -n 200 "/var/log/mk-ostree-host.sh-$(date +%Y-%m-%d).log"
Проверка результата на первом запуске и базовая эксплуатация
Когда VM загрузилась, задача администратора - убедиться, что активен нужный
deploy и базовые точки монтирования корректны.
Первые обязательные действия
1. Сменить временный пароль root (скрипт ставит root:changeme):
passwd
2. Посмотреть состояние OSTree:
ostree admin status
Смысл вывода:
* вы увидите, какие deploy существуют,
* какой активен сейчас,
* какой будет активен после перезагрузки (если применимо).
3. Проверить монтирование / и /boot:
mount | egrep ' on / | on /boot '
4. Проверить fstab:
cat /etc/fstab
Ожидаемая логика (пример):
* /dev/sda3 -> /
* /dev/sda2 -> /boot
Если ваша VM использует virtio, там должно быть /dev/vda* (иначе вы заложили
неверный root-dev и fstab).
5. Проверить записи загрузки:
ls -la /boot/loader/entries/
Это быстрый способ убедиться, что deploy действительно подготовил загрузочные entry.
Идея отката (rollback) без длинных портянок
OSTree хорош тем, что откат - это не "удалить 30 пакетов и вернуть 30 пакетов",
а переключение между deploy.
Модель простая:
* у вас есть "текущая версия" (deploy A),
* вы обновились/развернули новую (deploy B),
* если после перехода на B что-то не так - вы возвращаетесь на A.
На практике, прежде чем идти в сложную диагностику, в эксплуатационном контуре часто логичнее:
1. вернуть рабочее состояние откатом,
2. затем спокойно разбирать, почему новый deploy оказался проблемным.
В системах, где важен SLA, это прямое попадание в реальность: "сервис должен
работать", а не "мы сейчас два часа разгадываем зависимость".
Типовые ошибки и диагностика на клиенте
VM не загружается: неверный root-dev
Симптом:
kernel panic / cannot mount root / ожидание root device.
Причина: в загрузчике root= указывает на /dev/sda3, а реально диск -
/dev/vda3 (или наоборот).
Решение:
* пересобрать образ с правильным --root-dev,
* либо (в аварийном режиме) править loader entries и fstab вручную - но это уже
"ремонт", а не нормальный процесс.
Клиент не тянет ref: сеть/summary/ref
Симптом:
pull падает, ref не виден, список refs пустой.
Причины:
* нет доступа до HTTP(S) репозитория,
* сервер не публикует repo/,
* summary не обновлён,
* ref указан неправильно.
Быстрые проверки:
* curl -I до URL репозитория,
* ostree remote refs на клиенте,
* ostree summary -v на сервере.
Платформа UEFI, а образ BIOS
Симптом: VM не видит загрузчик, "No bootable device".
Причина: текущий mk-ostree-host.sh размечает BIOS-only (bios_grub), без EFI System Partition.
Решение:
* держать отдельный профиль сборки UEFI (ESP + grub2-efi),
* не пытаться "одним образом" закрыть все режимы загрузки без профилей.
Устройство загрузки поменялось, fstab не совпадает
Симптом: загрузка проходит частично, /boot не монтируется или корень монтируется не туда.
Причина: fstab жёстко привязан к /dev/sdX или /dev/vdX, а среда другая.
Решение:
* заранее стандартизировать профили (virtio/sata),
* либо перейти на UUID/labels в fstab как правило эксплуатации (это отдельное улучшение процесса).
Мини-чеклист "клиент готов"
1. Узел загрузился в нужный профиль (BIOS/virtio или BIOS/sata - как планировали).
2. Пароль root сменён (root:changeme не должен жить дальше первого входа).
3. ostree admin status показывает ожидаемый deploy/ref.
4. / и /boot смонтированы корректно, /etc/fstab соответствует реальной схеме диска.
5. /boot/loader/entries содержит записи, соответствующие deploy.
6. У вас есть регламент: как обновляем (ref stable/testing), и как откатываем при проблемах.
На этом "скелет" процесса закрыт: сервер публикует репозиторий и ref, клиент
разворачивает deploy в образ/узел и получает предсказуемые обновления и откаты.
|