Больше не нужно искать работу мечты — присоединяйтесь к команде Клауда

Конвертация контейнера в виртуальную машину

Вадим Белоус
Вадим Белоус
Системный инженер
27 августа 2024 г.
156
12 минут чтения
Средний рейтинг статьи: 2.5

Нередко на технических собеседованиях на позицию DevOps-инженера задают каверзный вопрос: «В чем отличие контейнера от виртуальной машины?». Большинство кандидатов теряются при ответе на этот вопрос, а некоторые интервьюеры и сами до конца не понимают, какой ответ они хотят услышать от собеседуемого. Чтобы наглядно понять различия и никогда не возвращаться к этому вопросу, я покажу, как превратить контейнер в виртуальную машину и запустить ее в облаке Timeweb Cloud.

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

Все дальнейшие действия я буду выполнять в окружении ОС Linux. Для подготовки необходимого образа я буду использовать виртуальную машину на базе гипервизора KVM, созданную с помощью VirtualBox. Вы также можете использовать любые другие провайдеры, например VMware, QEMU или virt-manager.

Конфигурация будущей виртуальной машины

Предлагаю начать этот увлекательный путь с создания контейнера. Для этого мы воспользуемся Docker. Если он у вас всё ещё не установлен, установите его с помощью команды, приведенной ниже (перед этим может потребоваться обновить список доступных пакетов — sudo apt update).

sudo apt install docker.io -y

Создайте контейнер на основе минимального образа alpine и прикрепитесь к его командной оболочке

sudo docker run --name test -it alpine sh

Установите с помощью пакетного менеджера apk необходимые программы, которые вы планируете использовать в будущей виртуальной машине. Вам не обязательно ограничиваться пакетами из стандартного репозитория Alpine — вы также можете добавить другие репозитории, а при необходимости скачать или собрать пакеты прямо в контейнере.

apk add tmux busybox-extras openssh-client openssh-server iptables dhclient ppp socat tcpdump vim openrc mkinitfs grub grub-bios

Вот список минимально необходимых пакетов:

  • tmux — консольный мультиплексор. Он пригодится для сохранения пользовательской сессии и контекста выполняемых процессов в случае разрыва сетевого соединения.

  • busybox-extras — расширенная версия BusyBox, которая включает в себя дополнительные утилиты, но по-прежнему остается компактным дистрибутивом стандартных инструментов.

  • openssh-client и openssh-server — клиент и сервер OpenSSH, необходимые для организации удаленных подключений.

  • iptables — утилита для настройки правил фильтрации IP-пакетов.

  • dhclient — DHCP-клиент, автоматизирующий конфигурацию сетевых настроек.

  • ppp — пакет для реализации канального протокола Point-to-Point.

  • socat — программа для создания туннелей, аналогичная netcat, с поддержкой шифрования и функционалом интерактивной командной оболочки.

  • tcpdump — утилита для захвата трафика, может пригодиться для отладки сетевых неисправностей.

  • vim — консольный текстовый редактор с богатыми возможностями кастомизации. Популярен среди опытных пользователей ОС Linux.

  • openrc — система инициализации, основанная на управлении зависимостями, работающая в связке с SysVinit. Один из ключевых компонентов, необходимых для превращения контейнера в виртуальную машину, поскольку контейнеры не имеют ее по умолчанию.

  • mkinitfs — пакет для генерации initramfs, позволяющий собрать необходимые драйверы и модули, загружаемые на начальном этапе инициализации системы.

  • grub и grub-bios — загрузчик ОС. В данном случае нас интересует именно создание загрузчика для систем с BIOS, использующих таблицу разделов MBR.

Задайте пароль суперпользователю:

export PASSWORD=<ваш секретный пароль>
echo "root:$PASSWORD" | chpasswd

Создайте пользователя. В дальнейшем он понадобится для удаленного подключения по SSH:

export USERNAME=<имя пользователя>
adduser -s /bin/sh $USERNAME

Установите SUID-бит на исполняемый файл busybox. Это нужно для того, чтобы пользователь мог запускать команды с правами суперпользователя:

chmod u+s /bin/busybox

Создайте скрипт, который будет выполняться при инициализации системы

cat <<EOF > /etc/local.d/init.start
#!/bin/sh

dmesg -n 1
mount -o remount,rw /
ifconfig lo 127.0.0.1 netmask 255.0.0.0
dhclient eth0
# ifconfig eth0 172.16.0.200 netmask 255.255.255.0
# route add -net default gw 172.16.0.1
busybox-extras telnetd
EOF

