Packer und Proxmox
Worum geht es?
Ich brauche ein paar Windows-Agenten für eine CI/CD-Pipeline, die ich gerne automatisch mit Terraform ausrollen würde. Dafür brauche ich ein Image/Template. Also ein Windows 10 mit etwas vorinstallierter Software, etwas angepasster Konfiguration und natürlich am Ende mit einem Sysprep versehen, damit ich das Ganze auch in Masse ausrollen kann.
Voraussetzungen
Als erstes müsst ihr euch von der Proxmox-Webseite die aktuelle VirtIO-Treiber herunterladen und diese in ein Isoverzeichnis in Proxmox hochladen. Dazu legt ihr am besten noch direkt eure Windows 10 ISO, damit auch diese später eingebunden werden kann.
Packerfile
packer {
required_plugins {
proxmox = {
version = ">= 1.1.7"
source = "github.com/hashicorp/proxmox"
}
}
}
variable "proxmox_username" {
type = string
default = "USERNAME@pve"
}
variable "proxmox_password" {
type = string
sensitive = true
}
variable "proxmox_url" {
type = string
default = "https://IP_TO_PROXMOXSERVER:8006/api2/json"
}
source "proxmox-iso" "windows" {
proxmox_url = var.proxmox_url
insecure_skip_tls_verify = true
username = var.proxmox_username
password = var.proxmox_password
node = "NODENAME"
bios = "ovmf"
machine = "q35"
efi_config {
efi_storage_pool = "STORAGEPOOLNAME"
pre_enrolled_keys = true
efi_type = "4m"
}
# Storage
disks {
storage_pool = "STORAGEPOOLNAMEs"
type = "scsi"
disk_size = "100G"
format = "raw"
}
# Windows 10 ISONAME
iso_file = "STORAGENAME:iso/Windows_10.iso"
unmount_iso = true
#Ordner der als ISO eingebunden werden soll.
additional_iso_files {
cd_files = ["cd-folder/"]
#Name ist wichtig für die Unattended installation
cd_label = "Unattend"
iso_storage_pool = "STORAGE_FÜR_ISO"
unmount = true
device = "sata0"
}
additional_iso_files {
device= "sata1"
iso_file= "STORAGENAME:iso/virtio-win-0.1.240.iso"
unmount= true
}
template_name = "TEMPLATENAME"
template_description = "Created on: ${timestamp()}"
vm_name = "VMNAME"
memory = "8192"
cores = "8"
sockets = "1"
cpu_type = "host"
numa = true
os = "win10"
scsi_controller = "virtio-scsi-single"
cloud_init = false
# Network
network_adapters {
model = "e1000" #Damit wir keine Treiber benötigen und direkte LAN haben.
bridge = "vmbr0"
mac_address= "MACADDR" #Fest IP Gesetzt. -> SSH / WIN_RM
}
#ssh
communicator = "ssh"
ssh_username = "user"
ssh_password = "password"
ssh_host = "192.168.0.xxx"
ssh_timeout = "12h"
# Boot
boot_wait = "5s"
boot_command = [
"<enter><enter><enter><enter>"
]
}
build {
name = "Proxmox Build"
sources = ["source.proxmox-iso.windows"]
# Ausführen Sysprep von hier aus.
provisioner "powershell" {
pause_before = "20s"
inline = ["c:/Windows/System32/Sysprep/sysprep.exe /generalize /oobe /unattend:'C:/Program Files/Cloudbase Solutions/Cloudbase-Init/conf/Unattend.xml'"]
}
}
Aufbau der Datei
AAls erstes erstellen wir das Plugin, das die Kommunikation mit unserem Proxmoxserver ermöglicht. Dafür nutzen wir das von Hashicorp bereitgestellte Plugin. Die Anleitung dazu findet ihr hier.
packer {
required_plugins {
proxmox = {
version = ">= 1.1.7"
source = "github.com/hashicorp/proxmox"
}
}
}
Als Nächstes legen wir ein paar Variablen fest, die wir für unsere Konfiguration brauchen. Die können wir später beim Aufruf durch Packer ersetzen. Da wir die URL und den Benutzernamen nicht immer eingeben wollen, setzen wir hier direkt einen Standardwert. Für das Passwort lassen wir den Wert weg. Dadurch müssen wir ihn später beim Aufruf mit angeben.
variable "proxmox_username" {
type = string
default = "USERNAME@pve"
}
variable "proxmox_password" {
type = string
sensitive = true
}
variable "proxmox_url" {
type = string
default = "https://IP_TO_PROXMOXSERVER:8006/api2/json"
}
Im nächsten Absatz geht’s um die Definition einer sogenannten Source. Die brauchen wir später in unserem eigentlichen Build. Hier legen wir fest, wie die VM in Proxmox angelegt werden soll. Wir definieren CPU, RAM, Festplatte und so weiter.
Allerdings starten wir mit dem Typ und einem Namen. Als Typ nehmen wir in diesem Fall “proxmox-iso”, da wir ein neues Image anhand einer ISO-Datei erstellen wollen. Der Name, den wir hier vergeben (zweiter Parameter), ist später im Build wichtig, da wir dann darüber die richtige Source auswählen.
source "proxmox-iso" "windows" {
...
Als Nächstes schauen wir uns die Verbindungsoptionen an. Hier nutzen wir jetzt die Variablen, die wir weiter oben definiert haben. Wenn ihr bei euch schon ein gültiges SSL-Zertifikat im Proxmox hinterlegt habt, könnt ihr die Option “insecure_skip_tls_verify” direkt löschen. Ich habe bei mir kein SSL-Zertifikat hinterlegt und brauche diese Option deshalb. Wichtig ist hier noch, dass ihr den Namen der Node für eure Bedürfnisse anpasst.
proxmox_url = var.proxmox_url
insecure_skip_tls_verify = true
username = var.proxmox_username
password = var.proxmox_password
node = "NODENAME"
Im nächsten Schritt legen wir fest, wo sowohl die EFI-Disk als auch unsere eigentliche Festplatte abgelegt und konfiguriert wird. Achtet dabei bitte wieder darauf, die Storagepools an euer System anzupassen. Hier kommen die Namen der Pools rein, die ihr in eurer Proxmoxinstallation benutzt.
Als Festplatte legen wir hier eine 100 GB großeplatte im Format RAW an. Diese dient uns später als C-Partition.
bios = "ovmf"
machine = "q35"
efi_config {
efi_storage_pool = "STORAGEPOOLNAME"
pre_enrolled_keys = true
efi_type = "4m"
}
disks {
storage_pool = "STORAGEPOOLNAMEs"
type = "scsi"
disk_size = "100G"
format = "raw"
}
Jetzt kommen wir zu den Dateien, die unser Image repräsentieren. Als erstes legen wir fest, welche ISO-Datei unser “Haupt-ISO” ist. In unserem Fall ist das eine Windows-10-ISO.
Als Nächstes wollen wir alles, was sich im Ordner “cd-folder” befindet, als eigene ISO mit dem Namen “Unattend” laden. (In diesem Ordner legen wir später auch unser Unattended.xml für die unbeaufsichtigte Windowsinstallation ab.)
Als Drittes und Letztes laden wir hier noch die Treiber-CD für Proxmox.
# Windows 10 ISONAME
iso_file = "STORAGENAME:iso/Windows_10.iso"
unmount_iso = true
#Ordner der als ISO eingebunden werden soll.
additional_iso_files {
cd_files = ["cd-folder/"]
#Name ist wichtig für die Unattended installation
cd_label = "Unattend"
iso_storage_pool = "STORAGE_FÜR_ISO"
unmount = true
device = "sata0"
}
additional_iso_files {
device= "sata1"
iso_file= "STORAGENAME:iso/virtio-win-0.1.240.iso"
unmount= true
}
Nun kommen wir zu einigen Parametern: - Späterer Name des Templates und Beschreibung dieses - Name der VM - Speicher und CPU - Welchen Typ CPU wir nutzen wollen (Als Default würde ich hier “host” empfehlen)
template_name = "TEMPLATENAME"
template_description = "Created on: ${timestamp()}"
vm_name = "VMNAME"
memory = "8192"
cores = "8"
sockets = "1"
cpu_type = "host"
numa = true
os = "win10"
scsi_controller = "virtio-scsi-single"
cloud_init = false
Als nächstes definieren wir die Netzwerkkrarte und geben eine feste MAC-Adresse an. Diese benötigen wir, damit wir in unserem DHCP-Server diese MAC eine feste IP-Adresse zuweisen können. Mit dieser IP können wir weiter unten dann die SSH Verbindung definieren.
# Network
network_adapters {
model = "e1000" #Damit wir keine Treiber benötigen und direkte LAN haben.
bridge = "vmbr0"
mac_address= "MACADDR" #Fest IP Gesetzt. -> SSH / WIN_RM
}
Jetzt kommt der Teil, in dem wir die Steuerung nach der ersten Installation einrichten. Ich nutze aktuell SSH, weil ich mit WINRM in der Vergangenheit nicht so gute Erfahrungen gemacht habe.
Den SSHD (SSH-Server) kannst du bei Windows ganz einfach über die erweiterten Optionen installieren. Sobald der Server gestartet ist, greift Packer mit dem angegebenen Benutzernamen und Passwort darauf zu und steuert das System mit den Befehlen, die später im Buildabschnitt definiert werden.
Im Abschnitt “Boot” können wir ein paar Tastenkombinationen angeben, die nach der Wartezeit in der VM ausgeführt werden sollen. Gerade bei Windows- oder Linux-Installationen können wir hier z. B. das “Bitte eine Taste drücken” oder auch Parameter übergeben.
#ssh
communicator = "ssh"
ssh_username = "user"
ssh_password = "password"
ssh_host = "192.168.0.xxx"
ssh_timeout = "12h"
# Boot
boot_wait = "5s"
boot_command = [
"<enter><enter><enter><enter>"
]
Als letztes kommen wir zu den wirklichen Arbeitsanweisungen. In der Buildkonfiguration legen wir zunächst die erste Source fest, um den initialen Build zu starten und die VM wie oben beschrieben zu erstellen. Danach (oder auch schon vorher) können wir weitere Schritte einfügen – zum Beispiel einen Powershell-Befehl. Wenn alle Schritte erledigt sind, fahren wir die VM herunter und verwandeln sie wie oben angegeben in ein Template.
build {
# Setzt den Namen des Prozesses.
name = "Proxmox Build"
# Legt die Quelle des Prozesses fest. In diesem Fall wird das Source-Proxmox-ISO-Windows verwendet.
sources = ["source.proxmox-iso.windows"]
# Ausführen Sysprep von hier aus. Dieser Teil ist wichtig, um die VM vor dem Erstellen zu konfigurieren.
provisioner "powershell" {
pause_before = "20s"
inline = [
"c:/Windows/System32/Sysprep/sysprep.exe /generalize /oobe /unattend:'C:/Program Files/Cloudbase Solutions/Cloudbase-Init/conf/Unattend.xml'"
]
}
}
Nachdem alles durchgelaufen ist, haben wir jetzt ein Template, das wir dann z. B. mit Terraform ausrollen können.
Sobald alle Dateien fertig sind, lade ich sie auf Github hoch. Dort findet ihr dann auch eine Beispiel-Unattended.xml und eine minimale Einrichtung. Allerdings erst, wenn auch die anderen Teile fertig sind. Im nächsten Schritt installieren und konfigurieren wir Chocolatey und Boxstarter, um die wichtigen Komponenten zu installieren und einzurichten.