Creating a Bridge-Connected VM with KVM
I created a VM using KVM on an Ubuntu PC at home. To allow SSH access to the VM from external hosts, I connected the VM to a bridge network created on the host. I ran into several issues along the way, so I’m documenting the entire procedure here.
Environment
Host OS
Ubuntu 22.04.1 LTS
Guest OS
Ubuntu 20.04.5 LTS
Procedure
I’ll skip the steps for installing KVM on Ubuntu (refer to the official instructions).
Create a Bridge
I referenced this article.
Remove the Default Network
When you install KVM, a bridge called virbr0 should already exist by default. This bridge uses NAT to allow the VM to access external networks. With this bridge, communication between the host and VM is possible, but external hosts cannot access the VM. Since we don’t need it for our purposes, let’s remove it.
The default configuration is registered as “default” in KVM, so we delete it.
$ virsh net-destroy default
$ virsh net-undefine default
Create a Bridge with netplan and Attach the NIC
We’ll use netplan. Edit an existing file or create a new one under /etc/netplan.
The host NIC enxf8e43bb371e8 has a static IP address of 192.168.0.130, so we’ll use that address.
# /etc/netplan/99_config.yaml
network:
version: 2
ethernets:
enxf8e43bb371e8:
dhcp4: false
dhcp6: false
bridges:
br0:
interfaces: [enxf8e43bb371e8]
addresses: [192.168.0.130/24]
gateway4: 192.168.0.1
nameservers:
addresses: [192.168.0.1, 8.8.8.8]
parameters:
stp: false
dhcp4: false
dhcp6: false
$ sudo netplan apply
This creates the new bridge br0. The IP address that was previously assigned to enxf8e43bb371e8 now appears under br0. Also, the master br0 label on the enxf8e43bb371e8 entry confirms that the NIC is properly connected to the bridge.
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enxf8e43bb371e8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000
link/ether f8:e4:3b:b3:71:e8 brd ff:ff:ff:ff:ff:ff
(skip)
16: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 72:e2:b6:e9:05:35 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.130/24 brd 192.168.0.255 scope global noprefixroute br0
valid_lft forever preferred_lft forever
inet6 fe80::70e2:b6ff:fee9:535/64 scope link
valid_lft forever preferred_lft forever
Register the bridge with KVM. Create the following host-bridge.xml file.
<network>
<name>host-bridge</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>
Register it with KVM.
$ virsh net-define host-bridge.xml
$ virsh net-start host-bridge
$ virsh net-autostart host-bridge
Verify that it’s registered correctly.
$ virsh net-list --all
Name State Autostart Persistent
------------------------------------------------
host-bridge active yes yes
Disable Bridge Netfilter
This is where I got stuck. Even after creating the VM in the next step, it couldn’t reach external networks. The cause was Bridge Netfilter blocking traffic through the bridge. Following this article, I disabled Bridge Netfilter on the host.
Add the following line to /etc/sysctl.conf. (Depending on your environment, a similar setting may already exist in /etc/sysctl.conf or /etc/sysctl.d/*, so be sure to check.)
net.bridge.bridge-nf-call-iptables = 0
Apply the changes.
$ sudo sysctl -p
Verify the update was applied correctly.
$ sudo sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 0
Create a VM
Create a VM with Ubuntu 20.04. Adjust the memory, CPU cores, disk size, etc. as needed.
$ virt-install \
--name vm1 \
--ram=2048 \
--disk size=10 \
--network network=host-bridge \
--vcpus 1 \
--os-type linux \
--os-variant ubuntu20.04 \
--graphics none \
--location 'http://archive.ubuntu.com/ubuntu/dists/focal/main/installer-amd64/' \
--extra-args "netcfg/disable_autoconfig=true console=ttyS0,115200n8 --- console=ttyS0,115200n8"
An important note here is the console=ttyS0,115200n8 --- console=ttyS0,115200n8 part in --extra-args.
If you’re doing everything via CLI, you’ll need to connect to the VM later with virsh console vm1 to configure SSH and other settings. Without enabling serial console access, you won’t be able to interact with the VM at all.
For more details, see this article.
Also, netcfg/disable_autoconfig=true prevents the installer from automatically assigning an address via DHCP during installation. Whether this is necessary depends on your environment, but in my case I wanted to assign a static address, so I used this option. (Reference)
I looked into whether it was possible to specify a static address at this point, and while it seems possible on RHEL, I couldn’t find a way to do it on Ubuntu.
Ubuntu Installation
An installation wizard will appear, so follow the on-screen instructions to complete the installation.
Configure SSH
After installation, if the console connection is still active, you can proceed directly. Otherwise, connect manually.
$ virsh console vm1
Install the SSH server and register your keys.
$ sudo apt update
$ sudo apt install openssh-server
$ mkdir ~/.ssh
$ wget "https://github.com/hiroyaonoe.keys" -O ~/.ssh/authorized_keys
After exiting, you should be able to SSH in using the username and IP address you specified during installation (if using DHCP, look up the assigned address accordingly).
Conclusion
Networking configuration is tricky. In particular, the Bridge Netfilter issue took a long time to resolve, so I hope this helps someone. Now that I have VM infrastructure set up on my home Ubuntu PC, I’d like to try building a Kubernetes cluster on it.
