Basics of Virtualization in Linux
The goal of this article is to explore some basic terminology and concepts around virtualization in Linux systems. It’s mostly intended as a quick reference, rather than a deep dive. I have decided to write this article, as I’ve previously touched on connecting to VMs, without talking about virtualization itself.
Hypervisor
A hypervisor is a piece of software that is responsible for creating and running virtual machines. The system on which the hypervisor runs virtual machines is called the host system, and the virtual machines themselves are called guest systems. The hypervisor manages and provides resources to the guest OS.
There are two main categories of hypervisors:
Type 1 Hypervisor
These run on bare metal and typically make use of the CPU’s feature set specifically built for virtualization, like AMD-V and Intel VT-x. This feature set is usually something that can be toggled in the BIOS to enable/disable. In this article the focus will be on kvm based virtualization. But there are other type 1 hypervisors, like VMWare ESXi and MS Hyper-V.
Type 2 Hypervisor
These run on top of a host OS, so they must negotiate with the host OS for system resources adding some overhead. This type includes solutions like qemu, VMWare Workstation and VirtualBox.
Type 1 vs Type 2
- -> In general, type 1 hypervisors have less overhead than type 2 hypervisors
- -> Type 1 hypervisors can only emulate the same architecture as the host CPU. E.g. If the Host is x86, then that is the only option for guest
- -> Some (but not all) Type 2 hypervisors are able to create VMs of other architectures, especially useful for testing
kvm/qemu
On its own, qemu can emulate systems entirely in software, including systems that use a different CPU architecture than the host, and kvm is a kernel module that can provide the efficiency of a type-1 hypervisor, kvm and qemu are able to interact each other, if:
- -> guest and host share the same CPU architecture
- -> host CPU supports virtualization extensions (AMD-V/Intel VT-x)
- -> host has kvm installed
In this fashion, qemu can delegate CPU execution and the memory virtualization to kvm, therefore making that part of the virtualization process type 1. Other things, such as calls to IO devices and disk drives, are routed through the OS, in a different fashion. In this way, the two can work together where qemu can build and manage virtual hardware components, and pass CPU and memory heavy instructions for processing to kvm, resulting in something between types 1 and 2.
The main takeaway is that kvm is a linux kernel subsystem that exposes hardware virtualization capabilities, while qemu provides the virtual hardware and machine emulation.
To check whether your CPU supports virtualization, use the following command:
root@linuxpc:/etc# kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used
The output will be fairly self-explanatory here.
If the above command is not found, then you can query device info manually:
grep -Ec '(vmx|svm)' /proc/cpuinfo
If the value is greater than 0, the CPU can support virtualization.
libvirt
Libvirt provides an interface that helps abstract away differences between different hypervisors, so that they can all be operated with a single toolset.
It gives:
- -> a C-based API that executes tasks on the host system
- -> virsh, a command line utility used to manage virtual machines, storage pools, virtual networks, vm checkpoints and backups
- -> language bindings, wrapper libs for scripting and programming languages to help out with automation
We will need this in order to be able to actually install and manage VMs, without having to directly call qemu binaries with long argument strings.
To install, as root:
apt install libvirt-daemon-system virtinst
Since we are talking about kvm/qemu here, we’ll also want to actually install qemu:
apt install qemu
Spinning up a VM
First, a disk image file is needed, which the VM can use as its disk. The format can be raw or qcow2.
qemu-img create -f raw -o size=30G vm1.raw
This should create an image with a 30G disk. It can be extended later if it’s not big enough.
Once the disk image is created, the disk image can be used as storage for spinning up a VM, using virt-install, passing an installation media so that some OS can be installed, in my case debian, but you can use whatever you like.
virt-install --name=vm1 --ram=8192 --vcpus=2 --disk path=vm1.raw --cdrom=/home/conquistador/Downloads/debian-12.11.0-amd64-netinst.iso --os-variant debian12.11.0 --network bridge=virbr-wifi,model=virtio
The flags are mostly self explanatory but let’s go through them anyway:
- ->
name: The VM is going to be calledvm1 - ->
ram: The VM is going to be able to use 8GB of RAM - ->
vcpus: The VM can utilize up to 2 CPU threads - ->
disk: The disk image this VM should use as storage - ->
cdrom: The installation media - ->
network: What network connectivity the VM should get, the option here will depend on the need, will loop back to this soon
If you don’t know the os-variant, you can give --osinfo detect=on,require=off a shot.
When executed, a virt-viewer window should appear with the installer running, and it should proceed as normal. The virt-viewer can be ran anytime to view the VM, e.g.
virt-viewer vm1
If you do use virt-viewer, you’ll want to use & and disown so you don’t have to keep the terminal window open.
Commands to shut down, start, and reboot it:
virsh shutdown vm1
virsh start vm1
virsh reboot vm1
Interactive TUI installations are also possible, then you need slightly different parameters, and the steps will be printed to the terminal. Furthermore, entirely headless installations are also supported. (Not covered here)
Machines can be told to autostart (or stop autostarting) along with whenever libvirtd comes up/stops:
virsh autostart vm1
virsh autostart --disable vm1
Sidenote on a fresh install of kvm/qemu
If the kvm/qemu is a fresh installation, there may not be networking access definitions out of the box, in that case, may need to create and apply a default one. E.g. create a file /tmp/default.xml:
<network>
<name>default</name>
<bridge name='virbr0'/>
<forward/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>
Then tell libvirt to read this definition:
virsh net-define /tmp/default.xml
Don’t worry about the /tmp location, libvirt should save a definition for itself, but if you don’t trust it, go ahead and replace the path with /usr/share/libvirt/networks.
And start up the new network interface:
virsh net-start default
virsh net-autostart default
->
net-define(The Blueprint): This saves the desired network layout to disk. It does not yet turn anything on. It just tells libvirt, “Hey, remember this configuration for later”.->
net-start(The Construction): This takes takes the saved network blueprint and builds it in real-time. It creates thevirbr0software switch, boots up the dnsmasq DHCP manager, and it is the interface the VM will try to connect to.
Sidenote on disk images
When it comes to disk image formats, the two options are raw vs qcow2.
There are two main things to keep in mind:
- -> raw is simpler, but usually offers slightly better performance, fixed space consumption
- -> qcow2 supports snapshots on the VM image layer, and supports compression, consumes space dynamically up to allocation maximum
If you care about the overall state of the VM, qcow2 is better, if you are like me and prefer to use ZFS to snapshot working data on the storage layer, raw is good enough.
Sidenote on virtio
virtio is a paravirtualized device standard designed specifically for virtual machines.
Instead of emulating a piece of physical hardware such as a real network card or disk controller, it allows the guest OS to communicate more directly with the hypervisor, reducing overhead.
Troubleshooting
Disk with installed VM exists, VM not registered
If the disk exists, but vm not defined, just call virt-install on the disk without plugging a cdrom installer:
virt-install --name=vm1 --ram=8192 --vcpus=2 --disk path=vm1.raw --osinfo detect=on,require=off --network bridge=virbr-wifi,model=virtio --import
The --import flag is what does the magic here: it tells virt-install that we want to build the VM around an existing disk image, rather than install.
VM running low on space
Extend the space on the VM disk in the host machine:
qemu-img resize vol.raw +5G
Inside the guest/ext4:
# grow the partition, extending the partition container to the end of the disk
growpart /dev/sda1
Then, expand the filesystem later to fill the newly grown partition
resize2fs /dev/sda1
Definitely make a backup of any useful or important data on the VM before doing this.
Cloning a VM
This is fairly straight forward, as long as you have enough space on the host:
virt-clone --original source_vm_name --name cloned_vm_name --auto-clone
Make sure you don’t forget to change hostname and assign a different IP for the cloned VM.