Tech
February 22, 2024

Proxmox и Cloud-Init

Proxmox я использую для ознакомления дома на стационарном ПК.

Всё что я делаю с ним сейчас, это эксперименты и проверка некоторых моих гипотез относительно моего представления о продукте и его реального функционала.

Важно отметить, что некоторые решения, такие как Terraform или Ansible, сознательно не используются для решения задачи в данной статье.

🎯 Задача

Поскольку Proxmox позволяет создавать шаблоны виртуальных машин и имеет поддержку Cloud-Init (стандарт для инициализации экземпляра), я задался целью ознакомиться с этими возможностями через призму собственного опыта и законспектировать:

  1. Создание виртуальной машины из снимка диска с Ubuntu
  2. Создание и использование шаблона виртуальной машины
  3. Использование Cloud-Init и конфигурации cloud-config
  4. Шаблонизирование Hostname виртуальной машины
  5. Выводы

🍺 Подготовка

Я установил 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.

Включим 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

Включаем Snippets

Далее, перейдем в папку для сниппетов

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

1000 – ID нашего шаблона

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

Добавлены в runcmd :

  - 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, похоже, невозможна из коробки.

Вот и всё. Пишите в комменты замечания, ставьте лайки.

P.S.

Возможно стоило бы еще описать здесь процедуру изменения размера диска, учитывая что мы используем .img в шаблоне с заранее заданными параметрами.