Рассмотрим скрипт построчно:

  • dmesg -n 1 — выводит критические сообщения из буфера ядра Linux, чтобы при запуске можно было обнаружить возможные неполадки.

  • mount -o remount,rw / — перемонтирует корневую ФС (/) с параметром rw (чтение и запись). Эта манипуляция позволит изменять ФС после загрузки.

  • ifconfig lo 127.0.0.1 netmask 255.0.0.0 — настраивает интерфейс loopback (lo) с IP-адресом 127.0.0.1 и маской подсети 255.0.0.0. Это необходимо для обеспечения внутренней сетевой связи на машине.

  • dhclient eth0 — запускает DHCP-клиент для интерфейса eth0, чтобы автоматически получить настройки IP-адреса и другие параметры сети от DHCP-сервера.

  • # ifconfig eth0 172.16.0.200 netmask 255.255.255.0 — эта строка закомментирована, но при раскомментировании задаст статический IP-адрес 172.16.0.200 и маску подсети 255.255.255.0 для интерфейса eth0. Я добавил эту строку в скрипт на случай, если потребуется статическая конфигурация сети.

  • # route add -net default gw 172.16.0.1 — эта строка тоже закомментирована, но при раскомментировании добавит маршрут по умолчанию с шлюзом 172.16.0.1. Это определяет, как пакеты будут маршрутизироваться в сеть за пределами локальной сети.

  • busybox-extras telnetd — запускает Telnet-сервер. Обратите внимание, что использование протокола Telnet в продуктивных средах не рекомендуется из-за отсутствия шифрования при передаче данных.

Сделайте скрипт исполняемым:

chmod +x /etc/local.d/init.start

Добавьте скрипт автоматической настройки в автозапуск:

rc-update add local

Добавьте демон OpenSSH-сервера в автозапуск. Это понадобится для того, чтобы в дальнейшем подключаться к облачному серверу по SSH:

rc-update add sshd default

Установите DNS-сервер по умолчанию:

echo nameserver 8.8.8.8 > /etc/resolv.conf

Открепитесь от терминала с помощью команды exit или комбинации клавиш CRTL+D. На следующем этапе нам нужно сохранить файловую систему контейнера на хосте в виде архива, что также можно сделать с помощью Docker. В моем случае конечный артефакт занимает всего 75 мегабайт.

sudo docker export test > test.tar
cloud

Трансформация Docker-образа в образ для виртуальной машины

Контейнеры представляют собой Linux-специфичную технологию, поскольку они не имеют своего ядра и используют абстракции хостового ядра Linux для обеспечения изоляции и управления ресурсами. К основным абстракциям относятся:

  • namespaces — изоляция относительно пространств имен: USER, TIME, PID, NET, MOUNT, UTS, IPC, CGROUP.

  • cgroups — ограничение относительно ресурсов: CPU, RAM, I/O.

  • capabilities — набор возможностей для выполнения определенных, в том числе привилегированных, операций без прав суперпользователя.

Эти компоненты ядра Linux делают Docker и другие контейнерные технологии тесно связанными с Linux, что делает невозможной их нативную работу на других операционных системах, таких как Windows, macOS или BSD*.

Для работы на Windows, macOS и BSD* существует Docker Desktop, который представляет собой виртуальную машину с минимальной операционной системой на основе ядра Linux. Внутри этой виртуальной машины установлен и запущен Docker Engine, который позволяет пользователям управлять контейнерами и образами Docker в привычной среде.

Поскольку нам нужна полноценная операционная система, а не просто контейнер, то потребуется собственное ядро.

Создайте файл-образ, с которым мы будем работать дальше:

truncate -s 200M test.img

С помощью fdisk создайте раздел на образе test.img.

echo -e "n\np\n1\n\n\nw" | fdisk test.img
  • n — создание нового раздела
  • p — указание того, что это будет основной (primary) раздел
  • 1 — номер нового раздела
  • \n\n — использование значений по умолчанию для начального и конечного секторов
  • w — запись изменений

Теперь свяжите файл-образ test.img с устройством /dev/loop3, начиная со смещения 2048 блоков по 512 байт каждый (1 МБ):

sudo losetup -o $[2048*512] /dev/loop3 test.img

Обратите внимание, что устройство /dev/loop3 может быть уже задействовано — часто первые несколько loop-интерфейсов уже используются. Вы можете проверить занятые устройства с помощью следующей команды:

losetup -l

Отформатируйте раздел, связанный с /dev/loop3, в файловую систему EXT4:

sudo mkfs.ext4 /dev/loop3

Смонтируйте /dev/loop3 в директорию /mnt:

sudo mount /dev/loop3 /mnt

Разархивируйте предварительно созданный Docker-образ test.tar в директорию /mnt:

sudo tar xvf test.tar -C /mnt

Создайте директорию /mnt/boot для размещения файлов загрузчика и ядра:

sudo mkdir -pv /mnt/boot

Скачайте исходный код ядра Linux:

wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.9.tar.xz

Разархивируйте исходный код ядра Linux в текущую директорию:

tar xf linux-6.8.9.tar.xz

Установите необходимые пакеты для сборки ядра Linux:

sudo apt install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison -y

Перейдите в директорию c исходным кодом и создайте конфигурационный файл для сборки по умолчанию:

cd linux-6.8.9
make defconfig

Добавьте необходимые параметры в файл конфигурации (вы также можете воспользоваться menuconfigsudo make menuconfig):

