Каждый плановый патчинг парка 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)


  1. MEGA_Nexus
    23.07.2026 15:38

    А где собственно сам ранбук для Ansible?

    И ещё вопрос. Почему:

    где‑то на третьем сервере оно падает, потому что в /var или /boot кончилось место.

    если

    Старые ядра удаляются через dnf repoquery --installed --installonly с явным исключением загруженного ядра.

    Т.е. почему заканчивается место в /boot, если старые ядра удаляются?

    Второй вопрос. Зачем расширять место автоматически в /var, если там, как правило, место съедают логи (/var/log) база данных (/var/lib). Может стоит лучше вручную посмотреть, что там за проблема? Если место скушала база, то +10 GB ей не особо помогут. Если место скушали логи, то может быть лучше настроить logrotate или посмотреть, что пишет слишком много логов, возможно, кто-то забыл отключить debug.

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


    1. dunnoOspf Автор
      23.07.2026 15:38

      Про /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'у согласен на все сто, триггеры нужны, и лучше предиктивные по тренду, а не голый порог.
      Но одно другое не заменяет: мониторинг не знает, сколько сожрёт конкретная транзакция, а плейбук не видит тренда между патчингами.


  1. Anthony_S_Chet
    23.07.2026 15:38

    Лично я не понимаю, зачем вообще на ВМках использовать LVM. Диски и ФС можно прозрачно увеличивать, а LVM - лишняя прокладка. Только один раз он действительно понадобился - когда потребовались разделы по 30 Тб.