Mensen denken vaak dat je VMware, Hyper-V, of minstens Proxmox nodig hebt om een “echte” hypervisor te draaien. Iets met een web UI, enterprise features, het hele pakket.

Maar hier is het ding: KVM met libvirt kan vrijwel alles wat die commerciële hypervisors doen. Live migratie, memory ballooning, CPU pinning, GPU passthrough, SR-IOV, nested virtualization — het is er allemaal. De Linux kernel is al meer dan tien jaar een production-grade hypervisor.

Ik draai NixOS als mijn hypervisor. Geen Proxmox, geen web UI, alleen declaratieve Nix configs en virsh. Laat me je tonen wat er mogelijk is.

De Basis: KVM en QEMU

Voordat we induiken, even de stack verduidelijken:

  • KVM (Kernel-based Virtual Machine): Een Linux kernel module die je kernel verandert in een type-1 hypervisor. Hardware-versneld, near-native performance.
  • QEMU: De userspace emulator die virtuele hardware (disks, netwerkkaarten, etc.) levert aan VMs.
  • libvirt: Een management laag die een consistente API biedt over verschillende virtualisatietechnologieën. Bevat de virsh CLI.

Samen drijft deze stack alles aan van de VMs op je laptop tot massieve cloud infrastructuur. AWS’s Nitro hypervisor? Gebouwd op KVM. Google Cloud? KVM. Het grootste deel van de cloud draait op deze technologie.

NixOS Configuratie

Zo stel ik NixOS in als hypervisor:

{ config, pkgs, ... }:

{
  # Enable virtualization
  virtualisation.libvirtd = {
    enable = true;
    qemu = {
      package = pkgs.qemu_kvm;
      runAsRoot = true;
      swtpm.enable = true;  # TPM emulatie
      ovmf = {
        enable = true;  # UEFI support
        packages = [ pkgs.OVMFFull.fd ];
      };
    };
  };

  # Voeg jezelf toe aan de libvirt groep
  users.users.tom = {
    extraGroups = [ "libvirtd" ];
  };

  # Handige tools
  environment.systemPackages = with pkgs; [
    virt-manager    # GUI als je wilt
    virt-viewer     # Voor console access
    libguestfs      # VM image manipulatie
    win-virtio      # Windows VirtIO drivers
  ];

  # Bridge networking (optioneel, voor bridged VMs)
  networking.bridges.br0.interfaces = [ "enp1s0" ];
  networking.interfaces.br0.useDHCP = true;
}

Pas deze config toe, en je hebt een volledig functionele hypervisor. Dat is het. Geen installer, geen setup wizard, alleen Nix.

VMs Maken met virsh

Laten we een VM maken. Eerst een disk alloceren:

# Maak een 50GB qcow2 disk
qemu-img create -f qcow2 /var/lib/libvirt/images/ubuntu-server.qcow2 50G

Definieer dan de VM met XML (of gebruik virt-install):

virt-install \
  --name ubuntu-server \
  --memory 4096 \
  --vcpus 2 \
  --disk /var/lib/libvirt/images/ubuntu-server.qcow2 \
  --cdrom /var/lib/libvirt/images/ubuntu-24.04-server.iso \
  --os-variant ubuntu24.04 \
  --network bridge=br0 \
  --graphics vnc

Nu heb je een draaiende VM. Beheer het met:

virsh list --all           # Lijst alle VMs
virsh start ubuntu-server  # Start VM
virsh shutdown ubuntu-server  # Graceful shutdown
virsh destroy ubuntu-server   # Force stop
virsh console ubuntu-server   # Seriële console

Live Migratie

Hier denken mensen dat je enterprise software nodig hebt. Dat is niet zo.

Live migratie verplaatst een draaiende VM van de ene host naar de andere met minimale downtime. De VM blijft draaien terwijl het geheugen gekopieerd wordt, dan gebeurt de finale switchover in milliseconden.

Vereisten:

  • Gedeelde storage (NFS, Ceph, GlusterFS) of zelfde disk pad op beide hosts
  • Zelfde CPU architectuur (of gebruik CPU model compatibiliteit)
  • libvirt draaiend op beide hosts
# Vanaf de bron host
virsh migrate --live ubuntu-server qemu+ssh://target-host/system

