Кастомные скрипты в initrd для обновления системы

Решил сделать зарисовку на тему корректного обновления системы на ригах (да и любых других подобных устройствах) из самой системы. Если быть точным, то просто позаписывать наработки за последний день на эту тему.

На старых ригах, в принципе, прокатывали апдейты прямо в живой системе. Приходилось лепить что-то в стиле

gunzip -c /tmp/rootfs.gz | dd of=/dev/sda2 oflag=sync bs=1M ; echo ok ; echo b > /proc/sysrq-trigger

и оно даже работало. Грубо, но работало.

В 9-м Debian’е, с его systemd и частыми обращениями на диск система начинала обижаться, когда этот самый диск внаглую начинали переписывать. Но с ростом числа ригов корректный апгрейд всего парка становится все актуальнее, так что решил проработать “правильный” вариант.

Правильный вариант заключается в перезаписи диска на стадии работы initrd. Раньше как-то избегал этой темы – частенько собирал ядра без необходимости в initrd, да и в целом во внутренностях не ковырялся. Решил исправить это упущение.

Итак… В initrd при сборке включается ряд скриптов. Часть из них может находиться в каких-то пакетах (тот же mdadm, к примеру) и находится в /usr/share/initramfs-tools/scripts/, для пользовательских есть каталог /etc/initramfs-tools/scripts/. В обоих случаях скрипты делятся на категории, определяющие момент выполнения. Об этом можно прочитать по ссылке.

Вкратце – обходятся каталоги *-top, *-premount и потом *-bottom – вначале init; потом в его теле boot, представленный в вариантах local или nfs. Иначе говоря, обход в моем случае идет так: init-top -> init-premount -> local-top -> local-premount -> local-bottom -> init-bottom. Вот где-то в районе local-top и можно размещать свои скрипты.

Особенность, с которой столкнулся, исследуя эту последовательность – ядро не подгружает автоматом нужные модули, как это делается в “большой” системе. Я написал скрипт, который сохраняет состояние системы на каждом этапе и потом сохраняет собранные данные на диск. И диск успешно монтировался только в bottom-секциях. Сохранив список модулей выяснил, что никакой поддержки ext4 на более ранних этапах еще нет – загрузкой занимаются какие-то еще скрипты. Подробнее не копал, но решил просто добавить в свой скрипт ручную загрузку модуля – после этого все заработало как надо.

Итоговый скрипт парсит /proc/cmdline на предмет наличия ключевого слова и если оно там есть – выполняет монтирование диска с данными и распаковку готовых образов на раздел с boot и rootfs, далее загрузка продолжается как обычно.

Чуть позже добавил еще один вариант – полную перезапись диска. Однако в этом случае перезаписываемые данные затерли бы и вычитываемый с диска образ, поэтому сделал чуть иначе – создается tmpfs по размеру сжатого образа (поэтому оперативной памяти должно хватать – возможно, стоит занулять пустое место на разделах диска, если файл получается сильно большим), образ копируется туда и исходный раздел отмонтируется – а далее с диском можно делать все, что угодно.

Осталось добавить в GRUB пункты меню с нужными триггерами. Вообще для кастомных записей есть файл /etc/grub.d/40_custom, но там придется продублировать штатный скрипт, создающий пункты меню, целиком. Поэтому я сделал чуть иначе – просто добавил в /etc/grub.d/10_linux после if’а с описанием recovery-строчки ряд своих (это почти в самом конце скрипта):

linux_entry "${OS}" "${version}" init-stopmining "disablemining=1 ${GRUB_CMDLINE_LINUX}"
linux_entry "${OS}" "${version}" init-NOKMS "disablemining=1 nomodeset=1 ${GRUB_CMDLINE_LINUX}"
linux_entry "${OS}" "${version}" init-sysupgrade "sysupgrade ${GRUB_CMDLINE_LINUX}"
linux_entry "${OS}" "${version}" init-imgupgrade "imgupgrade ${GRUB_CMDLINE_LINUX}"

Ключ init-* парсится сторонней функцией и то, что после тире, становится частью названия пункта меню.

Итого:

  1. Создаем /etc/initramfs-tools/scripts/local-top/sysupgrade.sh
  2. Правим /etc/grub.d/10_linux
  3. update-initramfs -k all -u
  4. update-grub

Маленькая особенность, чуть не забыл: скрипт выполняется в момент обновления образа initrd, стоит учитывать.

Каталоги с образами в системе размещаем в соответствии со своим скриптом. Далее grub-reboot 4 или grub-reboot 5, если использовать тот пример в 10_linux, что я привел выше.

Мой скрипт – напрашивается более аккуратный вариант и слияние обоих блоков в один, но уже решил не заморачиваться:

Скрипт

#!/bin/sh

grep -q sysupgrade /proc/cmdline && {
modprobe ext4
mkdir -p /run/sda3
mount /dev/sda3 /run/sda3
if [ -d '/run/sda3/sysupgrade' ]
then

for i in $(ls -1 /run/sda3/sysupgrade/sda[0-9]*)
do

type="$(basename "${i}" | sed 's/.*\.//g')"

if [ "${type}" == 'gz' ]
then
cmd='zcat'
elif [ "${type}" == 'bz2' ]
then
cmd='bzcat'
elif [ "${type}" == 'xz' ]
then
cmd='xzcat'
fi
${cmd} "${i}" > /dev/$(basename "${i}" | sed 's/\..*//g')
done

fi
sync
umount /dev/sda3
}

grep -q imgupgrade /proc/cmdline && {
modprobe ext4
mkdir -p /run/sda3 /run/sysimg
mount /dev/sda3 /run/sda3
if [ -d '/run/sda3/sysupgrade' ]
then

for i in $(ls -1 /run/sda3/sysupgrade/sda.*)
do

type="$(basename "${i}" | sed 's/.*\.//g')"
mount -t tmpfs tmpfs /run/sysimg -o size=$(du "${i}" | cut -f1)k
cp "${i}" /run/sysimg/
umount /dev/sda3

if [ "${type}" == 'gz' ]
then
cmd='zcat'
elif [ "${type}" == 'bz2' ]
then
cmd='bzcat'
elif [ "${type}" == 'xz' ]
then
cmd='xzcat'
fi
${cmd} /run/sysimg/$(basename "${i}") > /dev/$(basename "${i}" | sed 's/\..*//g')
sync
reboot
done
fi
}

[свернуть]

Добавить комментарий