Каждый плановый патчинг парка RedOS у меня начинался одинаково: запускаешь обновление — и где‑то на третьем сервере оно падает, потому что в /var или /boot кончилось место. Дальше знакомый ритуал: идёшь в vCenter, увеличиваешь диск, растишь раздел, pvresize, lvextend, молишься, что не перепутал диск, возвращаешься к обновлению. На одном сервере — терпимо. На парке — тоска.
В какой‑то момент я решил, что хватит, и написал комплект из двух Ansible‑ролей: первая сама находит, где тесно, и безопасно доращивает LVM (при необходимости — увеличивая VMDK через vCenter), вторая обновляет ядро и пакеты одной транзакцией и проверяет, что сервер после ребута жив и загрузился с правильным ядром. Всё запускается с jump host, хосты идут по одному (serial: 1), полный цикл — одна команда:
bash
ansible-playbook playbooks/site.yml -l server01 -u local_admin \ --ask-pass --become --ask-become-pass
Расскажу, как это устроено и почему некоторые решения выглядят параноидально. Спойлер: потому что автоматика, которая сама трогает диски продовых виртуалок, обязана быть параноиком.
Сначала план, потом руки
Главный страх при автоматизации работы с дисками — не «не сработает», а «сработает не туда». Поэтому первое архитектурное решение: роль storage живёт в двух режимах. storage_plan.yml только смотрит и считает: какие точки монтирования ниже порога, какой LV/VG/PV за ними стоит, на каком физическом диске лежит PV, что роль собирается сделать. Никаких изменений, даже пароль от vCenter не спрашивается. storage_apply.yml — тот же код, но с реальным применением.
Второе решение: роль почти никогда не падает с голым fail. Каждая точка монтирования получает статус в общем отчёте — «достаточно_места», «требуется_ручная_проверка», «требуется_перезагрузка» и так далее, с человеческим объяснением причины. Когда прогон по парку заканчивается, у тебя не простыня красных ошибок, а внятный список: тут всё хорошо, тут я расширил, сюда не полез и вот почему. С таким отчётом жить сильно легче, чем раскапывать, на какой из вложенных задач что упало.
Одиннадцать проверок перед тем, как тронуть VMDK
Самая нервная операция — увеличение виртуального диска. Новые диски я принципиально не создаю: роль доращивает первый системный VMDK (SCSI 0:0) на фиксированные 10 ГБ. Но прежде чем дёрнуть vCenter, она проверяет буквально всё:
целевая VG состоит ровно из одного PV — никаких «VG размазана по трём дискам, расширим какой‑нибудь»;
этот PV лежит на том же физическом диске, что и корень. Имя диска при этом не захардкожено: sda, vda, nvme0n1 — роль сама определяет системный диск по корневой ФС;
VMDK SCSI(0:0) совпадает с системным Linux‑диском по точному размеру — дополнительная страховка, что мы увеличиваем именно тот диск, а не диск с базой соседнего PostgreSQL;
у ВМ нет снапшотов и незавершённой consolidation — vSphere всё равно не даст расширить диск со снапшотом, но лучше узнать это до начала работ, а не в середине;
PV занимает диск целиком или находится в последнем разделе — двигать середину таблицы разделов автоматика не должна никогда;
на сервере есть parted, а раздел и ФС поддерживают онлайн‑расширение.
Не прошла хоть одна проверка — хост получает «требуется_ручная_проверка» с объяснением, и роль идёт дальше по списку. Отдельные диски с данными (тот же PostgreSQL на своём VMDK) не расширяются вообще: даже если такую точку случайно вписать в конфиг, роль остановится до обращения к vCenter.
Маркер операции, или что будет, если прогон умрёт на середине
Это моя любимая часть. Между «vCenter увеличил VMDK» и «Linux увидел новый размер, pvresize прошёл» есть окно, в котором прогон может умереть: сеть моргнула, кто‑то нажал Ctrl+C, jump host ушёл в ребут. Перезапускаешь плейбук — и наивная реализация снова добавит 10 ГБ, потому что «места‑то всё ещё не хватает». Ещё перезапуск — ещё 10 ГБ. Диск растёт, бюджет датастора плачет.
Поэтому перед обращением к vCenter роль пишет на хост маркер pending-system-disk-resize.json с точным целевым размером диска. При перезапуске она сначала читает маркер: если незавершённая операция есть — применяется записанный в ней размер, а не «текущий плюс 10». Удаляется маркер только после успешного pvresize. Заодно маркер блокирует параллельное расширение другого тома на том же диске, пока первая операция не доведена до конца.
Мелкая деталь с большими последствиями: сколько бы гигабайт ни приехало на диск, LV доращивается не «на всё», а до расчётного целевого остатка свободного места. Излишек остаётся в VG — следующая нехватка места на этом сервере закроется вообще без похода в vCenter.
Патчинг: одна транзакция и никакой самодеятельности
Ядро и остальные пакеты я осознанно не разделяю — обновление идёт одной командой dnf update -y (на RedOS 7 с yum — соответственно yum). Раздельное обновление «сначала ядро, потом всё остальное» звучит аккуратнее, но на практике порождает вдвое больше состояний, в которых что‑то может пойти не так.
Перед транзакцией — preflight: проверка, что на хосте не крутится другой yum/dnf/rpm (привет, коллега, который «быстренько поставил пакетик» прямо во время окна), опциональная раскатка эталонных.repo‑файлов. Причём каталог репозиториев выбирается не по имени группы в инвентаре, а по фактической мажорной версии ОС из Ansible facts — после того как я однажды увидел сервер, живущий не в той группе, доверять инвентарю в таких вещах перестал.
Дальше нюансы, каждый из которых — результат реального инцидента или почти‑инцидента:
/boot в read‑only. Часть серверов у меня монтирует /boot в ro. Роль запоминает исходный режим, перемонтирует в rw, а после всех работ хендлер возвращает как было. Без этого либо обновление ядра падает, либо /boot навсегда остаётся rw — оба варианта так себе.
Чистка старых ядер — без rm. Старые ядра удаляются через dnf repoquery --installed --installonly с явным исключением загруженного ядра. Никаких шаблонов «rm ‑f vmlinuz-5.*» — пакетный менеджер знает про установленные ядра больше, чем любой мой регэксп.
Ядро по умолчанию — с проверкой после ребута. Новое ядро назначается через grubby --set-default (с фолбэком на grub2-set-default 0, если grubby нет), а после перезагрузки assert сверяет uname -r с ожидаемым. Однажды словив сервер, который после обновления тихо загрузился со старым ядром, я больше не считаю эту проверку избыточной.
Стратегия manual_dracut. Для отдельных капризных серверов RedOS 7 есть режим: обновление с DNF_DISABLE_DRACUT=1, затем ручной dracut для самого нового ядра и grub2-mkconfig. Включается пофлажно в group_vars, по умолчанию всем достаётся normal.
Прикладные хосты: PostgreSQL и тд
Патчить голую ОС просто. Интересное начинается на серверах с прикладом.
Для хостов PostgreSQL роль перед обновлением бэкапит то, что обновление любит молча перетирать: unit‑файлы postgre*, .bash_profile пользователя postgres, crypto‑policies для krb5. После транзакции всё это восстанавливается (с daemon_reload), и только потом сервер уходит в ребут. Бэкапы складываются в датированный каталог и по умолчанию не удаляются — сначала руками убеждаешься, что база поднялась, потом чистишь.
Комментарии (3)