# Met opties voor betere performance
virsh migrate --live --persistent --undefinesource \
  --copy-storage-all \
  ubuntu-server qemu+ssh://target-host/system

Dat is het. Je VM draait nu op de andere host. De --copy-storage-all flag kopieert zelfs de disk als je geen gedeelde storage hebt.

Voor gescripte omgevingen kun je dit programmatisch doen:

#!/bin/bash
# Simpele load balancer - migreer VM naar minst belaste host

HOSTS=("host1" "host2" "host3")
VM="ubuntu-server"

# Vind host met laagste load
TARGET=$(for h in "${HOSTS[@]}"; do
  echo "$(ssh $h 'cat /proc/loadavg | cut -d" " -f1') $h"
done | sort -n | head -1 | cut -d" " -f2)

virsh migrate --live $VM qemu+ssh://$TARGET/system

Memory Ballooning

Memory ballooning laat je VM-geheugen dynamisch aanpassen zonder herstarten. De balloon driver van de VM geeft geheugen terug aan de host of vraagt meer aan.

Zet het aan in je VM definitie:

<memballoon model='virtio'>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
</memballoon>

Pas dan aan tijdens runtime:

# Zet huidig geheugen op 2GB (VM heeft 4GB max)
virsh setmem ubuntu-server 2G --live

# Geef weer 4GB terug
virsh setmem ubuntu-server 4G --live

Dit is ongelooflijk handig voor memory overcommitting. Je kunt 64GB toewijzen over VMs op een 32GB host, en de balloon driver balanceert het daadwerkelijke gebruik.

GPU Passthrough

Dit is de holy grail voor homelab enthousiastelingen: een fysieke GPU direct doorgeven aan een VM voor near-native graphics performance. Gaming VMs, machine learning workloads, transcoding — allemaal mogelijk.

Configureer eerst IOMMU in je NixOS config:

{
  boot.kernelParams = [
    "intel_iommu=on"  # of amd_iommu=on voor AMD
    "iommu=pt"
  ];

  # Isoleer de GPU van de host
  boot.extraModprobeConfig = ''
    options vfio-pci ids=10de:2204,10de:1aef
  '';

  boot.initrd.kernelModules = [
    "vfio_pci"
    "vfio"
    "vfio_iommu_type1"
  ];
}

Vind de PCI IDs van je GPU:

lspci -nn | grep -i nvidia
# 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation ... [10de:2204]
# 01:00.1 Audio device [0403]: NVIDIA Corporation ... [10de:1aef]

Voeg dan de GPU toe aan je VM:

<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
    <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
  </source>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
    <address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
  </source>
</hostdev>

De VM heeft nu directe toegang tot de GPU. Installeer de native drivers in de guest, en je krijgt volledige hardware acceleratie.

Netwerkkaart Passthrough en SR-IOV

Hetzelfde principe werkt voor netwerkkaarten. Voor 10GbE of 25GbE kaarten wil je misschien directe passthrough:

# Vind de NIC
lspci -nn | grep -i ethernet

# Geef door zoals de GPU

Maar de echte kracht is SR-IOV (Single Root I/O Virtualization). Het splitst één fysieke NIC in meerdere virtual functions, elk toewijsbaar aan een andere VM:

# Enable 4 virtual functions op de NIC
echo 4 > /sys/class/net/enp5s0f0/device/sriov_numvfs

# Lijst ze
lspci | grep "Virtual Function"

Elke VF krijgt zijn eigen PCI adres en kan worden doorgegeven aan een VM. Hardware-versnelde networking zonder de overhead van virtio.

CPU Pinning

Voor latency-gevoelige workloads wil je dat VMs specifieke CPU cores gebruiken zonder gemigreerd te worden door de scheduler:

<vcpu placement='static'>4</vcpu>
<cputune>
  <vcpupin vcpu='0' cpuset='4'/>
  <vcpupin vcpu='1' cpuset='5'/>
  <vcpupin vcpu='2' cpuset='6'/>
  <vcpupin vcpu='3' cpuset='7'/>
  <emulatorpin cpuset='0-1'/>
</cputune>

Dit pint de 4 vCPUs van de VM aan host cores 4-7, met emulator threads op cores 0-1. Gecombineerd met isolcpus in je kernel parameters krijg je near-bare-metal latency.

Declaratief VM Beheer op NixOS

Hier schittert NixOS. Je kunt VMs declaratief definiëren:

{
  virtualisation.libvirtd.enable = true;

  # Definieer VMs als Nix expressies
  systemd.services."libvirt-guest-ubuntu" = {
    after = [ "libvirtd.service" ];
    requires = [ "libvirtd.service" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      Type = "oneshot";
      RemainAfterExit = "yes";
    };
    script = ''
      ${pkgs.libvirt}/bin/virsh define /etc/libvirt/qemu/ubuntu-server.xml || true
      ${pkgs.libvirt}/bin/virsh start ubuntu-server || true
    '';
    preStop = ''
      ${pkgs.libvirt}/bin/virsh shutdown ubuntu-server || true
      sleep 10
      ${pkgs.libvirt}/bin/virsh destroy ubuntu-server || true
    '';
  };
}

Of gebruik NixVirt voor een meer geïntegreerde aanpak:

{
  virtualisation.libvirt.connections."qemu:///system" = {
    domains = [{
      definition = ./vms/ubuntu-server.xml;
      active = true;
    }];
  };
}

Je VMs zijn nu onderdeel van je NixOS configuratie. Version controlled, reproduceerbaar, declaratief.

De Limieten van CLI-Only

Ik draai deze setup al jaren, en het werkt goed. Maar ik ga niet doen alsof het allemaal rozengeur en maneschijn is.

Wat vervelend wordt:

  • VMs maken: virt-install heeft een miljoen flags. Ik heb wrapper scripts, maar mooi is het niet.
  • Monitoring: Je eindigt met scripts schrijven om VM status, resource gebruik en health te checken. Of grafana dashboards met libvirt exporters.
  • Storage beheer: Pools maken, volumes, snapshots — allemaal mogelijk met virsh, maar de commando’s zijn verbose.
  • Netwerk visualisatie: Welke VM zit op welke bridge? Welke heeft welk IP? Je hebt scripts of goede documentatie nodig.

Wat scripts niet makkelijk kunnen:

  • Snel visueel overzicht: Hoe doen mijn VMs het? Wat is het resource gebruik? Een dashboard verslaat virsh list plus virsh domstats plus parsen.
  • Console toegang: virt-viewer werkt, maar een web-based console is handiger voor remote beheer.
  • Delegatie: Wil je iemand anders specifieke VMs laten beheren zonder volledige host access? Dat is complex met raw libvirt.

Waarom Proxmox Bestaat

Dit brengt me bij Proxmox. Het is dezelfde onderliggende technologie — KVM, QEMU, libvirt — maar verpakt in een web UI die alles toegankelijker maakt.

Ik zeg niet dat je Proxmox nodig hebt. Voor een homelab waar jij de enige admin bent, is NixOS + virsh + scripts prima werkbaar. Ik heb het jaren zo gedraaid.

Maar er zit echte waarde in een goede UI:

  • Onboarding: Nieuw teamlid begrijpt de infrastructuur direct
  • Emergency response: Om 3 uur ’s nachts is klikken makkelijker dan virsh syntax onthouden
  • Zichtbaarheid: Zie alle VMs, hun status, resource gebruik, netwerk topologie in één oogopslag
  • Geïntegreerde features: Backup scheduling, HA clustering, firewall regels — geconfigureerd op één plek

Proxmox voegt ook clustering, Ceph integratie en backup beheer toe dat significante moeite zou kosten om te repliceren met scripts.

Mijn Aanbeveling

Als je virtualisatie diepgaand wilt leren, begin met NixOS + KVM + virsh. Je begrijpt precies wat er gebeurt. Geen magie, geen abstractie. Elke feature die de “enterprise” hypervisors adverteren, kun je zelf implementeren.

Als je virtualisatie in productie moet draaien of met een team, overweeg Proxmox. De UI is niet alleen gemak — het is operationele gezondheid.

En als je zoals ik bent — een homelab draait waar leren deel van de lol is — is NixOS als hypervisor een geweldige keuze. Declaratieve configs, volledige controle, en de voldoening van weten dat er geen vendor lock-in is. Gewoon Linux, doend wat Linux het beste doet.


De beste hypervisor is degene die je begrijpt. KVM geeft je dat begrip. Of je het verpakt in Proxmox, NixOS, of raw scripts is een kwestie van voorkeur en use case.