Create a KVM virtual machine template

Today we will see how to create a KVM virtual machine template. A template is essentially a copy of the installed virtual machine that comes in handy when you want to deploy multiple instances of virtual machines.

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

yay -Syyuu --noconfirm

Create a hard disk image

To run QEMU you will need a hard disk image, unless you are booting a live system from CD-ROM or the network (and not doing so to install an operating system to a hard disk image). A hard disk image is a file which stores the contents of the emulated hard disk.

A hard disk image can be raw, so that it is literally byte-by-byte the same as what the guest sees, and will always use the full capacity of the guest hard drive on the host. This method provides the least I/O overhead, but can waste a lot of space, as not-used space on the guest cannot be used on the host.

Alternatively, the hard disk image can be in a format such as qcow2 which only allocates space to the image file when the guest operating system writes to those sectors on its virtual hard disk. The image appears as the full size to the guest operating system, even though it may take up only a very small amount of space on the host system.

sudo qemu-img create -o preallocation=metadata -f qcow2 /var/lib/libvirt/images/rl85.qcow2 15G
Formatting '/var/lib/libvirt/images/rl85.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off preallocation=metadata compression_type=zlib size=16106127360 lazy_refcounts=off refcount_bits=16

By executing this command, you create an image in qcow2 format with a size of 15 gigabytes. A note about the preallocation=metada option: an image with preallocated metadata is initially larger but can improve performance when the image needs to grow.

Display attributes of an image

qemu-img info /var/lib/libvirt/images/rl85.qcow2
image: /var/lib/libvirt/images/rl85.qcow2
file format: qcow2
virtual size: 15 GiB (16106127360 bytes)
disk size: 2.51 MiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false

Resize an image

sudo qemu-img resize /var/lib/libvirt/images/rl85.qcow2 20G
Image resized.

Enable default network

If you want to have access to the internet, you must activate the default network. By default it is not activated and does not start automatically as you can see with this command.

sudo virsh net-list --all
 Name      State      Autostart   Persistent
----------------------------------------------
 default   inactive   no          yes

Start the default network.

sudo virsh net-start default
Network default started

Autostart the default network.

sudo virsh net-autostart default
Network default marked as autostarted

And verify again.

sudo virsh net-list --all
 Name      State    Autostart   Persistent
--------------------------------------------
 default   active   yes         yes

Create the virtual machine

As you may have noticed previously via the name of the image we instantiated, we will create a virtual machine under Rocky Linux 8.5.

sudo virt-install --name rl85 --memory 4096 \
--disk /var/lib/libvirt/images/rl85.qcow2,format=qcow2 --vcpus 4 \
--os-variant rocky8.5 --network default --graphics none --console pty,target_type=serial \
--location "https://download.rockylinux.org/pub/rocky/8.5/BaseOS/x86_64/os/" \
--extra-args "console=ttyS0,115200n8 serial"

Starting install...
Retrieving file vmlinuz...                                                               | 9.6 MB  00:00:00
Retrieving file initrd.img...                                                            |  72 MB  00:00:04
Running text console command: virsh --connect qemu:///system console rl85
Connected to domain 'rl85'
Escape character is ^] (Ctrl + ])
[    0.172004] pci 0000:04:00.0: reg 0x14: [mem 0xfd400000-0xfd400fff]
[    0.183006] pci 0000:04:00.0: reg 0x20: [mem 0xfe400000-0xfe403fff 64bit pref]
[    0.198441] pci 0000:00:01.3: PCI bridge to [bus 04]
[    0.200017] pci 0000:00:01.3:   bridge window [io  0x4000-0x4fff]
[    0.201009] pci 0000:00:01.3:   bridge window [mem 0xfd400000-0xfd5fffff]
[    0.202031] pci 0000:00:01.3:   bridge window [mem 0xfe400000-0xfe5fffff 64bit pref]
[    0.203641] acpiphp: Slot [0-5] registered
[...]

Let’s review all the arguments used with the virt-install command:

As explained, we are going to get the Rocky Linux 8.5 source over the network. You will find here the list of mirrors, select one according to your location.

Moreover, I do this way because my connection permits me, for people who do not have a decent bandwidth, it is better to download an ISO image and put the path of the latter in place of the URL.

Start installation

Once the kernel has loaded, you reach the Anaconda installer. We want to install Rocky Linux 8.5 in text mode, so we have to choose option 2.

Starting installer, one moment...
anaconda 33.16.5.6-1.el8.rocky.1 for Rocky Linux 8 started.
 * installation log files are stored in /tmp during the installation
 * shell is available on TTY2
 * if the graphical installation interface fails to start, try again with the
   inst.text bootoption to start text installation
 * when reporting a bug add logs from /tmp as separate text/plain attachments
