Setting up KVM/Libvirt on CentOS7

I always get so annoyed with myself when I can't remember things I've done in the past. Setting up KVM/Libvirt is one of those things. Recently while working for a client, I found myself fumbling around trying to figure out why their VMs couldn't talk to the internet. So rather than let future me bang his head against the desk trying to figure this out again, I thought I'd write a quick post detailing the setup and solution. Hopefully others will find this post helpful as well.

First off, this procedure is written for CentOS7. It may well work on other distros, but I haven't tested it.

Okay, so you have your CentOS7 host installed and updated. Now its time to install the necessary packages and set the libvirt daemon to run on reboot: 

yum -y install libvirt libvirt-python qemu qemu-kvm
systemctl enable libvirtd

Now that KVM/libvirt is installed, we can work on the area that usually causes me problems, networking. First lets disable and stop NetworkManager else it will cause problems with the networking setup we will be implementing.

systemctl disable NetworkManager
systemctl stop NetworkManager

Next, lets make a new config for a bridge interface by creating the file /etc/sysconfig/network-scripts/ifcfg-br0. It should contain our system's network settings. If you use static addressing, this is where you will will want to include the IP address, subnet mask, gateway, and DNS settings. I use DHCP, so it's a bit easier.

TYPE=Bridge
ONBOOT=yes
BOOTPROTO=dhcp
DEFROUTE=yes
IPV6INIT=no
DEVICE=br0
DELAY=0
HWADDR=00:00:00:00:00:00

Next, lets edit the network config file that the system is currently using. For me on this particular machine that file is /etc/sysconfig/network-scripts/ifcfg-enp1s0. Due to the way that RHEL7 based systems now name their interfaces, yours may be named differently. In any case, we're simplifying this file as we've already moved the network settings to our bridge file. The big thing that this one will do is tell the interface to use the new bridge. 

DEVICE=enp1s0
ONBOOT=yes
IPV6INIT=no
BRIDGE=br0

Alright, if all of that is done, then our last step is to restart networking.

systemctl restart network

If all went well, you should have a new br0 interface with an IP. 

We're almost done, but there are a couple of things left that need to be addressed. First, you need to allow IP forwarding on your host, else your VMs will not be able to pass traffic. To enable this, add the following to /etc/sysctl.conf.

net.ipv4.ip_forward = 1

That done, tell the OS to re-read the file.

sysctl -p /etc/sysctl.conf

The last step is to make sure IP Tables allows your VM traffic to traverse the system. Now, there are two ways to do this. You can disable IP tables on bridges altogether, which means that you rely on the VMs to provide their own firewalls. To go this route, add/edit another variable in /etc/sysctl.conf.

net.bridge.bridge-nf-call-iptables = 0

If you choose to have IP Tables on the host to continue evaluating traffic destined for the VMs, i.e. leaving net.bridge.bridge-nf-call-iptables with the default value of 1, then you will need to make sure that there are IP Tables rules running on the host to allow passing traffic meant for your virtual machines.

To take a little bit of the headache out of this, I've started on a Salt state to handle the easy stuff. https://github.com/alektant/salt_states/tree/master/kvm