profile picture

Michael Stapelberg

My KVM best practices (2012) (2012)

published 2012-11-21, last modified 2018-03-18
Edit Icon

KVM stands for Kernel-based Virtual Machine and is my preferred way of virtualizing a Linux system, in combination with libvirt. This article documents a few hints on what my current best practice setup is.

For the host and most VMs, I use Debian wheezy (which is not yet released as of writing this article). I want a recent kernel and, most importantly, systemd.

Networking

An easy setup is to just use a bridge which contains the real ethernet device (eth0). However, I have noticed a few problems with IPv6 routes on bridge devices which turned out to be kernel bugs (1, 2, possibly I forgot some). The symptom is similar to this post by Marc, essentially leading to VMs which don’t come up properly after rebooting the host or even rebooting just the VM.

Therefore, I use tap devices instead. They are a bit complicated to setup. See the libvirt documentation on generic ethernet connections and this libvirt wiki page, which explains how you can lower the host protection to be able to use these interfaces. Here is the XML part of the VM definition which I use:

<interface type='ethernet'>
  <mac address='52:54:00:fa:f1:f1'/>
  <script path='/etc/libvirt/qemu/networks/ifup-infra.sh'/>
  <target dev='tap.infra'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</interface>

The script /etc/libvirt/qemu/networks/ifup-infra.sh contains:

#!/bin/sh
#
# Network configuration for kvm domain "infra"
#
# Generate a random (but fix) MAC address:
#     echo -n 'f6:'; openssl rand -hex 5 | sed 's/\(..\)/\1:/g; s/.$//'
# F6 is a prefix which is considered Locally Administered because the second
# lowest bit of the first byte is set to 1.

set -e

/sbin/ip link set $1 address f6:63:cb:bb:59:e1
/sbin/ip link set $1 up
/sbin/ip -4 route add 79.140.39.194/32 dev $1
/sbin/ip -4 route add 79.140.39.198/32 via 79.140.39.194 dev $1
/sbin/ip -6 route add 2001:4d88:100e:1::/64 dev $1
# RZL VPN
/sbin/ip -6 route add 2001:4d88:100e:ccc0::/64 via 2001:4d88:100e:1::2 dev $1
/sbin/ip -6 route add 2001:4d88:100e:ccc::/64 via 2001:4d88:100e:1::2 dev $1

The script sets a static, locally administered MAC address on the tap interface on the host. This is necessary so that the VM can use the same link-local address as default route.

IP Addresses

On the host, I have a /27 IPv4 network and a /48 IPv6 network. Previously, I used an IP address configuration such as 79.140.39.194/27 inside the VM. However, that only works when you have no special routes such as an IP address you route into a VPN (or you add routes within each VM which is error-prone and tiresome).

Therefore, I configure IPs in the VM with a /32 netmask, e.g. 79.140.39.194/32. The default IPv4 route within each VM is 192.168.23.23, so that I don’t lose one of my precious public IPs for that purpose:

host # ip -4 address add 192.168.23.23/32 dev lo
vm # ip -4 route add 192.168.23.23 dev eth0
vm # ip -4 route add default via 192.168.23.23

For IPv6, we use a link-local address, which depends on the MAC address we set earlier in ifup-infra.sh:

vm # ip -6 route add default via fe80::f463:cbff:febb:59e1 dev eth0

For the reference, here is the entire /etc/network/interfaces of a VM:

auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
    address 79.140.39.194
    netmask 255.255.255.255
    # dns-* options are implemented by the resolvconf package, if installed
    dns-nameservers 80.244.244.244
    dns-search in.zekjur.net
    post-up ip -4 route add 192.168.23.23 dev eth0
    post-up ip -4 route add default via 192.168.23.23
    post-up ip -4 address add 79.140.39.197/32 dev eth0
    post-up ip -6 address add 2001:4d88:100e:1::2/64 dev eth0
    post-up ip -6 address add 2001:4d88:100e:1::3/64 dev eth0
    post-up ip -6 route add default via fe80::f463:cbff:febb:59e1 dev eth0
    # iptables
    post-up iptables-restore < /etc/network/iptables
    post-up ip6tables-restore < /etc/network/ip6tables

Serial console

It is benefical to be able to use the virsh console so that you don’t have to use VNC to access your virtual machine’s text consoles. For VNC, you probably need to create an SSH-tunnel first and then you might have issues with your keyboard layout. Also, the virsh console is much faster since it’s text-only. Either way (virsh console or VNC) can be used to recover your VM in situations where you e.g. messed up /etc/network/interfaces and the VM is not reachable over the network anymore.

Luckily, with systemd inside the VM, the only thing you have to do is modify /etc/default/grub like this:

GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0 init=/bin/systemd"

The order is important here! See also Lennart’s blog post about serial consoles with systemd.

In case you are not yet using systemd, you have to uncomment the getty entry for ttyS0 in /etc/inittab to get a login prompt.

After configuring your VM accordingly, you can use virsh console infra to access the VM called "infra" on serial console level.

Caching / Performance

Depending on your use-case, you might want to change the cache settings which can bring dramatic disk bandwidth improvements in the virtual machine at the expense of data security.

Also see the section "Performance Tuning" of Michael David’s post about his KVM setup for further tips on performance.

Snapshots (backup)

I previously wrote about snapshots to backup virtual machines and still use that approach. Unfortunately, there is a bug which prevents lvremove from working properly.

I updated my script to include a workaround which works in the vast majority of cases (I only once had to clean up block devices manually in a high-load scenario since deploying the workaround).

Find it at the xen-lvm-snapshot git repository.

I run a blog since 2005, spreading knowledge and experience for almost 20 years! :)

If you want to support my work, you can buy me a coffee.

Thank you for your support! ❤️