================================================================================
================================================================================
Text mode provides a limited set of installation options. It does not offer
custom partitioning for full control over the disk layout. Would you like to use
VNC mode instead?

1) Start VNC
2) Use text mode

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: 2

Configurations

Now you can configure your server as you like: language settings, time settings, installation source, software selection, installation destination, Kdump, network configuration and root password.

Installation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (America/New_York timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Server with GUI)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [!] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is enabled)
       selected)
7) [x] Network configuration             8) [!] Root password
       (Wired (enp1s0) connected)               (Root account is disabled.)
9) [!] User creation
       (No user will be created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]:

Language settings

I want to keep the language settings (English (United States)) so I don’t change anything. However, I send 2 to change time settings.

Time settings

I want to change the timezone so I send 1.

Time settings

Timezone: America/New_York

NTP servers:not configured

1) Change timezone
2) Configure NTP servers

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: 1

I choose my region.

Timezone settings

Available regions
1) Europe                  5) Antarctica              9) Indian
2) Asia                    6) Pacific                 10) Arctic
3) America                 7) Australia               11) US
4) Africa                  8) Atlantic                12) Etc

Please select the timezone. Use numbers or type names directly ['b' back to
region list, 'c' to continue, 'q' to quit, 'r' to refresh]: 1

Then, Paris.

Timezone settings

Available timezones in region Europe
1) Amsterdam               21) Kaliningrad            41) San_Marino
2) Andorra                 22) Kiev                   42) Sarajevo
3) Astrakhan               23) Kirov                  43) Saratov
4) Athens                  24) Lisbon                 44) Simferopol
5) Belgrade                25) Ljubljana              45) Skopje
6) Berlin                  26) London                 46) Sofia
7) Bratislava              27) Luxembourg             47) Stockholm
8) Brussels                28) Madrid                 48) Tallinn
9) Bucharest               29) Malta                  49) Tirane
10) Budapest               30) Mariehamn              50) Ulyanovsk
11) Busingen               31) Minsk                  51) Uzhgorod
12) Chisinau               32) Monaco                 52) Vaduz
13) Copenhagen             33) Moscow                 53) Vatican
14) Dublin                 34) Oslo                   54) Vienna
15) Gibraltar              35) Paris                  55) Vilnius
16) Guernsey               36) Podgorica              56) Volgograd
17) Helsinki               37) Prague                 57) Warsaw
18) Isle_of_Man            38) Riga                   58) Zagreb
19) Istanbul               39) Rome                   59) Zaporozhye
20) Jersey                 40) Samara                 60) Zurich

Please select the timezone. Use numbers or type names directly ['b' back to
region list, 'c' to continue, 'q' to quit, 'r' to refresh]: 35

Installation source

I want to keep the installation source settings (https://download.rockylinux.org/pub/rocky/8.5/BaseOS/x86_64/os/) so I do not change anything.

Software selection

I want to change the software selection so I send 4.

Installation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (Europe/Paris timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Server with GUI)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [!] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is enabled)
       selected)
7) [x] Network configuration             8) [!] Root password
       (Wired (enp1s0) connected)               (Root account is disabled.)
9) [!] User creation
       (No user will be created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]: 4

I choose Minimal Install to install only the necessary packages.

Software selection

Base environment

1) [x] Server with GUI                  4) [ ] Workstation
2) [ ] Server                           5) [ ] Custom Operating System
3) [ ] Minimal Install                  6) [ ] Virtualization Host

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: 3

Continue.

Software selection

Base environment

1) [ ] Server with GUI                  4) [ ] Workstation
2) [ ] Server                           5) [ ] Custom Operating System
3) [x] Minimal Install                  6) [ ] Virtualization Host

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

I don’t want any additional softwares, continue.

Software selection

Additional software for selected environment

1) [ ] Guest Agents                     8) [ ] Headless Management
2) [ ] Standard                         9) [ ] Network Servers
3) [ ] Legacy UNIX Compatibility        10) [ ] RPM Development Tools
4) [ ] Container Management             11) [ ] Scientific Support
5) [ ] Development Tools                12) [ ] Security Tools
6) [ ] .NET Core Development            13) [ ] Smart Card Support
7) [ ] Graphical Administration Tools   14) [ ] System Tools

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

Installation destination

I want to change the installation destination so I send 5.

nstallation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (Europe/Paris timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Minimal Install)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [!] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is enabled)
       selected)
7) [x] Network configuration             8) [!] Root password
       (Wired (enp1s0) connected)               (Root account is disabled.)
9) [!] User creation
       (No user will be created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]: 5

I confirm the installation destination, continue.

Installation Destination

1) [x] DISK: 20 GiB (vda)

1 disk selected; 20 GiB capacity; 20 GiB free

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