Anthony_S_Chet
23.07.2026 15:38Лично я не понимаю, зачем вообще на ВМках использовать LVM. Диски и ФС можно прозрачно увеличивать, а LVM - лишняя прокладка. Только один раз он действительно понадобился - когда потребовались разделы по 30 Тб.
MEGA_Nexus
А где собственно сам ранбук для Ansible?
И ещё вопрос. Почему:
если
Т.е. почему заканчивается место в /boot, если старые ядра удаляются?
Второй вопрос. Зачем расширять место автоматически в /var, если там, как правило, место съедают логи (/var/log) база данных (/var/lib). Может стоит лучше вручную посмотреть, что там за проблема? Если место скушала база, то +10 GB ей не особо помогут. Если место скушали логи, то может быть лучше настроить logrotate или посмотреть, что пишет слишком много логов, возможно, кто-то забыл отключить debug.
Также было бы неплохо использовать мониторинг, типа zabbix, чтобы проблемы с нехваткой места решать заранее, а не когда происходят внезапные работы с сервером.
dunnoOspf Автор
Про /boot: ядра-то сносятся нормально, дело не в них.
dnf по дефолту держит три ядра (installonly_limit), и лишнее выкидывает только когда ставит новое. А ядро — это не только vmlinuz на 10 метров, к нему ещё initramfs 40–100 (в host-only и жирнее бывает) плюс kdump-образ такого же порядка, если kdump включён. Три ядра с kdump — уже под 700 метров, а /boot по дефолтной разметке 512М–1Г. Впритык изначально.
Плюс пик — ровно в середине транзакции: новое ядро распаковано, dracut initramfs собрал, а старое ещё лежит, оно уедет в конце. То есть в моменте там N+1 ядер, и оно падает именно тогда, а не «потом, когда почистится».
Плюс половина барахла в /boot вообще не принадлежит пакетам, поэтому repoquery его в упор не видит. Дёрни
rpm -qf /boot/initramfs-*.img— на что-то скажет not owned by any package. initramfs же генерится скриптлетом уже после установки, в файлах пакета его нет. При штатном сносе ядра он подчищается, а если ядро выпиливали руками, транзакция когда-то отвалилась или машину клонировали — висит сиротой вечно. Плюс initramfs-0-rescue-*, который не удаляется никогда, и после смены machine-id их накапливается по несколько штук по 80 метров.du -sh /boot/*— и обычно сразу видно, что полраздела занято тем, чего в rpmdb нет.А /var кончается по своей причине, к ядрам вообще отношения не имеет: dnf тянет всю транзакцию в /var/cache/dnf до начала установки, там же rpmdb и history.
Про расширение /var — оно не про логи. Это чтобы обновление не сдохло на середине транзакции с недописанным rpmdb, разгребать такое куда дороже, чем пара лишних гигов на LV. Если место скушала база или забытый debug — да, + память - это только отодвинет, но плейбук её и не должен лечить, его задача довести апдейт до конца и не оставить сервер в разобранном виде.
По zabbix'у согласен на все сто, триггеры нужны, и лучше предиктивные по тренду, а не голый порог.
Но одно другое не заменяет: мониторинг не знает, сколько сожрёт конкретная транзакция, а плейбук не видит тренда между патчингами.