VMs & Storage Pools mit libvirt und virt-install

TL;DR

VMs reproduzierbar per Kommandozeile: Storage Pools (dir) für VM-Disks und ISOs anlegen, das Debian-13-ISO prüfen, VMs mit virt-install erstellen, per VNC/SSH installieren und als Template für schnelle Deployments generalisieren — die Grundlage für IaC mit OpenTofu.

Getestet auf
  • Debian 13 (Trixie, ISO 13.5.0) / libvirt 11.3 / 2026-06
Inhalt

Einleitung

In diesem Tutorial lernst du, wie du virtuelle Maschinen (VMs) über die Kommandozeile mit virt-install erstellst. Außerdem schauen wir uns an, wie wir Storage Pools mit virsh erstellen können. Anders als GUI-Tools wie virt-manager ermöglicht dir der CLI-Ansatz, VM-Erstellung zu automatisieren und in Infrastructure as Code (IaC) Workflows zu integrieren.

Warum Kommandozeile statt virt-manager?

virt-manager (GUI)virt-install (CLI)
Klicken, AuswählenReproduzierbar via Script
Nicht versionierbarYAML/Shell-Script in Git
Nur lokalRemote via SSH
ManuellAutomatisierbar (Ansible, OpenTofu)

Was erreichen wir?

  • ✅ Storage Pools für VM-Disks einrichten
  • ✅ ISO-Images herunterladen und verifizieren
  • ✅ VM-Erstellung via virt-install
  • ✅ VNC-Zugriff für Installation
  • ✅ Grundlage für OpenTofu/Terraform Integration

Zielgruppe: Homelab-Betreiber mit KVM-Host-Setup (siehe KVM-Virtualisierung Tutorial)


Voraussetzungen

  • KVM-Host mit installiertem libvirt (siehe KVM-Virtualisierung Tutorial)
  • Ansible-User mit libvirt-Berechtigungen (siehe KVM-Virtualisierung Tutorial)
  • VLAN-Netzwerk konfiguriert (z.B. VLAN 40)
  • SSH-Zugriff zum KVM-Host

Prüfen:

# KVM-Host erreichbar?
ssh ansible@192.168.40.100

# libvirtd läuft?
systemctl status libvirtd

# Ansible-User in libvirt-Gruppe?
groups ansible | grep libvirt

# qemu-utils installiert? (WICHTIG für QCOW2-Images) 
dpkg -l | grep qemu-utils

# Falls qemu-utils fehlt:
sudo apt update 
sudo apt install -y qemu-utils

Phase 1: Storage Pools einrichten

Storage Pools sind libvirt’s Abstraktionsschicht für Storage. Statt Disks direkt in /var/lib/libvirt/images/ zu legen, definierst du Pools für verschiedene Storage-Backends.

Was sind Storage Pools?

┌─────────────────────────────────────┐
│  VM-Disks                           │
│  ├── vm1.qcow2                      │
│  ├── vm2.qcow2                      │
│  └── vm3.qcow2                      │
└──────────────┬──────────────────────┘
               │ libvirt Storage Pool
┌─────────────────────────────────────┐
│  Backend (Directory, LVM, NFS, etc.)│
│  /var/lib/libvirt/images/           │
│  oder: /mnt/storage-pool/           │
└─────────────────────────────────────┘

Pool-Typen:

TypBackendUse Case
dirDirectoryEinfach, lokal (QCOW2, RAW)
lvmLVM Volume GroupPerformance, thin provisioning
netfsNFS/CIFSShared Storage (Cluster)
iscsiiSCSISAN
zfsZFS DatasetAdvanced Features

Wir nutzen: dir (einfach, flexibel)

Szenario 1: Separate Disk für VMs

Hardware: Zweite Disk /dev/sdb dediziert für VMs.

Schritt 1: Disk vorbereiten

# Auf KVM-Host als root/ansible mit sudo
# WARNUNG: Alle Daten auf /dev/sdb werden gelöscht!

# Disk-Info prüfen
lsblk