I want to use all space, continue.

Partitioning Options

1) [ ] Replace Existing Linux system(s)
2) [x] Use All Space
3) [ ] Use Free Space
4) [ ] Manually assign mount points

Installation requires partitioning of your hard drive. Select what space to use
for the install target or manually assign mount points.

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

I want to use LVM, continue.

Partition Scheme Options

1) [ ] Standard Partition
2) [x] LVM
3) [ ] LVM Thin Provisioning

Select a partition scheme configuration.

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

Kdump

I want to disable Kdump so I send 6.

Installation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (Europe/Paris timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Minimal Install)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [x] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is enabled)
       selected)
7) [x] Network configuration             8) [!] Root password
       (Wired (enp1s0) connected)               (Root account is disabled.)
9) [!] User creation
       (No user will be created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]: 6

I deselect Enable kdump.

Kdump

1) [x] Enable kdump
2) Reserve amount (160 - 3416 MB)
   auto

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: 1

Continue.

Kdump

1) [ ] Enable kdump

Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: c

Network configuration

I don’t want to change network configuration so I don’t send 7. Regarding the IP address, for simplicity reasons, I will let the local DHCP server of Libvirt assign me one. We’ll see later how to take control over this setting.

Root password

I want to change the root password so I send 8

Installation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (Europe/Paris timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Minimal Install)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [x] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is disabled)
       selected)
7) [x] Network configuration             8) [!] Root password
       (Wired (enp1s0) connected)               (Root account is disabled.)
9) [!] User creation
       (No user will be created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]: 8
================================================================================
================================================================================
Root password

Please select new root password. You will have to type it twice.

Password:
Password (confirm):

User creation

I let you create a user if you need to. Don’t forget to add it in the wheel group if you want to use sudo.

Usually, it’s supposed to look like this.

User creation

1) [x] Create user
2) Full name
   John Doe
3) User name
   jdoe
4) [x] Use password
5) Password
   Password set.
6) [x] Administrator
7) Groups
   wheel

Launch the installation

All items are checked, I send b to begin installation.

Installation

1) [x] Language settings                 2) [x] Time settings
       (English (United States))                (Europe/Paris timezone)
3) [x] Installation source               4) [x] Software selection
       (https://download.rockylinux.org/        (Minimal Install)
       pub/rocky/8.5/BaseOS/x86_64/os/)
5) [x] Installation Destination          6) [x] Kdump
       (Automatic partitioning                  (Kdump is disabled)
       selected)
7) [x] Network configuration             8) [x] Root password
       (Wired (enp1s0) connected)               (Password is set.)
9) [x] User creation
       (Administrator jdoe will be
       created)

Please make a selection from the above ['b' to begin installation, 'q' to quit,
'r' to refresh]: b
================================================================================
================================================================================
Progress

.
Setting up the installation environment
Setting up com_redhat_kdump addon
Setting up org_fedora_oscap addon
..
Configuring storage
Creating disklabel on /dev/vda
Creating xfs on /dev/vda1
Creating lvmpv on /dev/vda2
Creating swap on /dev/mapper/rl-swap
Creating xfs on /dev/mapper/rl-root
...
Running pre-installation scripts
.
Running pre-installation tasks
...
Installing.
Starting package installation process
[...]

The installation progresses and a couple of minutes later you have to press ENTER to exit Anaconda.

[...]
Executing com_redhat_kdump addon
Executing org_fedora_oscap addon
..
Generating initramfs
...
Storing configuration files and kickstarts
.
Running post-installation scripts
.
Installation complete

Use of this product is subject to the license agreement found at:
/usr/share/rocky-release/EULA

Installation complete. Press ENTER to quit:

The virtual machine restarts and we are ready to connect as root user.

[  OK  ] Started Login Service.
[  OK  ] Started firewalld - dynamic firewall daemon.
[  OK  ] Reached target Network (Pre).
         Starting Network Manager...
[  OK  ] Started Network Manager.
[  OK  ] Reached target Network.
         Starting Permit User Sessions...
         Starting OpenSSH server daemon...
         Starting Dynamic System Tuning Daemon...
         Starting Network Manager Wait Online...
[  OK  ] Started Permit User Sessions.
         Starting Hold until boot process finishes up...
[  OK  ] Started Command Scheduler.
         Starting Terminate Plymouth Boot Screen...
         Starting Hostname Service...
[  OK  ] Started OpenSSH server daemon.
[    4.283140] IPv6: ADDRCONF(NETDEV_UP): enp1s0: link is not ready

Rocky Linux 8.5 (Green Obsidian)
Kernel 4.18.0-348.12.2.el8_5.x86_64 on an x86_64

localhost login: root
Password:

Create the KVM virtual machine template

Update the system packages

Log into the brand new virtual machine and update all the system packages.

[root@localhost ~]# dnf -y update
Rocky Linux 8 - AppStream                      6.0 MB/s | 6.3 MB     00:01
Rocky Linux 8 - BaseOS                         7.1 MB/s | 2.3 MB     00:00
Rocky Linux 8 - Extras                          28 kB/s | 9.6 kB     00:00
Dependencies resolved.
Nothing to do.
Complete!

Install packages

At this point, it can be useful to install standard tools (to avoid installing them every time), for example: vim, wget, cURL… This is totally optional.

[root@localhost ~]# dnf -y install vim

Clean the virtual machine

To create a clone of a virtual machine, it must be cleaned. To do this, the virt-sysprep tool helps us greatly. Steps in this process include removing SSH host keys, persistent network MAC configuration, and user accounts.

The virtual machine must be shut down before you use this command, and disk images must not be edited concurrently.

Shutdown the virtual machine

sudo virsh shutdown rl85
Domain 'rl85' is being shutdown

Install virt-sysprep

yay -S --mflags --nocheck guestfs-tools
sudo virt-sysprep --domain rl85 --selinux-relabel --root-password file:/tmp/root-password.txt
[   0.0] Examining the guest ...
[   3.7] Performing "abrt-data" ...
[   3.7] Performing "backup-files" ...
[   4.1] Performing "bash-history" ...
[   4.1] Performing "blkid-tab" ...
[   4.2] Performing "crash-data" ...
[   4.2] Performing "cron-spool" ...
[   4.2] Performing "dhcp-client-state" ...
[   4.2] Performing "dhcp-server-state" ...
[   4.2] Performing "dovecot-data" ...
[   4.3] Performing "ipa-client" ...
[   4.3] Performing "kerberos-hostkeytab" ...
[   4.3] Performing "logfiles" ...
[   4.4] Performing "machine-id" ...
[   4.4] Performing "mail-spool" ...
[   4.4] Performing "net-hostname" ...
[   4.5] Performing "net-hwaddr" ...
[   4.6] Performing "net-nmconn" ...
[   4.7] Performing "pacct-log" ...
[   4.7] Performing "package-manager-cache" ...
[   4.8] Performing "pam-data" ...
[   4.8] Performing "passwd-backups" ...
[   4.8] Performing "puppet-data-log" ...
[   4.9] Performing "rh-subscription-manager" ...
[   4.9] Performing "rhn-systemid" ...
[   5.0] Performing "rpm-db" ...
[   5.0] Performing "samba-db-log" ...
[   5.0] Performing "script" ...
[   5.0] Performing "smolt-uuid" ...
[   5.0] Performing "ssh-hostkeys" ...
[   5.1] Performing "ssh-userdir" ...
[   5.1] Performing "sssd-db-log" ...
[   5.1] Performing "tmp-files" ...
[   5.1] Performing "udev-persistent-net" ...
[   5.2] Performing "utmp" ...
[   5.2] Performing "yum-uuid" ...
[   5.2] Performing "customize" ...
[   5.3] Setting a random seed
[   5.3] Setting the machine ID in /etc/machine-id
[   5.3] Setting passwords
[   6.2] SELinux relabelling
[   6.3] Performing "lvm-uuids" ...

Many flags and options are available with the virt-sysprep utility, the ones I used are the following:

As you can see, the root password can be set (via --root-password file:/tmp/root-password.txt). You should provide a file containing the cleartext password, ensuring that it is not readable by other users on the system.

chmod 600 /tmp/root-password.txt

Clone the virtual machine

sudo virt-clone --original rl85 --name server.local --file /var/lib/libvirt/images/server.local.qcow2
Allocating 'server.local.qcow2'                                                                                      |  20 GB  00:00:01

Clone 'server.local' created successfully.

Start the cloned virtual machine

If like me you used the --selinux-relabel flag, the startup will take a bit longer (between 20 and 30 seconds depending on your system performance).

sudo virsh start server.local
Domain 'server.local' started

Generate a SSH key pair

On your local machine, generate a SSH key pair for easy access to the virtual machine.

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/<key pair name> -C <comment> 
<passphrase>
<confirm>

Copy the public key to the virtual machine

First, you must know the virtual machine’s IP address.

sudo virsh domifaddr server.local
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet0      52:54:00:81:61:4e    ipv4         192.168.122.238/24
ssh-copy-id -i ~/.ssh/<key pair name>.pub jdoe@192.168.122.238
<jdoe's password>

Add the server’s information inside your SSH config file (as described here) and connect to your virtual machine.

ssh server.local

You have just created a template of a Rocky Linux 8.5 system. Now, if you want to create a new machine, you just have to use the virt-clone utility and you will save a lot of time.