A couple of weeks ago, as part of an update to my DAS setup + the Mini PC with Intel Celeron J4125 I’ve previously talked about, I had the opportunity to get an AMD Ryzen 5 5600G, along with everything needed to build a system where I could integrate hard drives at a very reasonable price. One of the first things I envisioned for this processor, which has proven to handle everything thrown at it, was to delve into virtualization. I had a couple of projects in mind that required a Windows virtual machine, as I didn’t want to install Windows 10 or similar on the server but needed software running 24/7 that specifically works mostly under Windows environments.
Now that I have a system capable of virtualization with remarkable performance, I decided to install libvirt and explore the capabilities of this software solution. I enjoyed the results, although I’ll leave some notes here because the information is scattered beyond the RedHat documentation, the creators of Libvirt, and for whose systems most development is focused. For reference, all this has been done on a Debian 12 system, and so far, I have only focused on KVM virtualization (libvirt supports LXC, Xen, and Virtualbox in addition to KVM). However, I haven’t touched anything related to networking yet, so currently, my virtual machines can access the internet, but no one outside the host system can access them.
It’s worth mentioning that, at the moment, my knowledge about all this is limited. The following are just notes and things I’ve seen that work.
Libvirt is an application that aims to make virtualization and container creation easier by abstracting many of the things that previously required different parameters (like KVM and LXC) or manual edits (like virtual networks). The idea is that, thanks to libvirt acting as an abstraction layer, container and virtual machine creation can be managed across different platforms.
Naturally, before getting started, it’s necessary to install the tools provided by libvirt. In Debian, this is done with the following command:
$ sudo apt-get install --no-install-recommends qemu-kvm libvirt-daemon-system libvirt-daemon virtinst bridge-utils libosinfo-bin
Once installed, libvirt-daemon should start automatically (the unit is called libvirtd in Debian). This unit is responsible for loading the KVM modules in the kernel to enable virtualization. If you have an AMD or Intel processor, it should also load the kvm-amd or kvm-intel modules, enabling hardware-assisted virtualization. You can check the list of loaded kernel modules with the following command:
$ lsmod | grep kvm
If kvm_intel or kvm_amd doesn’t appear in the result of this command, and the processor supports hardware-assisted virtualization, it’s essential to activate this support in the UEFI options of the system. Otherwise, you won’t be fully utilizing the power these systems can offer.
Finally, it’s important to start the default network provided by libvirt. This allows communication between the host and virtual machines if needed. This is done with the following commands:
$ sudo virsh net-start default
$ sudo virsh net-autostart default
Creating Virtual Machines
After installing these packages and configuring the automatic startup of the network provided by libvirt, we are ready to start creating a virtual machine. Normally, this process is not the most straightforward, as, at the very least, after creating the virtual machine, you need to install the operating system. VNC is commonly used to view the virtual machine, transmitting the image from a remote server, but it doesn’t handle other media, such as audio. Fortunately, we can set up a spice server, which, among other things, will help us greatly by providing both audio and video accessibility when managing the virtual machine.
Example 1: Linux Virtual Machine
For this example, we will create a virtual machine (using virt-install) where we can install Debian 12. This machine will listen through the Spice server on a consecutive port, starting from 5900, and we can connect to it with a password. Once inside the Spice session, we can manage the installer and activate its screen reader if necessary to complete the installation process on the virtual system.
The first step is always to download the ISO image of the system you want to install and then present it to libvirt as a disk unit. For Debian 12, you can download the netinst image, which contains only the minimal system from which the installation is run. The netinst system ISO images are available on this page.
$ wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.1.0-amd64-netinst.iso
Next, move the image to the libvirt-owned directory; otherwise, it will complain about not being able to access the image in subsequent steps:
$ sudo mkdir /var/lib/libvirt/iso
$ sudo mv debian-12.1.0-amd64-netinst.iso /var/lib/libvirt/iso
Finally, we can create the virtual machine with the following command:
$ sudo virt-install --name test --cdrom /var/lib/libvirt/iso/debian-12.1.0-amd64-netinst.iso --os-variant=debian11 --network network=default --disk size=20,cache=none,bus=virtio --memory 2048 --sound default --graphics spice,listen=::,password=test1 --vcpu 2 --noautoconsole
The options, according to the virt-install documentation, are explained as follows:
- –name: The name of our “domain.” For libvirt, everything created, whether a virtual machine or a container, is called a domain. This name will be useful later for operations on the virtual machine.
- –cdrom: Specifies the path to an ISO image containing the files needed for the system installation. This ISO will be considered as the “installation CD.” If you are in the /var/lib/libvirt/iso directory, this path could be relative.
- –os-variant: The variant of the system to install. The closest variant should be chosen, even if the version doesn’t match, as in this case. To see the list of variants that can be used here, you can use the command
virt-install --os-variant=listand select one.
- –network: Network to use. If there are more than one network created during the libvirt installation on the host system, a different one can be specified here.
- –disk: These are the options for adding a disk unit to the new virtual machine. When the options to be added have several parameters, the syntax is to place them all together, separated by a comma, but usually without spaces. Here the size parameter controls the number of gigabytes to reserve for the virtual hard disk, and we decided to disable the cache to increase the performance and lifespan of the server’s SSD. Finally, with bus=virtio, we use “paravirtualization” to significantly improve the performance of our virtual machine. This type of improvement in virtualization allows more efficient and shared use of some hardware resources on the system where the virtual machine runs, avoiding the emulation of all the hardware needed for the operating system.
- –memory: Amount of memory, in megabytes, to assign to the virtual machine. It should always be less than that of the host system. It’s important not to exceed the value of the host system in all virtual machines in general, as a problem may occur if all machines start using more memory at the same time, and the host runs out of RAM.
- –sound default: Adds a default audio device to the virtual machine.
- –graphics: This is perhaps one of the most complex and important arguments. Spice (Simple Protocol for Independent Computing Environments) is what allows us to view a virtual machine, even if we are not on the same system where it was created. In addition to some other improvements, Spice transmits audio and video by default, which will be helpful for running the installation in an accessible way. By default, Spice reserves port 5900 for the first session, then 5901 for the second session, and so on. With listen, the listening address is indicated. By default, it is 127.0.0.1, which makes the connection accessible only through the same system where the virtual machine is created. By setting it to 0.0.0.0, it listens on all available IPV4 addresses, allowing remote connections. If we write ::, it does the same, but using IPV6 instead of version 4. Finally, a password should be specified with password. The client Spice requests this password when connecting and should not be the same as the one used when installing the virtual system.
- –vcpu: Number of virtual CPUs to assign to the domain.
- –noautoconsole: This option prevents libvirt from attempting to connect to the console immediately after creating and starting the domain.
Once this command is executed, libvirt will create the image where the virtual hard disk files will be stored, assign a virtual network device to the VM, and start it. From this point on, we must start the session by connecting to the spice session to install the operating system. How the system will be installed is not covered in this post, but it’s important to talk about the client we will use to connect to Spice. If you want to review the connection URL for your domain, from the machine where virt install was executed, you can obtain it like this:
$ sudo virsh domdisplay test
This will display the URL string, which will be of the form “spice://localhost:5900”. When connecting to the service, remember to change “localhost” to the server’s IP address; otherwise, it will fail.
Downloading Remote Viewer
Remote viewer is an application that allows us to connect to a virtual machine using the Spice protocol. This application is available for all popular operating systems, although it’s not accessible on Windows. However, it’s not difficult to use. You can download it from this page, which provides virt viewer for 32-bit or 64-bit systems. Regardless of the architecture, download and install it like any other installable file for Windows.
To connect to Spice, it is necessary to enter a character string indicating the protocol at the beginning. If the IP where our Spice server is listening is, for example, 192.168.1.12, and our port is 5900 as in the previous example, then our spice URL would be spice://192.168.1.12:5900.
Once Remote Viewer is opened, simply enter the Spice connection URL and press Enter. After that, if the window title changes to “authentication required,” enter the password and press Enter again. If it changes to something else, you can use the OCR of the screen reader to read the information in the window and understand the error.
After authenticating the session, the window title should change to the name of our virtual machine. If this is the case, it means that we can see the virtual machine in the window, and as soon as we press some keystroke, for example, it will be sent to the VM. We can also hear sound if it is emitted. By default, when the VM window gains focus, the keyboard and mouse are captured and sent to the VM. To exit, you can press the Ctrl+Alt+G shortcut, and you can work on the physical machine instead of the virtual one. At this point, you should perform the normal installation of any system.
An important and interesting point during installation: after finishing the first phase, when the system should restart, for some reason that I still don’t fully understand, it shuts down instead, and we must start it manually. Fortunately, this only happens the first time. To start a domain, use this command, where “test” is the domain name:
$ sudo virsh start test
If you want to see the list of domains and their current state, use this command:
$ sudo virsh list --all
Having a Linux Virtual Machine is good, but being able to have both Windows and Linux on one system is even better. For this, however, some extra steps need to be taken, as the most optimal way for Windows systems to work well when virtualized is to use VirtIO drivers for Windows, which are not included in the installation ISO provided by Microsoft. So, in summary, download the drivers into their own ISO image, download the desired Windows ISO for installation, and generate a virtual machine with both images as devices. Then, when Windows displays the screen to select the disk where the system should be installed, load the new drivers by reading them from the VirtIO drivers ISO.
Once the drivers are downloaded, place both ISO images in the /var/lib/libvirt/iso directory, and then create the domain with a command similar to the following:
$ sudo virt-install --name windows-vm --cdrom /var/lib/libvirt/iso/windows10.iso --os-variant=win10 --network network=default --disk size=50,cache=none,bus=virtio --disk path=/var/lib/libvirt/iso/virtio-drivers.iso,device=cdrom --memory 4096 --sound default --graphics spice,listen=::,password=hardpasswordforwindows --vcpu 4 --video qxl --noautoconsole
In this command, what has changed is the addition of a second disk with the content of the VirtIO drivers ISO image. After finishing the domain creation, Spice can be used to install the system on the virtual machine, loading the driver directly from the unit that should be visible to Windows.
Once the normal Windows installation is completed, the “Balloon” driver should be installed to allow faster, more efficient, and organized access to the host system’s RAM. To do this, from the device manager of the virtual machine, in the “other devices” section, right-click on the device named simply “PCI device,” and select the “Update driver” option. Then, from the appearing dialog, choose the option “Browse my computer for drivers,” allowing the search for a system directory where the drivers are located. At this point, select the directory of the disk unit containing the VirtIO drivers, and let Windows search and install the drivers from that location.
Finally, from the same disk unit, the “guest tools” can be installed if necessary. These tools improve the performance of Spice in general.
From this point, the possibilities are numerous. You can experiment with networks and port redirection between the host and virtual machines, create more machines and test the system on different ones, create snapshots of them, and even clone a VM to serve as a base for new creations.