Proxmox и Cloud-Init
Proxmox я использую для ознакомления дома на стационарном ПК.
Всё что я делаю с ним сейчас, это эксперименты и проверка некоторых моих гипотез относительно моего представления о продукте и его реального функционала.
Важно отметить, что некоторые решения, такие как Terraform или Ansible, сознательно не используются для решения задачи в данной статье.
🎯 Задача
Поскольку Proxmox позволяет создавать шаблоны виртуальных машин и имеет поддержку Cloud-Init (стандарт для инициализации экземпляра), я задался целью ознакомиться с этими возможностями через призму собственного опыта и законспектировать:
- Создание виртуальной машины из снимка диска с Ubuntu
- Создание и использование шаблона виртуальной машины
- Использование Cloud-Init и конфигурации cloud-config
- Шаблонизирование Hostname виртуальной машины
- Выводы
🍺 Подготовка
Я установил Proxmox с ISO как OS, предаврительно скачав образ здесь https://www.proxmox.com/en/downloads/proxmox-virtual-environment
Образ был записан на флешку с помощью Balena Etcher https://etcher.balena.io/
Установка выполнена с помощью мастера установки без каких либо специфичных настроек. В рамках этой статьи установка рассмотрена не будет.
Далее я буду использовать оптимизированный снимок диска Ubuntu для публичных облаков. Поставляются эти снимки в формате снимка диска (.img, .qcow2). Скачать такой снимок можно здесь.
Я буду стараться использовать терминал вместо Web UI.
☁️ Ubuntu Cloud
Я выбрал Ubuntu Minimal 22.04 LTS (Jammy Jellyfish) release [20240207] доступный по ссылке: https://cloud-images.ubuntu.com/minimal/releases/jammy/release/
Необходимо скачать файл с расширением .img (в других дистрибутивах это может быть .qcow2).
Скачать файл можно в директорию /var/lib/vz/template/iso/
cd /var/lib/vz/template/iso/ wget https://cloud-images.ubuntu.com/minimal/releases/jammy/release/ubuntu-22.04-minimal-cloudimg-amd64.img
Но стоит понимать, что это не образ установочного диска как это обычно бывает с ISO, мы скачиваем виртуальный диск уже готовой системы.
🚜 Виртуальная машина
Создаем новую VM с использованием утилиты qm
, которая является частью пакета программного обеспечения Proxmox
qm create 1000 --memory 2048 --core 2 --name ubuntu-cloud --net0 virtio,bridge=vmbr0
create 1000
: создает новую VM с идентификатором 1000.--memory 2048
: выделяет 2048 МБ ОЗУ памяти для VM--core 2
: выделяет 2 ядра процессора для виртуальной машины.--name ubuntu-cloud
: называет виртуальную машинуubuntu-cloud
.--net0 virtio,bridge=vmbr0
: настраивает сетевой интерфейсnet0
с использованием виртуального драйвераvirtio
и моста с именемvmbr0
.
Импортируем скачанный ранее снимок VM
qm disk import 1000 ubuntu-22.04-minimal-cloudimg-amd64.img local-lvm
qm disk import
: это команда для импорта дискового образа.1000
: это идентификатор VM, в которую будет импортирован диск.ubuntu-22.04-minimal-cloudimg-amd64.img
: это путь к файлу образа диска, который будет импортирован.local-lvm
: это указывает, что диск будет храниться на локальном томе LVM, вы можете подсмотреть информацию о том как ваш том называется в WebUI Proxmox.
Далее с помощью qm set
немного конфигурируем виртуальную машину
qm set 1000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-1000-disk-0,ssd=1
--scsihw virtio-scsi-pci
: устанавливает виртуальный контроллер SCSI для виртуальной машины на virtio-scsi-pci
. Это оптимальный выбор для использования виртуального диска с прямым доступом к устройству (Virtio SCSI), который обеспечивает хорошую производительность.
--scsi0 local-lvm:vm-1000-disk-0
: это настройка первого SCSI устройства (scsi0
) в виртуальной машине. Он связывает диск vm-1000-disk-0
, расположенный на локальном томе LVM с именем local-lvm
, с этим устройством.
Опция ssd=1
указывает, что этот диск должен быть эмулирован как SSD.
Настроим устройство IDE виртуальной машины для загрузки данных инициализации из образа cloud-init
qm set 1000 --ide2 local-lvm:cloudinit
--ide2 local-lvm:cloudinit
: Этот флаг настраивает второе устройство IDE (ide2
) в виртуальной машине. Он связывает образ диска с именем cloudinit
, расположенный на локальном томе LVM с именем local-lvm
, с этим устройством.
Установим параметры загрузки для виртуальной машины
qm set 1000 --boot c --bootdisk scsi0
--boot c
: Этот параметр указывает виртуальной машине загружаться с устройства, на котором установлена операционная система. В данном случаеc
обозначает первое устройство загрузки, что обычно является устройством, где располагается операционная система.--bootdisk scsi0
: Этот параметр указывает, что устройство, которое было определено какscsi0
, должно быть использовано как основное устройство загрузки.
Настроим последовательный порт и вывод графического адаптера для виртуальной машины
qm set 1000 --serial0 socket --vga serial0
--serial0 socket
: Этот параметр устанавливает первый последовательный порт (serial0
) в режим "socket", что означает, что вывод последовательного порта будет перенаправлен в сокет, что обычно используется для подключения к виртуальной машине через виртуальную консоль.--vga serial0
: Этот параметр указывает, что графический адаптер (vga
) должен использовать вывод последовательного портаserial0
.
Виртуальная машина в целом готова и уже сейчас мы можем её запустить. Но мы планируем её использовать как шаблон, поэтому запускать её НЕЛЬЗЯ.
Включим опцию Use QEMU Guest Agent
qm set 1000 --agent 1
📀 Cloud-Init
Конечно, WebUI позволяет заполнить некоторые поля для Cloud-Init
Но Cloud-Init стандарт который позволяет делать больше, чем представлено WebUI Proxmox. Я рекомендую заполнить основные данные в WebUI, а ниже мы обогатим Cloud-Init.
После настройки Cloud-Init через UI мы выполним экспорт YAML конфигурацию.
Для того, чтобы потом эти конфигурации сохранить, мы предварительно включим Snippets хранилище. Это позволит нам корректно использовать наши YAML-ы.
Получить список хранилищ (их ID).
pvesh get /storage
┌───────────┐ │ storage │ ╞═══════════╡ │ local │ ├───────────┤ │ local-lvm │ └───────────┘
Я планирую использовать для этого хранилище с ID: local
pvesh get /storage/local
┌─────────┬──────────────────────────────────────────┐ │ key │ value │ ╞═════════╪══════════════════════════════════════════╡ │ content │ iso,vztmpl,backup │ ├─────────┼──────────────────────────────────────────┤ │ digest │ bd . . . 93 │ ├─────────┼──────────────────────────────────────────┤ │ path │ /var/lib/vz │ ├─────────┼──────────────────────────────────────────┤ │ shared │ 0 │ ├─────────┼──────────────────────────────────────────┤ │ storage │ local │ ├─────────┼──────────────────────────────────────────┤ │ type │ dir │ └─────────┴──────────────────────────────────────────┘ Видим, что в поле content отсутствуют snippets.
pvesh set /storage/local -content iso,vztmpl,snippets,backup
pvesh get /storage/local
┌─────────┬──────────────────────────────────────────┐ │ key │ value │ ╞═════════╪══════════════════════════════════════════╡ │ content │ backup,vztmpl,snippets,iso │ ├─────────┼──────────────────────────────────────────┤ │ digest │ bd . . . 93 │ ├─────────┼──────────────────────────────────────────┤ │ path │ /var/lib/vz │ ├─────────┼──────────────────────────────────────────┤ │ shared │ 0 │ ├─────────┼──────────────────────────────────────────┤ │ storage │ local │ ├─────────┼──────────────────────────────────────────┤ │ type │ dir │ └─────────┴──────────────────────────────────────────┘ Видим, что в поле content теперь присутствует snippets.
Может так случиться, что перед включением snippets вам потребуется зарегистрировать директорию /var/lib/vz/snippets/ в системе.
Всё что мы сделали выше можно проделать и в WebUI
Далее, перейдем в папку для сниппетов
cd /var/lib/vz/snippets/
Теперь выполним дамп user
конфигурации cloudinit в эту папку
qm cloudinit dump 1000 user > user.yml
Так, например, будет выглядеть user.yml
#cloud-config hostname: ubuntu-cloud fqdn: ubuntu-cloud manage_etc_hosts: true user: cloud password: $5$qz...GEiL/ ssh_authorized_keys: - ssh-rsa AAAAB3N ... cloud@cloud chpasswd: expire: False users: - default package_upgrade: true
Допишем конфигурацию в конец файла user.yml
packages: - qemu-guest-agent - docker.io runcmd: - echo "Hello cloud-init" > /cloud-init-hello.txt - systemctl enable qemu-guest-agent - systemctl start qemu-guest-agent
С помощью packages
установим доп. пакеты в систему.
С помощью runcmd
мы выполним произвольную команду, а также включим и запустим qemu-guest-agent
Вы можете ознакомиться со стандартом этой конфигурации здесь https://cloudinit.readthedocs.io/en/latest/
Рекомендую также ознакомиться с разделом Hostname данной статьи, где я дополняю runcmd, чтобы при клонировании VM каждый раз получать уникальный hostname.
Применим эту конфигурацию для нашей виртуальной машины
qm set 1000 --cicustom "user=local:snippets/user.yml"
Если вы решите снова выполнить команду qm cloudinit dump 1000 user
то вы по-прежнему получите старую конфигурацию (которая выполнена через WebUI);
Чтобы просмотреть, что указано в cicustom вы можете выполнить команду
qm config 1000 | grep cicustom
Удалить cicustom можно командой
qm set 1000 --delete cicustom
Кроме user
cicustom имеет поля meta
и network
. В рамках этой статьи эти поля не рассматриваются.
➕ Создание шаблона
Теперь когда наша VM полностью готова, превращаем её в шаблон.
qm template 1000
После этого WebUI больше не будет предлагать вам запускать эту VM но вы можете использовать операцию Clone.
👯♀️ Клонирование из шаблона
qm clone 1000 505 --full --name cloudy --storage local-lvm
505
– ID новой виртуальной машины
cloudy
– имя виртуальной машины (это имя используется и отображается только в WebUI, оно никак не влияет на hostname.
🏠 Hostname
По инструкции выше вы сможете создать виртуальную машину из шаблона с исполненным Cloud-Init. Но каждый раз, когда вы будете создавать таким образом VM вы будете получать один и тот же Hostname, что может привести к некоторым проблемам в будущем.
Я почитал документацию, посмотрел примеры, погугли, и, похоже, что Proxmox из коробки не поддерживает передачу в cloud-config каких-либо динамических данных которые можно было бы использовать в полях hostname
или fqdn
.
Можно вручную изменить hostname, но я решил это иначе.
Решение, которое будет предложено ниже скорее костыль, но оно работает.
Я напомню, что в рамках данной статьи я намеренно не использую сторонние решения автоматизации. Также, я не проверял свое решение временем в боевых условиях. Учитывайте это.
Я использовал SMBIOS Settings чтобы передать внутрь виртуальной машины ID виртуальной машины. Это необходимо для того, чтобы в runcmd можно было получить этот ID и использовать его для создания уникального значения hostname машины.
Мы приведем hostname к виду vm-<number>
Где vm-
– это статическая часть, а <number>
будет задаваться перед первым запуском виртуальной машины.
Изменим сниппет (файл) user.yml
, теперь он будет выглядеть так:
#cloud-config user: cloud password: $5$qzDhkVM1$NTT19i6fIi3NPJ.MdGIppu41EKuUbxQWRhVNSzGEiL/ ssh_authorized_keys: - ssh-rsa AAAA ... cloud@cloud chpasswd: expire: False users: - default package_upgrade: true packages: - qemu-guest-agent - docker.io runcmd: - systemctl enable qemu-guest-agent - systemctl start qemu-guest-agent - echo vm-$(dmidecode -s system-serial-number) > /etc/hostname - hostnamectl set-hostname $(cat /etc/hostname)
hostname: ubuntu-cloud fqdn: ubuntu-cloud manage_etc_hosts: true
- echo vm-$(dmidecode -s system-serial-number) > /etc/hostname - hostnamectl set-hostname $(cat /etc/hostname)
Теперь hostname
будет формироваться как vm-<number>.
Где number это серийный номер сервера. Осталось только этот system-serial-number передать внутрь виртуальной машины.
Делать мы это будем с помощью qm set
параметра --smbios1
.
Теперь команда создания и запуска виртуальной машины с hostname vm-601
может быть такой:
VMID=601 && \ qm clone 1000 $VMID --full --name vm-$VMID --storage local-lvm && \ qm set $VMID --smbios1 serial=$VMID &&\ qm start $VMID
🐸 Выводы
Если использовать Proxmox, то следует рассмотреть Terraform и Ansible для автоматизации создания виртуальных машин или их конфигурирования, поскольку шаблонизация cloud-config и передача данных в Cloud-Init, похоже, невозможна из коробки.
Вот и всё. Пишите в комменты замечания, ставьте лайки.
Возможно стоило бы еще описать здесь процедуру изменения размера диска, учитывая что мы используем .img в шаблоне с заранее заданными параметрами.