echo -e "CONFIG_BRIDGE=y\nCONFIG_TUN=y\nCONFIG_PPP=y\nCONFIG_PPP_ASYNC=y\nCONFIG_PPP_DEFLATE=y" >> .config
  • CONFIG_BRIDGE=y — поддержка сетевых мостов для объединения нескольких сетевых интерфейсов в один.

  • CONFIG_TUN=y — поддержка виртуальных сетевых интерфейсов TUN/TAP. Может пригодиться, если понадобится использовать VPN.

  • CONFIG_PPP=y — поддержка протокола Point-to-Point Protocol (PPP).

  • CONFIG_PPP_ASYNC=y — обеспечение работы асинхронного режима PPP для последовательных портов.

  • CONFIG_PPP_DEFLATE=y — сжатие данных PPP с помощью алгоритма DEFLATE.

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

make prepare -j4
make scripts -j4
make bzImage -j4
make modules -j4

Установите собранное ядро и модули в директорию, где находится ФС нашего образа —  /mnt/boot.

sudo make INSTALL_PATH=/mnt/boot install
sudo make INSTALL_MOD_PATH=/mnt modules_install

Установите загрузчик GRUB для образа test.img в директорию /mnt/boot (убедитесь, что вы находитесь в директории с образом test.img):

sudo grub-install --target=i386-pc --boot-directory=/mnt/boot/test.img --modules='part_msdos'

Смонтируйте директории хостовой системы /proc, /sys и /dev в /mnt. Это понадобится для создания initramfs:

sudo mount --bind /proc /mnt/proc/
sudo mount --bind /sys /mnt/sys/
sudo mount --bind /dev /mnt/dev/

Произведите chroot — прикрепитесь к ФС, находящейся в /mnt, с помощью оболочки командной строки sh:

sudo chroot /mnt /bin/sh

Создайте initramfs:

mkinitfs -k -o /boot/initrd.img-6.8.9 6.8.9

Сгенерируйте конфигурацию загрузчика GRUB:

grub-mkconfig -o /boot/grub/grub.cfg

Создание образа завершено. В результате мы получили артефакт в виде небольшого образа для виртуальной машины

Локальная проверка собранного образа

Для локальной проверки работоспособности наиболее удобно использовать QEMU. Этот пакет доступен для Windows, macOS и Linux. Установите его, следуя инструкции для вашей ОС на официальном сайте, если он еще не установлен.

Конвертируйте test.img в формат qcow2. Это позволит уменьшить размер итогового образа с 200 МБ до 134 МБ.

qemu-img convert test.img -O qcow2 test.qcow2

Запустите образ с помощью QEMU.

qemu-system-x86_64 -hda test.qcow2

Если все шаги были выполнены корректно, процесс инициализации пройдет успешно, и появится интерактивное меню для ввода логина и пароля.

Image9

Чтобы проверить версию установленного ядра, используйте команду uname -a, которая выведет необходимую информацию.

Image7

Создание виртуальной машины в Timeweb Cloud

Перейдите в раздел «Облачные серверы» и начните процесс создания нового сервера. В качестве образа для сервера выберите только что подготовленный и проверенный образ. Для этого сначала добавьте его в список доступных образов. В качестве поддерживаемых форматов доступны: iso, qcow2, vmdk, vhd, vhdx, vdi, raw, img.

Image6

Загрузите образ одним из доступных вариантов: с компьютера или по ссылке.

Image8

Обратите внимание, что после загрузки образ также становится доступен по URL.

Image3

Продолжите создание облачного сервера и укажите остальные параметры его конфигурации. Так как образ минимальный, то и запустить его можно будет на самой маленькой конфигурации.

Image1

Когда облачный сервер будет создан, перейдите во вкладку «Консоль» и проверьте, успешно ли завершилось создание виртуальной машины из образа.

Image5

Виртуальная машина создана и работает корректно.

Image4

Поскольку мы добавили в автозагрузку демон OpenSSH заранее, теперь возможно установить полноценное удаленное подключение к серверу с помощью имени пользователя, IP-адреса и пароля.

Image2

Разворачивайте свои проекты в облаке Timeweb Cloud

Вывод

Для того чтобы превратить контейнер в полноценную легковесную виртуальную машину, мы последовательно добавили ключевые компоненты: систему инициализации OpenRC, загрузчик GRUB, ядро Linux и initramfs. Этот процесс подчеркнул важность каждого компонента в общей архитектуре виртуальной машины и продемонстрировал на практике отличия от контейнерных сред.

В результате проделанного эксперимента мы выяснили, насколько важно понимать архитектуру и функции каждого компонента, чтобы успешно создавать образы для своих нужд и более эффективно управлять виртуальными машинами с точки зрения ресурсов. Собранный в рамках статьи образ является довольно минимальным, так как это Proof-of-Concept, но можно пойти еще дальше, например, использовать специальную инструкцию по минимизации ядра, а также обратить внимание на минимальные дистрибутивы Linux, такие как Tiny Core Linux или SliTaz. В то же время, если ваш выбор склоняется к добавлению функционала путем увеличения размера образа, настоятельно рекомендую ознакомиться с Gentoo Wiki. Этот ресурс предлагает обширную информацию относительно точечной настройки системы.

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
27 августа 2024 г.
156
12 минут чтения
Средний рейтинг статьи: 2.5
Пока нет комментариев