In the previous article, we introduced virtualization to our home server. This allows us to start virtual machines with different operating systems. The combination of libvirt, KVM and Cockpit also allows us to create virtual machines with a graphical frontend.
But, why don't we automate the setup of virtual machines, too? So, let's level up our Ansible automation game.
This series is explaining how we are setting up a home server from scratch. The previous articles are addressing the basic setup, including some management.
This is the second part, taking care of virtualization.
Under the hood, we want to use so called cloud-images, Cloud-Init and Ansible to automate the deployment. For now, I am ok to have an Ansible Playbook that will create a new machine, start it and that's it.
We can use Ansible to talk to libvirt/KVM for the heavy work. For the first boot automation, I will opt for Cloud-Init, which makes it quite easy to set up new machines.
In the end, we can create new machines from our workstation. For convenience, we will also introduce some prompts, to tell the Ansible Playbook how many CPU, Memory or Disk is needed.
Ok, enough of the preface, let's automate! The next chapters we will:
For convenience, I recommend writing a new create_vm.yml playbook. You can move some parts of it afterwards. For now, it will demonstrate the complete process, quite conveniently.
The first thing we want to do is "Downloading the image", but we also need to create a proper directory for these base images.
We just need to create a new playbook with the below content.
As you can see, I have also added a ton of variables. We will need all of them for the upcoming tasks. For now, we will ensure that the libvirt_base_directory is existing, and we will download the Fedora Cloud Base Image.
Furthermore, we will check if the already exists, before doing something that may be harmful.
Creating a Cloud-Init configuration
Ok, an image is downloaded and the playbook is prepared. So far, so easy. Now we need to address the automatic configuration. Today, I will not dig into too many details, but provide a followup article about Cloud-Init.
For now, Cloud-Init is a technology that can be consumed from most cloud images and autoconfigure a machine. Therefore, one has to prepare a "user-data" and "meta-data" file and put both in an ISO.
Yes, we can automate this configuration. Therefore, we need to add some more tasks to our playbook.
In the second and third task, we are using the template module. Therefore, we have to create the two templates.
The meta-data.j2 is just an empty file.
If you run this, you will end up with the two files and a cloud-init.iso in /var/lib/libvirt/boot/test01/. This is the ISO we want for the next step.
You may guess (from the template above), that the Cloud-Init configuration is not too sophisticated. We create a user admin with the password password and update the machine. Finally, we will disable the Cloud-Init service, since we don't want to reconfigure the machine on every reboot.
Creating the VM
Finally, we can create a VM. We need to add some more jobs to our playbook. So, one more time, please.
Oh, there is another template. Libvirt works with XML configurations which are used to define which disk should be booted, which device is available and much more. I have prepared the below snippet, which will create machines that boot with EFI and SecureBoot enabled.
If you look very closely in the above template, you can find our variables. As you can see, we identify the VM Name, CPU, Memory and Images here. We will also ensure that the "cloud-init.iso" is mounted and available.
There is something special with this configuration, too. Instead of just booting our base image, we are creating a new image which has the base image as "backingStore".
This is a pretty awesome feature, since our VM images are sharing this backing image and only the difference (new packages, changed configuration) will allocate disk space. A new VM can be as small as 2 MiB(!) with a backing store. Pretty cool, huh? :D
The first run
Now that everything is in place, we can run our playbook. If everything is fine, we will have a fresh Fedora VM with the name "test01" afterwards.
Phew, that was a lot. Or wasn't it? Anyway, we can improve this a bit.
Prompt for VM values
The playbook will work and if I want a new VM, I just need to edit the playbook and run it again. But... this is tedious. Ansible provides something to "prompt for values". If we use this, the playbook will ask us for the VM Name, CPU or Memory. So, let's refine our playbook.
We just need to remove the vm_ variables and write some prompts for the same.
The result will look something like this:
If you run the playbook now, you will get some interactive input to add the important things. This makes it pretty easy to create new virtual machines.
# Create a new VM
$ ansible-playbook -u admin -k -K -i home, --diff ansible/playbooks/create_vm.yml
VM Name: test02
Virtual CPU Cores? : 4
Virtual RAM (MB)? : 2048
Disk Size (GB)? : 20
I will elaborate on Ansible Prompts in another article, for sure.