Я много работаю с PXE загрузкой машин по сети. Для тестирования загрузочных образов я использую qemu и однажды я понял, что для установки ubuntu на виртуальную машину мне удобнее использовать PXE, чем загружаться с образа оптического диска. В данной статье я буду работать с ubuntu, однако подобный подход можно применять и для других дистрибутивов (у меня есть позитивный опыт работы со SLES).
Для генерации загрузочного образа нам понадобятся squashfs-tools и debootstrap, установим их:
apt-get update
apt-get install --no-install-recommends -y squashfs-tools debootstrap
Теперь можно установить минимальную систему при помощи debootstrap:
IMAGE_ROOT=/tmp/new_root
mkdir -p "$IMAGE_ROOT" && cd "$IMAGE_ROOT"
. /etc/os-release
debootstrap --arch amd64 --variant=minbase --components=main,restricted,universe \
--include=live-boot,systemd-sysv,openssh-server,linux-image-virtual,curl \
${UBUNTU_CODENAME} .
Как видно из команды я выбираю минимальную установку и добавляю пакеты, которые мне нужны.
Создаем конфиг для apt
printf "deb http://archive.ubuntu.com/ubuntu/ %s main restricted universe multiverse\n" \
$UBUNTU_CODENAME{,-backports,-updates,-security} > ./etc/apt/sources.list
И для сети
mkdir -p /etc/systemd/{network,system}
cat > ./etc/systemd/network/80-dhcp.network <<NETWORK
[Match]
Name=!lo* !docker*
[Network]
DHCP=yes
NETWORK
Кроме того я добавляю маленький сервис, при помощи которого я смогу после загрузки выполнять произвольные скрипты, которые будут загружаться по http. Так как для загрузки я использую ipxe, то http сервер у меня в любом случае задействован. Добавляя параметр вида http_hook=http://server/path/to/script.sh я могу дополнительно настраивать образ после загрузки. В построенном загрузочном образе ssh ключей нет, так что при помощи такого дополнительного скрипта я добавляю временный ssh ключ, сгеренированный спепциально для конкретного запуска.
cat > ./etc/systemd/system/http_hook.service <<HTTP_HOOK
[Unit]
Description=Fetch via http and run script provided via http_hook kernel parameter
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c ". <(grep -oP 'http_hook=\K\S+' /proc/cmdline|xargs -r -L1 -P1 curl -sf)"
[Install]
WantedBy=multi-user.target
HTTP_HOOK
Надо активировать новый сервис, а также systemd-networkd
chroot . /bin/bash -c \
"systemctl enable systemd-networkd.service && systemctl enable http_hook"
Разрешаем root ssh-доступ по ключу
sed -i -e '/^PermitRootLogin/d' \
-e '$aPermitRootLogin without-password' ./etc/ssh/sshd_config
После удаления лишнего можно сгенерировать образ корневой файловой системы
rm -rf ./var/cache/apt ./etc/{hostname,hosts} ./var/log/*.log ./root/.cache
mksquashfs . ./boot/root.squashfs -b 1048576 -comp xz -Xdict-size 100% -regex \
-e "proc/.*" -e "sys/.*" -e "run/.*" -e "var/lib/apt/lists/.*" -e "boot/.*"
Образ будет находиться в директории ./boot, так как там же находятся ядро и initrd, что облегчает последующее копирование. Лучше всего все команды выше выполнять в докере, вот скрипт.
Теперь, когда у нас есть необходимые артефакты, можно протестировать их при помощи qemu. Для этого я использую скрипт, который понимает команды start, stop, ssh и console.
Команда console нужна для отладки, когда доступ по ssh по какой-то причине не работает. Но для ее использования надо не забыть в раскомментировать в скрипте построения артефактов строку, которая устанавливает пароль для root.
Для запуска виртуальной машины надо выполнить команду ./qemu.sh start. Эта команда создаст временный ssh ключ для доступа и запустит http сервер (используя встроенный в python 3 модуль http.server). В опциях запуска виртуальной машины мы указываем параметры для использования ipxe, а также форвардим ssh порт с виртуальной машины для последующего доступа.
Как только виртуальная машина будет доступна по ssh скрипт сообщит вам об этом. В противном случае по истечении таймаута скрипт сообщит о неудаче. Для диагностирования проблем лог консоли сохраняется в ./output/qemu.log.
Если машина загрузилась без проблем вы можете зайти на нее по ssh при помощи команды ./qemu.sh ssh.
Для завершения работы машины вы можете либо выполнить команду shutdown -P now непосредственно в сеансе ssh или выполнить команду ./qemu.sh stop.
Теперь я покажу процесс установки ubuntu на диск виртуальной машины. Создаем образ диска
qemu-img create -f qcow2 /var/tmp/myimage.qcow2 16G
Когда диск будет готов надо запустить виртуальную машину вот таким образом
QEMU_OPTS="-drive file=/var/tmp/myimage.qcow2" ./qemu.sh start
и дождаться подтверждения, что машина готова. Теперь залогинимся на виртуальную машину при помощи команды ./qemu.sh ssh и выполним установку ubuntu на диск.
Будем устанавливать ubuntu 24.04, машину назовем ubuntu-dev
INSTALL_CODENAME=noble
INSTALL_HOSTNAME=ubuntu-dev
Установка будет идти через http прокси на хост системе
export http_proxy=http://10.0.2.2:3128/
export https_proxy=$http_proxy
Для установки нам будут нужны debootstrap и gdisk
apt-get update && apt-get install -y debootstrap gdisk
Разметим и отформатируем диск
sgdisk --zap-all /dev/sda
sgdisk -n 0:0:+1MiB -t 0:ef02 -c 0:grub /dev/sda
sgdisk -n 0:0:0 -t 0:8300 -c 0:root /dev/sda
mkfs.ext4 /dev/sda2
Смонтируем диск и установим минимальную систему
mount /dev/sda2 /mnt
debootstrap --arch amd64 --variant=minbase $INSTALL_CODENAME /mnt/
Создадим конфиг для apt
. /etc/os-release
grep "^deb h" /etc/apt/sources.list \
| sed -e "s/$UBUNTU_CODENAME/$INSTALL_CODENAME/" >/mnt/etc/apt/sources.list
cat <<APT_CONF >/etc/apt/apt.conf.d/99_norecommends
APT::Install-Recommends "false";
APT::AutoRemove::RecommendsImportant "false";
APT::AutoRemove::SuggestsImportant "false";
APT_CONF
Смонтируем псевдо файловые системы для работы в chroot среде
mount --make-private --rbind /dev /mnt/dev
mount --make-private --rbind /proc /mnt/proc
mount --make-private --rbind /sys /mnt/sys
Сконфигурируем имя машины, /etc/fstab
echo "$INSTALL_HOSTNAME" >/mnt/etc/hostname
sed -e "s/\(127.0.1.1\).*/\1 $INSTALL_HOSTNAME/" /etc/hosts >/mnt/etc/hosts
cat >/mnt/etc/fstab <<FSTAB
/dev/sda2 / ext4 errors=remount-ro 0 1
tmpfs /tmp tmpfs nosuid,nodev 0 0
FSTAB
Переключаемся в chroot среду
chroot /mnt /bin/bash
Устанавливаем нужные нам пакеты
apt-get update
apt-get install -y grub2 linux-image-generic openssh-server systemd-sysv \
initramfs-tools sudo
Правим конфиг grub, чтобы можно было пользоваться консолью
sed -i -e 's/^\(GRUB_CMDLINE_LINUX_DEFAULT\).*/\1="console=ttyS0,115200n8"/' \
/etc/default/grub
Правим конфиг сети, чтобы получать адрес по dhcp
cat > /etc/systemd/network/80-dhcp.network <<NETWORK
[Match]
Name=en*
[Network]
DHCP=yes
NETWORK
Активируем systemd-networkd
systemctl enable systemd-networkd.service
Конфигурируем пользователя
USERNAME=toor
SSH_PUB=public_ssh_key_you_want_to_use
useradd -m -s /bin/bash $USERNAME
usermod -p '*' $USERNAME
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/$USERNAME
chmod 0440 /etc/sudoers.d/$USERNAME
mkdir /home/$USERNAME/.ssh
echo "$SSH_PUB" >/home/$USERNAME/.ssh/authorized_keys
chown -R $USERNAME:$USERNAME /home/$USERNAME
usermod -a -G adm,cdrom,dip,plugdev,sudo $USERNAME
Обновляем initrd, устанавливаем grub
update-initramfs -c -k all
grub-install /dev/sda
update-grub
Все готово. Можно выключать машину командой shutdown -P now.
Теперь вы можете загрузить ее с образа диска вот такой командой
qemu-system-x86_64 -drive file=/var/tmp/myimage.qcow2 \
-device virtio-net-pci,netdev=n1 \
-netdev user,id=n1,hostfwd=tcp:127.0.0.1:2222-:22 \
-nographic -enable-kvm -cpu max -m 4096
И получить доступ по ssh
ssh -p 2222 127.0.0.1
Команды выше можно автоматизировать при помощи простого скрипта. Тогда для установки ubuntu надо выполнить вот такую команду
./vm-debootstrap-ubuntu.sh |./qemu.sh ssh
Отдельно стоит отметить, что поскольку машина доступна по ssh можно использовать ansible или другие системы управления конфигурациями.
Обратите внимание, что вся установка в моем случае идет через http прокси, который работает на хост системе. Если вы захотите переиспользовать мой скрипт пожалуйста убедитесь, что у вас работает http прокси. В противном случае вам надо убрать конфигурацию прокси из скрипта и убедиться, что у вас нормально резолвится dns.
Все скрипты из этого поста можно найти на github. Также я настроил в этом репозитории workflow для построения артефактов непосредственно на github. Готовые артефакты можно найти в релизах.