Erwartete Ausgabe:

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0   120G  0 disk 
├─sda1   8:1    0   512M  0 part /boot/efi
├─sda2   8:2    0   110G  0 part /
└─sda3   8:3    0    10G  0 part [SWAP]
sdb      8:16   0   500G  0 disk           <-- Diese Disk

Schritt 2: Partition und Dateisystem erstellen

# Partition erstellen (gesamte Disk)
sudo parted /dev/sdb --script mklabel gpt
sudo parted /dev/sdb --script mkpart primary ext4 0% 100%

# Dateisystem erstellen
sudo mkfs.ext4 -L vm-storage /dev/sdb1

# UUID herausfinden
sudo blkid /dev/sdb1

Ausgabe:

/dev/sdb1: LABEL="vm-storage" UUID="abc12345-..." TYPE="ext4"

Schritt 3: Mountpoint erstellen und einhängen

# Mountpoint erstellen
sudo mkdir -p /mnt/vm-storage

# In /etc/fstab eintragen (persistent)
echo "UUID=abc12345-... /mnt/vm-storage ext4 defaults,noatime 0 2" | \
    sudo tee -a /etc/fstab

# fstab über systemd neu einlesen
sudo systemctl daemon-reload

# Mounten
sudo mount -a

# Prüfen
df -h | grep vm-storage

Ausgabe:

/dev/sdb1       492G   73M  467G   1% /mnt/vm-storage

Schritt 4: Berechtigungen setzen

# libvirt-qemu User/Group benötigt Zugriff
sudo chown -R libvirt-qemu:kvm /mnt/vm-storage
sudo chmod 770 /mnt/vm-storage

Schritt 5: Storage Pool in libvirt definieren

# Pool-Definition erstellen
sudo virsh pool-define-as \
    vm-storage \
    dir \
    --target /mnt/vm-storage

# Pool starten
sudo virsh pool-start vm-storage

# Autostart aktivieren
sudo virsh pool-autostart vm-storage

# Status prüfen
sudo virsh pool-list --all

Ausgabe:

 Name         State    Autostart
---------------------------------
 default      active   yes
 vm-storage   active   yes

Details anzeigen:

sudo virsh pool-info vm-storage

Ausgabe:

Name:           vm-storage
UUID:           ...
State:          running
Persistent:     yes
Autostart:      yes
Capacity:       492.00 GiB
Allocation:     73.00 MiB
Available:      491.93 GiB

Szenario 2: Selbe Disk wie Host-System

Hardware: Nur eine Disk /dev/sda, VMs sollen auf separater Partition/Directory liegen.

Option A: Separate Partition (empfohlen bei Neuinstallation)

Während Host-Installation separate Partition für VMs erstellen:

/dev/sda1   512 MB   /boot/efi
/dev/sda2   100 GB   /
/dev/sda3    10 GB   swap
/dev/sda4   500 GB   /var/lib/libvirt/storage  <-- VM-Storage

Nach Installation:

# Mountpoint sollte bereits existieren
df -h | grep libvirt

# Falls nicht, siehe Szenario 1 (Partition erstellen)

Option B: Directory im Root-Filesystem (einfachste Lösung)

Vorteil: Keine Repartitionierung nötig Nachteil: VMs teilen sich I/O mit Host-OS

# Storage-Directory erstellen
sudo mkdir -p /var/lib/libvirt/storage

# Berechtigungen
sudo chown -R libvirt-qemu:kvm /var/lib/libvirt/storage
sudo chmod 770 /var/lib/libvirt/storage

Pool definieren:

sudo virsh pool-define-as \
    vm-storage \
    dir \
    --target /var/lib/libvirt/storage

sudo virsh pool-start vm-storage
sudo virsh pool-autostart vm-storage

Storage Pool für ISOs

Separate Pool für ISO-Images (saubere Trennung).

# ISO-Directory erstellen
sudo mkdir -p /var/lib/libvirt/isos
sudo chown -R libvirt-qemu:kvm /var/lib/libvirt/isos

# Pool definieren
sudo virsh pool-define-as \
    iso-storage \
    dir \
    --target /var/lib/libvirt/isos

sudo virsh pool-start iso-storage
sudo virsh pool-autostart iso-storage

# Prüfen
sudo virsh pool-list

Ausgabe:

 Name         State    Autostart
---------------------------------
 default      active   yes
 iso-storage  active   yes
 vm-storage   active   yes

Phase 2: Debian 13 ISO herunterladen

Schritt 1: Download-Verzeichnis vorbereiten

# In ISO-Pool wechseln
cd /var/lib/libvirt/isos

# Als ansible-User (mit sudo falls nötig)
# Falls keine Schreibrechte:
sudo chown ansible:libvirt /var/lib/libvirt/isos

Schritt 2: Debian 13 netinst ISO herunterladen

Offizielle Quelle: https://cdimage.debian.org/debian-cd/

# Debian 13.5.0 amd64 netinst
wget https://cdimage.debian.org/debian-cd/13.5.0/amd64/iso-cd/debian-13.5.0-amd64-netinst.iso

# Checksummen herunterladen
wget https://cdimage.debian.org/debian-cd/13.5.0/amd64/iso-cd/SHA512SUMS
wget https://cdimage.debian.org/debian-cd/13.5.0/amd64/iso-cd/SHA512SUMS.sign

Download-Größe: ~400 MB

Schritt 3: ISO-Integrität verifizieren

Wichtig: Immer Checksummen prüfen!

# SHA512-Checksumme prüfen
sha512sum -c SHA512SUMS 2>&1 | grep netinst

Erwartete Ausgabe:

debian-13.5.0-amd64-netinst.iso: OK

Falls FAILED: ISO ist korrupt, erneut herunterladen!

Schritt 4: GPG-Signatur verifizieren (optional, empfohlen)

# Debian Signing Keys importieren (einmalig)
gpg --keyserver keyring.debian.org --recv-keys 0x6294BE9B
gpg --keyserver keyring.debian.org --recv-keys 0x42028EA404AAD9AA

# Signatur prüfen
gpg --verify SHA512SUMS.sign SHA512SUMS

Erwartete Ausgabe:

gpg: Good signature from "Debian CD signing key <debian-cd@lists.debian.org>"

Warnung über Trust: Normal, solange “Good signature” erscheint ✅

Schritt 5: Dateiberechtigungen korrigieren

# ISO für libvirt lesbar machen
sudo chown libvirt-qemu:kvm debian-13.5.0-amd64-netinst.iso
sudo chmod 644 debian-13.5.0-amd64-netinst.iso

Phase 3: VM mit virt-install erstellen

Schritt 1: Netzwerk vorbereiten

Voraussetzung: VLAN-Netzwerk in libvirt definiert (siehe OPNsense Tutorial).

Prüfen:

sudo virsh net-list --all

Erwartete Ausgabe:

 Name          State    Autostart   Persistent
------------------------------------------------
 default       active   yes         yes
 vlan40-infra  active   yes         yes

Falls vlan40-infra fehlt: Siehe KVM-Virtualisierung Tutorial → Netzwerk-Sektion.

Schritt 2: VM-Parameter definieren

Best Practice: Parameter in Variablen für Reproduzierbarkeit.

# VM-Konfiguration
VM_NAME="test-vm-01"
VM_RAM=4096              # 4 GB RAM
VM_VCPUS=2               # 2 vCPUs
VM_DISK_SIZE=40          # 40 GB Disk
VM_NETWORK="vlan40-infra"
VM_OS_VARIANT="debian12" # Debian 13 nutzt debian12 Template
ISO_PATH="/var/lib/libvirt/isos/debian-13.5.0-amd64-netinst.iso"

OS-Varianten anzeigen:

osinfo-query os | grep -i debian

Ausgabe (Auszug):

 debian10        | Debian 10
 debian11        | Debian 11
 debian12        | Debian 12

