Как сделать облако дома с Proxmox

Однажды я понял, что хочу домашний сервер для экспериментов с деплоем и инфраструктурой. В значительной степени этому поспособствовали посты на r/homelab и r/selfhosted.

Изначально я хотел пойти по пути bare-metal и собрать k3s кластер на нескольких RaspberryPi, но сравнив стоимость, ограничения и возможности, я решил купить пару мини-ПК Lenovo ThinkCentre и установить на них Proxmox Virtual Environment (PVE).

В рабочих целях я активно использовал Selectel и Yandex.Cloud, поэтому для меня было важно удобство работы с виртуальными серверами, аналогичное тому, что предоставляет compute cloud.

Основные требования к инфраструктуре виртуализации:

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

Подготовка приватной сети

Я решил обеспечить минимальную безопасность, выделив виртуальные серверы в отдельную от других устройств сеть.

Я не стал тратить время на VLAN, сложную маршрутизацию и межсетевые экраны, а использовал отдельный Linux Bridge на хосте PVE.

На диаграмме ниже отражена концептуальная схема сети. Все VM подключаются к интерфейсу приватной сети vmbr1. VM, которые должны быть доступны из домашней сети, дополнительно подключаются к интерфейсу vmbr0. Для обеспечения доступа в интернет приватных VM реализуется NAT из vmbr1 через vmbr0.

Router PC PVE Host VM1 Web Server VM2 Database VM3 Storage vmbr1 10.x.x.x vmbr0 192.168.1.x NAT

Интерфейс для приватной сети можно добавить через панель управления Proxmox, либо стандартными средствами Debian.

Рассмотрим второй вариант и добавим следующую конфигурацию в файл /etc/network/interfaces:

auto vmbr1
iface vmbr1 inet static
    address 10.0.2.1/24
    bridge-ports none
    bridge-stp off
    bridge-fd 0

Новый интерфейс необходимо активировать командной:

ifup vmbr1

Созданный интерфейс должен отобразиться в панели управления Proxmox.

Скриншот настроек network из панели управления Proxmox после добавления нового интерфейса

Для доступа VM в интернет необходимо настроить NAT.

В файле /etc/sysctl.conf нужно включить IP-форвардинг, добавив следующую строку:

net.ipv4.ip_forward=1

Сразу же применить эту настройку, выполнив команду:

sysctl -w net.ipv4.ip_forward=1

Затем необходимо включить NAT в iptables, выполнив команду:

iptables -t nat -A POSTROUTING -s 10.0.2.0/24 -o vmbr0 -j MASQUERADE

Здесь vmbr0 — имя интерфейса, имеющего доступ в интернет.

Для сохранения настроек iptables между перезагрузками, необходимо установить пакет iptables-persistent:

apt install iptables-persistent

Сеть сконфигурирована и можно переходить к следующему этапу.

Подготовка образа

Для создания VM аналогично облаку, нужен специальный облачный образ Debian.

Также понадобятся инструменты для модификации образов виртуальных машин:

apt install libguestfs-tools

Команда для скачивания и подготовки образа к использованию в шаблоне:

wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2 \
&& virt-customize -a debian-12-generic-amd64.qcow2 --install qemu-guest-agent,net-tools \
&& virt-customize -a debian-12-generic-amd64.qcow2 --run-command "echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen" \
&& virt-customize -a debian-12-generic-amd64.qcow2 --run-command "locale-gen" \
&& virt-customize -a debian-12-generic-amd64.qcow2 --run-command "update-locale LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8" \
&& virt-customize -a debian-12-generic-amd64.qcow2 --truncate /etc/machine-id

В аргументе --install можно указать дополнительные пакеты, которые будут автоматически установлены на всех серверах. Пакет qemu-guest-agent необходим для работы агента PVE.

Также важно вызвать --truncate /etc/machine-id, чтобы уникальный идентификатор генерировался для каждой копируемой VM.

Манипуляции с локалью не обязательны, но помогают избежать мусорных сообщений об ошибках локали при работе по SSH.

После того как образ подготовлен, необходимо создать непосредственно шаблон VM в Proxmox. Для этого можно выполнить следующую команду:

QM_ID=9001 \
&& qm create $QM_ID --name "debian12-cloudinit" --memory 512 --cores 1 --net0 virtio,bridge=vmbr1 --machine q35 \
&& qm importdisk $QM_ID debian-12-generic-amd64.qcow2 local-lvm \
&& qm set $QM_ID --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-$QM_ID-disk-0 \
&& qm set $QM_ID --boot c --bootdisk scsi0 \
&& qm set $QM_ID --ide2 local-lvm:cloudinit \
&& qm set $QM_ID --serial0 socket --vga serial0 \
&& qm set $QM_ID --agent enabled=1 \
&& qm template $QM_ID

