Skip to main content

My Experience with libvirt

My daily driver setup has been working exceptionally well. If you missed them, you can check out the previous posts for details:

Recently, I decided to harden my setup by writing custom AppArmor profiles and nftables rules. During my research, libvirt kept popping up in tutorials. In fact, most “QEMU/KVM” guides simply assume you are using it.

Initially, I decided against using libvirt because I wasn’t a fan of its design philosophy. However, I kept hearing that it can automatically generate AppArmor profiles and firewall rules for each VM. Furthermore, this article highlighted several things that libvirt genuinely simplifies. Intrigued, I decided to dive in and get some first-hand experience.

The plan was to migrate my existing VM setup (which relies on bare QEMU scripts) to libvirt to see if it was a good fit. I specifically wanted to evaluate the parts of libvirt I was previously skeptical about:

  • Reliance on a highly privileged daemon.
  • Configuration stored in managed XML files, which makes it harder to create reusable components or templates.

The Good Parts

  • As noted in this article, while bare QEMU/KVM lacks a stable API, libvirt provides a very stable XML API.
  • CPU pinning and lifecycle management (like gracefully shutting down a VM) are as simple as just a few lines of XML. With bare QEMU, I had to write a Python daemon to parse the QMP protocol just to achieve this. libvirt also seamlessly handles a lot of edge cases during shutdowns.

The OK Parts

  • domxml-from-native didn’t work for me; this might be a Debian-specific issue. Thankfully, it wasn’t too difficult to manually recreate the XML file from my existing QEMU arguments.
  • Instead of using virsh define, I found I could use virsh create. This reads from an XML config and creates a transient VM. I love this approach because it lets me maintain control over my XML configs, making it possible to use scripts or Jinja templates to define reusable templates.
  • The highly privileged daemon is a mixed bag. Sometimes root access is required to configure network interfaces or AppArmor profiles, but I generally prefer to isolate those operations. For example, I’d rather pre-configure the network interfaces and define AppArmor profiles in systemd service files to keep the daemon rootless. That said, it’s not a dealbreaker:
    • The Arch Wiki notes that the daemon isn’t strictly required in all scenarios.
    • I had already ended up writing my own custom daemons for CPU pinning and lifecycle management anyway.
  • Networking has its quirks. libvirt can add a managed TAP device, but out of the box, it lacks NAT and network filtering.
    • It can use my existing TAP device as an unmanaged interface, but then it won’t apply network filters.
    • I couldn’t seem to manually fix the name of the TAP device, which makes writing custom nftables rules a headache.
    • Network configurations have to be defined separately (though they can be created transiently).
    • While libvirt can auto-generate network filters, any custom logic requires a separate, permanent XML config. I’d much rather just write plain nftables rules directly.
  • libvirt automatically creates AppArmor profiles for each VM, restricting the VM to only read its own disk image. The profile libvirt provides is actually quite reusable. I’ve used it in the past for server projects, even if it’s not perfect.

The Bad Parts

  • libvirt pulls in a lot of dependencies. Most annoyingly, nwfilter has a hard dependency on iptables. I’ve heard libvirt supports nftables now, so this might just be another Debian-specific quirk.
  • It wasn’t easy to assign a unique user to each VM. Setting the DAC seclabel resulted in a permission error regarding “master-key.aes”. This is likely because /var/lib/libvirt/qemu is restricted exclusively to the libvirt-qemu user.
  • I couldn’t get sound to work at all (I use ALSA). I probably just needed to add the libvirt-qemu user to the right audio groups, but it was another hurdle.

Conclusion

It is undeniable that libvirt is highly scalable and offers a rock-solid API. However, for my specific use case, it makes 80% of the setup incredibly easy while making the last 20% frustratingly difficult. To use it efficiently, you really have to commit to speaking the “libvirt language”.

I could probably iron out the remaining kinks if I spent a few more hours on it, but I don’t see the point right now. I’ll likely reconsider it in the future when I start experimenting with disposable VMs.

Comments

Popular posts from this blog

Exploring Immutable Distros and Declarative Management

My current server setup, based on Debian Stable and Docker, has served me reliably for years. It's stable, familiar, and gets the job done. However, an intriguing article I revisited recently about Fedora CoreOS, rpm-ostree, and OSTree native containers sparked my curiosity and sent me down a rabbit hole exploring alternative approaches to system management. Could there be a better way? Core Goals & Requirements Before diving into new technologies, I wanted to define what "better" means for my use case: The base operating system must update automatically and reliably. Hosted services (applications) should be updatable either automatically or manually, depending on the service. Configuration and data files need to be easy to modify, and crucially, automatically tracked and backed up. Current Setup: Debian Stable + Docker My current infrastructure consists of several servers, all running Debian Stable. System Updates are andled automatically via unattended-upgrades. Se...

Qubes OS: First Impressions

A few days ago, while browsing security topics online, Qubes OS surfaced—whether via YouTube recommendations or search results, I can't recall precisely. Intrigued by its unique approach to security through compartmentalization, I delved into the documentation and watched some demos. My interest was piqued enough that I felt compelled to install it and give it a try firsthand. My overall first impression of Qubes OS is highly positive. Had I discovered it earlier, I might have reconsidered starting my hardware password manager project. Conceptually, Qubes OS is not much different from running a bunch of virtual machines simultaneously. However, its brilliance lies in the seamless desktop integration and the well-designed template system, making it far more user-friendly than a manual VM setup. I was particularly impressed by the concept of disposable VMs for temporary tasks and the clear separation of critical functions like networking (sys-net) and USB handling (sys-usb) into the...

Determine Perspective Lines With Off-page Vanishing Point

In perspective drawing, a vanishing point represents a group of parallel lines, in other words, a direction. For any point on the paper, if we want a line towards the same direction (in the 3d space), we simply draw a line through it and the vanishing point. But sometimes the vanishing point is too far away, such that it is outside the paper/canvas. In this example, we have a point P and two perspective lines L1 and L2. The vanishing point VP is naturally the intersection of L1 and L2. The task is to draw a line through P and VP, without having VP on the paper. I am aware of a few traditional solutions: 1. Use extra pieces of paper such that we can extend L1 and L2 until we see VP. 2. Draw everything in a smaller scale, such that we can see both P and VP on the paper. Draw the line and scale everything back. 3. Draw a perspective grid using the Brewer Method. #1 and #2 might be quite practical. #3 may not guarantee a solution, unless we can measure distances/p...