Hinweis: Debian 13 (Trixie) nutzt debian12 Template bis offizielles Template verfügbar.

Schritt 3: VM erstellen via virt-install

sudo virt-install \
  --name="${VM_NAME}" \
  --ram="${VM_RAM}" \
  --vcpus="${VM_VCPUS}" \
  --disk pool=vm-storage,size="${VM_DISK_SIZE}",format=qcow2,bus=virtio \
  --cdrom="${ISO_PATH}" \
  --os-variant="${VM_OS_VARIANT}" \
  --network network="${VM_NETWORK}",model=virtio \
  --graphics vnc,listen=0.0.0.0 \
  --console pty,target_type=serial \
  --boot uefi \
  --noautoconsole

Parameter-Erklärung:

ParameterWertErklärung
--nametest-vm-01VM-Name (eindeutig)
--ram4096RAM in MB (4 GB)
--vcpus2Anzahl virtueller CPUs
--disk pool=.qcow2Pfad zur virtuellen Festplatte im Pool
--disk size=40Disk-Größe in GB
--disk format=qcow2Format: QCOW2 (Copy-on-Write, sparse)
--disk bus=virtioParavirtualisierter Controller (beste Performance)
--cdrom.isoISO-Datei für Installation
--os-variantdebian12OS-Typ für Optimierungen
--network network=vlan40-infraLibvirt-Netzwerk-Name
--network model=virtioParavirtualisierte NIC (beste Performance)
--graphicsvnc,listen=0.0.0.0VNC-Server auf allen Interfaces
--consoleptySerielle Konsole (virsh console)
--bootuefiModerner UEFI-Boot (statt Legacy BIOS)
--noautoconsole-Konsole nicht automatisch öffnen

Wichtige Hinweise:

bus=virtio: Paravirtualisierte Disk = deutlich bessere Performance als IDE/SATA format=qcow2: Sparse-File, wächst dynamisch (40 GB Limit, aber initial nur ~1 GB belegt) listen=0.0.0.0: VNC von überall erreichbar (Firewall beachten!)

Ausgabe:

Starting install...
Domain is still running. Installation may be in progress.
You can reconnect to the console to complete the installation process.

Schritt 4: VM-Status prüfen

# Alle VMs anzeigen
sudo virsh list --all

Ausgabe:

 Id   Name         State
----------------------------
 1    test-vm-01   running

Weitere Infos:

# VM-Details
sudo virsh dominfo test-vm-01

# VM-Disk-Info
sudo virsh domblklist test-vm-01

# VM-Netzwerk-Info
sudo virsh domiflist test-vm-01

Phase 4: VNC-Zugriff zur Installation

VNC-Port herausfinden

sudo virsh vncdisplay test-vm-01

Ausgabe:

127.0.0.1:0

Bedeutung:

  • 127.0.0.1 = Nur lokal (wegen unserer listen=0.0.0.0 sollte aber 0.0.0.0 stehen)
  • :0 = Display 0 → Port 5900 + 0 = 5900

Korrektur (falls 127.0.0.1):

# VM XML bearbeiten
sudo virsh edit test-vm-01

Suche nach <graphics type='vnc' und ändere:

<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
  <listen type='address' address='0.0.0.0'/>
</graphics>

Speichern: :wq

VM neu starten:

sudo virsh destroy test-vm-01
sudo virsh start test-vm-01

Variante 1: VNC direkt (nicht empfohlen für Production)

VNC-Viewer installieren (falls nicht vorhanden):

# Auf Client
sudo apt install tigervnc-viewer

Verbinden:

vncviewer 192.168.40.100:5900

Sicherheitshinweis: VNC ist unverschlüsselt! Nur in trusted Networks nutzen.

Variante 2: SSH-Tunnel (empfohlen)

Sicherer Zugriff via SSH-Port-Forwarding:

# Auf Client
ssh -L 5900:localhost:5900 ansible@192.168.40.100

In neuem Terminal:

vncviewer localhost:5900

Vorteil: Traffic durch SSH-Tunnel verschlüsselt ✅

Variante 3: virt-manager Remote

GUI-Alternative:

# Auf Client
virt-manager -c 'qemu+ssh://ansible@192.168.40.100/system'

In virt-manager:

  1. Doppelklick auf test-vm-01
  2. Konsolen-Fenster öffnet sich
  3. Debian Installer startet

Phase 5: Debian Installation (Detaillierte Schritte)

Installer starten

  1. Install-Methode wählen: Graphical Install

Installation durchführen

  • Language:
    • English
  • Location:
    • other → Europe → Germany
  • Locales:
    • en_US.UTF-8
  • Keyboard:
    • German
  • DHCP-Fehler:
    • Mit Continue bestätigen
  • Configure network manually
    • IP address: 192.168.40.120
    • Netmask: 255.255.255.0
    • Gateway: 192.168.40.1
    • Name server: 192.168.40.1
  • Hostname:
    • test-vm-01
  • Domain name:
    • home.arpa
  • Root password / Re-enter password to verify:
    • Starkes Passwort (mind. 16 Zeichen)
  • Full name for new user:
    • (leer lassen)
  • Username for your account:
    • ansible
  • Choose a password for the new user / Re-enter password to verify:
    • Starkes Passwort (mind. 16 Zeichen)
  • Wichtig:
    • User ansible wird für spätere Automation benötigt!
  • Partition disks:
    • Guided - use entire disk → Virtual disk 1 → All files in one partition → Finish partitioning and write changes to disk
  • Write changes to disk?
    • Yes
  • Scan for extra installation media?
    • No
  • Package Manager:
    • Germany → deb.debian.org
  • HTTP proxy:
    • (leer lassen)
  • Participate in the package usage survey?
    • No
  • Software selection:
    • Alles abwählen, dann nur auswählen:
      • SSH server
      • standard system utilities
  • Continue
    • to reboot

Nach dem Reboot

Die VM ist nun unter 192.168.40.120 per SSH erreichbar.

Autostart aktivieren

Damit die VM auch wieder hochfährt, wenn das Host-System mal crasht, aktivieren wir an der VM noch den Autostart.

sudo virsh autostart test-vm-01

Phase 6: Post-Installation

Schritt 1: SSH-Zugriff testen

In Phase 5 wurde die statische IP 192.168.40.120 direkt im Debian-Installer gesetzt. Die VM hat also keine DHCP-Adresse, sondern ist direkt unter dieser IP erreichbar.

SSH-Login:

ssh ansible@192.168.40.120

Falls die IP aus irgendeinem Grund unbekannt ist (z. B. beim Testen mit DHCP statt statischer IP), kannst du sie über die VNC-Konsole oder ARP ermitteln:

# Via VNC-Konsole in VM
ip addr show

# Oder via ARP vom Host
arp -a | grep -i "00:16:3e"  # libvirt MAC-Präfix

Schritt 2: Statische IP konfigurieren

In VM:

sudo nano /etc/network/interfaces

Inhalt:

auto lo
iface lo inet loopback

auto enp1s0
iface enp1s0 inet static
    address 192.168.40.120/24
    gateway 192.168.40.1
    dns-nameservers 192.168.40.1
    dns-search home.arpa

Wichtig: Interface-Name prüfen mit ip addr show.

# Netzwerk neu starten
sudo systemctl restart networking

# Testen
ping -c 3 192.168.40.1

Schritt 3: System aktualisieren

sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y

Schritt 4: VM herunterfahren

Via SSH:

sudo shutdown -h now

Oder via virsh:

sudo virsh shutdown test-vm-01

# Force (falls hängt)
sudo virsh destroy test-vm-01

Phase 7: VM-Management-Basics

VM starten/stoppen

# Starten
sudo virsh start test-vm-01

# Herunterfahren (sauber)
sudo virsh shutdown test-vm-01

# Ausschalten (unsauber, force)
sudo virsh destroy test-vm-01

# Neustart
sudo virsh reboot test-vm-01

VM-Autostart

# Autostart aktivieren (VM startet bei Host-Boot)
sudo virsh autostart test-vm-01

# Autostart deaktivieren
sudo virsh autostart --disable test-vm-01

# Status prüfen
sudo virsh dominfo test-vm-01 | grep Autostart

VM-Info anzeigen

# Alle VMs
sudo virsh list --all

# VM-Details
sudo virsh dominfo test-vm-01

# Ressourcen-Nutzung
sudo virsh domstats test-vm-01

# Disk-Info
sudo virsh domblklist test-vm-01

# Netzwerk-Info
sudo virsh domiflist test-vm-01

VM löschen

# VM stoppen
sudo virsh destroy test-vm-01

# VM aus libvirt entfernen
sudo virsh undefine test-vm-01

# Disk manuell löschen
sudo rm /var/lib/libvirt/storage/test-vm-01.qcow2

Achtung: undefine löscht nur die VM-Definition, nicht die Disk!


Phase 8: VM-Snapshots

Snapshots speichern den VM-Zustand zu einem bestimmten Zeitpunkt.

Snapshot erstellen

# VM sollte laufen
sudo virsh snapshot-create-as test-vm-01 \
    --name "fresh-install" \
    --description "Direkt nach Debian-Installation"

Snapshots anzeigen

sudo virsh snapshot-list test-vm-01

Ausgabe:

 Name           Creation Time               State
----------------------------------------------------
 fresh-install  2026-01-31 14:23:45 +0100   shutoff

Snapshot wiederherstellen

# VM stoppen
sudo virsh shutdown test-vm-01

# Auf Snapshot zurücksetzen
sudo virsh snapshot-revert test-vm-01 fresh-install

# VM starten
sudo virsh start test-vm-01

Snapshot löschen

sudo virsh snapshot-delete test-vm-01 fresh-install

Hinweis: Snapshots belegen zusätzlichen Speicherplatz!


Phase 9: VM-Templates erstellen

Idee: Basis-VM als Template für schnelle Deployments.

Schritt 1: Basis-VM vorbereiten

  1. Frische Debian-Installation
  2. System aktualisieren
  3. Wichtige Pakete installieren (vim, htop, etc.)
  4. SSH-Key hinterlegen
  5. WICHTIG: Hostname & statische IP nicht setzen (wird pro VM individuell)

Schritt 2: VM generalisieren

In VM:

# Machine-ID löschen (wird bei Boot neu generiert)
sudo rm /etc/machine-id
sudo touch /etc/machine-id

# SSH Host Keys löschen (werden bei Boot neu generiert)
sudo rm /etc/ssh/ssh_host_*

# Cloud-init (optional, für Automation)
sudo apt install cloud-init

VM herunterfahren:

sudo shutdown -h now

Schritt 3: Template speichern

# Disk als Template kopieren
sudo cp /var/lib/libvirt/storage/test-vm-01.qcow2 \
       /var/lib/libvirt/storage/debian13-template.qcow2

# Schreibschutz (verhindert versehentliche Änderung)
sudo chmod 444 /var/lib/libvirt/storage/debian13-template.qcow2

Schritt 4: Neue VM aus Template

# Template kopieren
sudo cp /var/lib/libvirt/storage/debian13-template.qcow2 \
       /var/lib/libvirt/storage/neue-vm.qcow2

# Schreibrechte wiederherstellen
sudo chmod 644 /var/lib/libvirt/storage/neue-vm.qcow2

# VM-Definition erstellen
sudo virt-install \
  --name="neue-vm" \
  --ram=4096 \
  --vcpus=2 \
  --disk path=/var/lib/libvirt/storage/neue-vm.qcow2,format=qcow2,bus=virtio \
  --import \
  --os-variant=debian12 \
  --network network=vlan40-infra,model=virtio \
  --graphics vnc,listen=0.0.0.0 \
  --noautoconsole

Parameter --import: Nutzt existierende Disk statt ISO!