Значения идентификатора шаблона 9001 и его названия "debian12-cloudinit" могут быть изменены на любые произвольные. Ресурсы и настройки сети будут переопределяться для каждого копируемого инстанса.

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

Terraform

Для работы с Proxmox через Terraform или OpenTofu можно использовать провайдер telmate/proxmox.

Из соображений безопасности рекомендуется создать отдельного пользователя с ограниченным набором прав. Однако в контексте домашнего использования с доступом только из локальной сети этот шаг можно пропустить.

Содержимое файла provider.tf:

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.2-rc03" # последняя версия на момент написания статьи: 3.0.2-rc07
    }
  }
}

provider "proxmox" {
  pm_api_url      = "https://${var.pm_host}:8006/api2/json"
  pm_user         = "${var.pm_user}@pam"
  pm_password     = var.pm_password
  pm_tls_insecure = true
}

Содержимое файла variables.tf:

variable "pm_user" {}
variable "pm_password" { sensitive = true }
variable "pm_host" {}
variable "ssh_key" { default = "~/.ssh/id_rsa.pub" }

После установки провайдера командой terraform init можно описывать инфраструктуру также как при работе с облачными поставщиками.

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

Пример создания виртуального сервера, доступного только в приватной сети:

locals {
  debian_template    = "debian12-cloudinit"
  debian_template_id = 9001
}


resource "proxmox_vm_qemu" "internal-vm" {
  vmid        = 310
  name        = "internal-vm"
  target_node = "pve-1"
  clone       = local.debian_template
  onboot      = true
  cpu {
    cores = 1
  }
  memory           = 512
  boot             = "order=scsi0"
  scsihw           = "virtio-scsi-single"
  agent            = 1
  vm_state         = "running"
  automatic_reboot = true

  ciupgrade = false
  ipconfig0 = "ip=10.0.2.11/24,gw=10.0.2.1"
  skip_ipv6 = true
  ciuser    = "cloud"
  sshkeys   = file(var.ssh_key)

  serial {
    id = 0
  }

  disks {
    scsi {
      scsi0 {
        disk {
          storage = "local-lvm"
          size    = "8G"
        }
      }
    }
    ide {
      ide1 {
        cloudinit {
          storage = "local-lvm"
        }
      }
    }
  }

  network {
    id     = 0
    bridge = "vmbr1"
    model  = "virtio"
  }
}

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

# ...

resource "proxmox_vm_qemu" "jump-host" {
  vmid        = 110
  name        = "jump-host"
  target_node = "pve-1"
  clone_id     = local.debian_template_id
  # ...
  ipconfig0 = "ip=10.0.2.10/24,gw=10.0.2.1"
  ipconfig1 = "ip=dhcp"

  # ...

  network {
    id     = 0
    bridge = "vmbr1"
    model  = "virtio"
  }

  network {
    id      = 1
    bridge  = "vmbr0"
    model   = "virtio"
    macaddr = "AA:24:11:75:01:10"
  }
}

MAC-адрес VM фиксируется для создания статических IP на стороне DHCP сервера. Созданные выше VM доступны по SSH через jump-host под пользователем cloud и SSH-ключу, указанному в переменных Terraform.

Особенности работы с кластером PVE

Для управления инфраструктурой кластера можно подключаться к API любой его ноды. В PVE нет «ведущего» хоста. Через подключение к API одной ноды можно создавать и модифицировать ресурсы на других нодах.

Однако если в состоянии Terraform есть ресурсы, которые находятся на выключенной ноде, TF будет падать с ошибкой. Поэтому при использовании Terraform, должны быть доступны все ноды кластера с управляемыми ресурсами.

Ошибка кворума

Для тестовой имитации распределённых систем я использую кластер из двух хостов. Большую часть времени мне не нужны оба хоста PVE, и включен только один.

По умолчанию такой подход приводит к ошибке «cluster not ready - no quorum?» и полной неработоспособности Proxmox.

Чтобы кластер мог работать даже с одной нодой, необходимо выполнить команду:

pvecm expect 1

Сделать настройку персистентной можно, добавив в файл /etc/pve/corosync.conf три строчки:

# ...

quorum {
  # ... defaults

  expected_votes: 1
  two_node: 0
  wait_for_all: 0
}

# ...

Работа с шаблонами

Так как образ шаблона VM хранится в локальной файловой системе, необходимо создать его на каждом хосте.

Proxmox ожидает уникальные идентификаторы серверов и шаблонов на уровне кластера, поэтому: