Setting up KVM guests manually with an install ISO is suffering. Here's an example way of templating KVM guests by hand.

NB: There are many ways this can be achieved with tools like virt-clone, so think of this as a more hands-on approach for learning. The process described here is very easy to automate with tools like Ansible or simple scripts, enabling distribution of templates across hypervisors and easy orchestration.

To make sure you understand what's going on here, you need some basic knowledge of KVM, virsh, block devices and linux logical volumes (LVM).

Creating a template from an installed guest

Configure your template guest to be inside a raw file, ie. have the disk setup as below:

<disk type='file' device='disk'>
  <driver name='qemu' type='raw' cache='none' io='native'/>
  <source file='/myvmtemplate'/>
  <target dev='vda' bus='virtio'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
</disk>

Install your guest like you would do otherwise, do any tasks that all new guests will share, such as setting up resolvers, installing packages, removing NetworkManager and disabling SELinux 🙂 ...

Tip: Keep the disk as small as possible, typically a minimal CentOS 7 installation will not require more than 1.5 GB of disk space

Once your guest is installed and you're satisfied with its OS setup, shutdown the VM. Your template is now effectively the disk file mentioned earlier, ie. /myvmtemplate from the above XML snippet.

Setup new block device with your template

Once you have the template file, create a new block device for your new guest and write the template into it with dd:

[[email protected] ~]# lvcreate cl -n centos7guest2 --size 10G
Logical volume "centos7guest2" created.
[[email protected] ~]# dd if=/myvmtemplate of=/dev/cl/centos7guest2
2312192+0 records in
2312192+0 records out
1183842304 bytes (1.2 GB) copied, 41.3912 s, 28.6 MB/s

Once this is done, your template's partitions and OS are on the new block device. As the partition tables were copied verbatim we should probably recreate the partition table to resize the OS partition to maximum allowed size:

[[email protected] ~]# fdisk /dev/cl/centos7guest2
 
The device presents a logical sector size that is smaller than
the physical sector size. Aligning to a physical sector (or optimal
I/O) size boundary is recommended, or performance may be impacted.
Welcome to fdisk (util-linux 2.23.2).
 
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
 
Command (m for help): print
 
Disk /dev/mapper/cl-centos7guest2: 10.7 GB, 10737418240 bytes, 20971520 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk label type: dos
Disk identifier: 0x000a965d
 
Device Boot Start End Blocks Id System
/dev/mapper/cl-centos7guest2p1 2048 2312191 1155072 83 Linux
 
Command (m for help): d
Selected partition 1
Partition 1 is deleted
 
Command (m for help): n
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p):
Using default response p
Partition number (1-4, default 1):
First sector (2048-20971519, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-20971519, default 20971519):
Using default value 20971519
Partition 1 of type Linux and of size 10 GiB is set
 
Command (m for help): w
The partition table has been altered!
 
Calling ioctl() to re-read partition table.
 
WARNING: Re-reading the partition table failed with error 22: Invalid argument.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.
[[email protected] ~]#

Tip: If you don't need additional partitions, you can use this fdisk oneliner: printf "d\nn\n\n\n\n\nw\n" | fdisk /dev/cl/centos7guest2

Now, create device mapper entries and resize the first partition (with commands appropriate for your chosen filesystem) then remove the device mapper entries.

[[email protected] ~]# kpartx -a -p p /dev/cl/centos7guest2
[[email protected] ~]# e2fsck -f /dev/mapper/cl-centos7guest2p1
e2fsck 1.42.9 (28-Dec-2013)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/cl-centos7guest2p1: 24702/73728 files (0.2% non-contiguous), 269501/288768 blocks
[[email protected] ~]# resize2fs /dev/mapper/cl-centos7guest2p1
resize2fs 1.42.9 (28-Dec-2013)
Resizing the filesystem on /dev/mapper/cl-centos7guest2p1 to 2621184 (4k) blocks.
The filesystem on /dev/mapper/cl-centos7guest2p1 is now 2621184 blocks long.
 
[[email protected] ~]# kpartx -d -p p /dev/cl/centos7guest2
[[email protected] ~]#

Editing the filesystem (optional)

Optionally before removing device mapper entries you could also mount the filesystem to edit certain data before booting, like giving the guest an IP address, hostname...

[[email protected] ~]# mount /dev/mapper/cl-centos7guest2p1 /mnt 
[[email protected] ~]# ls /mnt 
bin dev home lib64 media opt root sbin sys usr boot etc lib lost+found mnt proc run srv tmp var

Creating the guest and booting

From this point on, you'd create your guest like you would otherwise. The difference is you no longer need to waste time installing an operating system via the installer ISO, or similar.