Phase 10: Automation mit Scripten

Beispiel: VM-Creation Script

nano create-vm.sh

Inhalt:

#!/bin/bash
set -e

# Parameter
VM_NAME="$1"
VM_RAM="${2:-4096}"
VM_VCPUS="${3:-2}"
VM_DISK_SIZE="${4:-40}"

# Validierung
if [ -z "$VM_NAME" ]; then
    echo "Usage: $0 <vm-name> [ram_mb] [vcpus] [disk_gb]"
    exit 1
fi

# Pfade
ISO_PATH="/var/lib/libvirt/isos/debian-13.5.0-amd64-netinst.iso"
DISK_PATH="/var/lib/libvirt/storage/${VM_NAME}.qcow2"

# VM erstellen
echo "Creating VM: $VM_NAME"
echo "  RAM: ${VM_RAM} MB"
echo "  vCPUs: $VM_VCPUS"
echo "  Disk: ${VM_DISK_SIZE} GB"

sudo virt-install \
  --name="${VM_NAME}" \
  --ram="${VM_RAM}" \
  --vcpus="${VM_VCPUS}" \
  --disk path="${DISK_PATH}",size="${VM_DISK_SIZE}",format=qcow2,bus=virtio \
  --cdrom="${ISO_PATH}" \
  --os-variant=debian12 \
  --network network=vlan40-infra,model=virtio \
  --graphics vnc,listen=0.0.0.0 \
  --console pty,target_type=serial \
  --boot uefi \
  --noautoconsole

echo "VM created successfully!"
echo "Connect via: vncviewer $(hostname -I | awk '{print $1}'):5900"

Ausführbar machen:

chmod +x create-vm.sh

Nutzung:

# Standard (4 GB RAM, 2 vCPU, 40 GB Disk)
./create-vm.sh my-new-vm

# Custom
./create-vm.sh my-big-vm 8192 4 100

Vorbereitung für OpenTofu/Terraform

Terraform Provider: dmacvicar/libvirt

Später (OpenTofu-Tutorial, im Backlog): VMs deklarativ via HCL definieren.

Beispiel (Vorschau):

resource "libvirt_volume" "vm_disk" {
  name   = "my-vm.qcow2"
  pool   = "vm-storage"
  format = "qcow2"
  size   = 42949672960  # 40 GB
}

resource "libvirt_domain" "vm" {
  name   = "my-vm"
  memory = "4096"
  vcpu   = 2

  disk {
    volume_id = libvirt_volume.vm_disk.id
  }

  network_interface {
    network_name = "vlan40-infra"
  }
}

In Forgejo CI/CD: OpenTofu applied automatisch bei Git-Push — Details folgen in einem eigenen Tutorial.


Zusammenfassung

Wir haben gelernt:

Storage Pools einrichten (separate Disk oder Directory) ✅ ISO-Download und Integritätsprüfung ✅ VM-Erstellung via virt-installVNC-Zugriff für Installation ✅ VM-Management (start, stop, autostart) ✅ Snapshots für Backups ✅ Templates für schnelle Deployments ✅ Automation via Shell-Scripts

Workflow:

1. Storage Pool erstellen
2. ISO herunterladen & verifizieren
3. virt-install ausführen
4. Via VNC installieren
5. Post-Installation (SSH, Updates)
6. (Optional) Template erstellen

Nächste Schritte

Serienreihenfolge:

  1. Was ist ein Homelab? (Serie Teil 0)
  2. OPNsense: Eigene Firewall fürs Homelab mit VLANs (Teil 1)
  3. KVM-Virtualisierung auf Debian 13 einrichten (Teil 2)
  4. VMs & Storage Pools mit libvirt und virt-installDu bist hier
  5. Weiterer KVM-Node + Cluster (in Vorbereitung)

Wichtige Befehle (Cheat Sheet)

Storage Pools

# Pools anzeigen
sudo virsh pool-list --all

# Pool-Info
sudo virsh pool-info <pool-name>

# Pool starten
sudo virsh pool-start <pool-name>

# Pool stoppen
sudo virsh pool-destroy <pool-name>

# Pool löschen
sudo virsh pool-delete <pool-name>
sudo virsh pool-undefine <pool-name>

VM-Management

# VMs anzeigen
sudo virsh list --all

# VM starten
sudo virsh start <vm-name>

# VM stoppen (sauber)
sudo virsh shutdown <vm-name>

# VM stoppen (force)
sudo virsh destroy <vm-name>

# VM löschen
sudo virsh undefine <vm-name>
sudo rm /path/to/disk.qcow2

# VM-Info
sudo virsh dominfo <vm-name>

# Autostart
sudo virsh autostart <vm-name>
sudo virsh autostart --disable <vm-name>

Snapshots

# Snapshot erstellen
sudo virsh snapshot-create-as <vm> --name <snapshot-name>

# Snapshots anzeigen
sudo virsh snapshot-list <vm>

# Snapshot wiederherstellen
sudo virsh snapshot-revert <vm> <snapshot-name>

# Snapshot löschen
sudo virsh snapshot-delete <vm> <snapshot-name>

VNC

# VNC-Port anzeigen
sudo virsh vncdisplay <vm-name>

# VNC-Client verbinden
vncviewer <host-ip>:5900

# Via SSH-Tunnel
ssh -L 5900:localhost:5900 user@host
vncviewer localhost:5900

Fehlerbehebung

Problem: virt-install schlägt fehl mit “Permission denied”

Symptom:

ERROR    internal error: process exited while connecting to monitor

Lösung:

# Disk-Pfad Berechtigungen prüfen
ls -la /var/lib/libvirt/storage/

# Besitzer korrigieren
sudo chown libvirt-qemu:kvm /var/lib/libvirt/storage/*.qcow2

# SELinux/AppArmor (falls aktiviert)
sudo aa-complain /usr/sbin/libvirtd

Problem: VM bekommt keine IP via DHCP

Lösungen:

# 1. Netzwerk in libvirt aktiv?
sudo virsh net-list --all
sudo virsh net-start vlan40-infra

# 2. OPNsense DHCP für VLAN 40 aktiv?
# In OPNsense GUI prüfen

# 3. MAC-Adresse in DHCP-Range?
sudo virsh domiflist <vm-name>
# MAC notieren, in OPNsense Firewall-Logs prüfen

Problem: VNC-Verbindung verweigert

Lösungen:

# 1. VNC läuft?
sudo virsh vncdisplay <vm-name>

# 2. Firewall (auf KVM-Host)?
sudo ufw allow 5900/tcp

# 3. VNC auf 0.0.0.0?
sudo virsh edit <vm-name>
# <listen type='address' address='0.0.0.0'/> prüfen

Problem: Disk voll trotz QCOW2

QCOW2 wächst dynamisch, aber:

# Tatsächliche Größe
ls -lh /var/lib/libvirt/storage/vm.qcow2

# Virtuelle Größe
qemu-img info /var/lib/libvirt/storage/vm.qcow2

# Compact (Leerraum zurückgewinnen)
sudo qemu-img convert -O qcow2 vm.qcow2 vm-compact.qcow2
sudo mv vm-compact.qcow2 vm.qcow2

Weiterführende Ressourcen

Offizielle Dokumentation

Storage

Automation


Weiterführend


Changelog

  • 2026-06-24 — Phase 6 konsolidiert: “IP via DHCP”-Hinweis entfernt, da Phase 5 eine statische IP setzt; DHCP-Fallback als optionale Anmerkung behalten; “GitLab Tutorial”-Verweis auf “OpenTofu-Tutorial (Backlog)” geändert; Forgejo statt GitLab CI/CD; K3s-Schritte aus “Nächste Schritte” entfernt und durch aktuelle Serienreihenfolge mit internen Links ersetzt; Weiterführend + Changelog ergänzt.
  • 2026-01-25 — Erstveröffentlichung (Entwurf).