Howto use centos cloud images with cloud-init on KVM/libvirtd

6 minute read

Images versus unattended setup


Unattended setup

In a traditional environment, systems are installed from a CDROM. The configuration is executed by the system administrator through the installer. This soon becomes a borning and unpractical task when we need to set up a lot of systems also it is important that systems are configured in same - and hopefully correct - way.

In a traditional environment, this can be automated by booting via BOOTP/PXE boot and configured is by a system that “feeds” the installer. Examples are:

Cloud & co


In a cloud environment, we use images to install systems. The system automation is generally done by cloud-init. Cloud-init was originally developed for Ubuntu GNU/Linux on the Amazon EC2 cloud. It has become the de facto installation configuration tool for most Unix like systems on most cloud environments.

Cloud-init uses a YAML file to configure the system.


Most GNU/Linux distributions provide images that can be used to provision a new system. You can find the complete list on the OpenStack website

The OpenStack documentation also describes how you can create your own base images in the OpenStack Virtual Machine Image Guide

Use a centos cloud image with libvirtd

Download the cloud image


Download the latest “GenericCloud” centos 7 cloud image and sha256sum.txt.asc sha256sum.txt from:


You should verify your download - as always against a trusted signing key -

On a centos 7 system, the public gpg is already installed at /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

Verify the fingerprint


staf@centos7 iso]$ gpg --with-fingerprint /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
pub  4096R/F4A80EB5 2014-06-23 CentOS-7 Key (CentOS 7 Official Signing Key) <>
      Key fingerprint = 6341 AB27 53D7 8A78 A7C2  7BB1 24C6 A8A7 F4A8 0EB5
[staf@centos7 iso]$ gpg --with-fingerprint /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

and verify the fingerprint, the fingerprints that are used by centos are listed at:

Import key

Import the pub centos gpg key:

[staf@centos7 iso]$ gpg --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
gpg: key F4A80EB5: public key "CentOS-7 Key (CentOS 7 Official Signing Key) <>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
[staf@centos7 iso]$ 

List the trusted gpg key:

staf@centos7 iso]$ gpg --list-keys
pub   4096R/F4A80EB5 2014-06-23
uid                  CentOS-7 Key (CentOS 7 Official Signing Key) <>

[staf@centos7 iso]$ gpg --list-keys

Verify the sha256sum file

[staf@centos7 iso]$ gpg --verify sha256sum.txt.asc
gpg: Signature made Thu 31 Jan 2019 04:28:30 PM CET using RSA key ID F4A80EB5
gpg: Good signature from "CentOS-7 Key (CentOS 7 Official Signing Key) <>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 6341 AB27 53D7 8A78 A7C2  7BB1 24C6 A8A7 F4A8 0EB5
[staf@centos7 iso]$ 

The key fingerprint must match the one of RPM-GPG-KEY-CentOS-7.

Verify the iso file

[staf@centos7 iso]$ xz -d CentOS-7-x86_64-GenericCloud-1901.qcow2.xz
[staf@centos7 iso]$ sha256sum -c sha256sum.txt.asc 2>&1 | grep OK
CentOS-7-x86_64-GenericCloud-1901.qcow2: OK
[staf@centos7 iso]$ 



The image we download is a normal qcow2 image, we can see the image information with qemu-info

[root@centos7 iso]# qemu-img info CentOS-7-x86_64-GenericCloud-1901.qcow2
image: CentOS-7-x86_64-GenericCloud-1901.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 895M
cluster_size: 65536
Format specific information:
    compat: 0.10
[root@centos7 iso]# 

Copy & resize

The default image is small - 8GB - we might be using the image to provision other systems so it better to leave it untouched.

Copy the image to the location where we’ll run the virtual system.

[root@centos7 iso]# cp -v CentOS-7-x86_64-GenericCloud-1901.qcow2 /var/lib/libvirt/images/tst/tst.qcow2
'CentOS-7-x86_64-GenericCloud-1901.qcow2' -> '/var/lib/libvirt/images/tst/tst.qcow2'
[root@centos7 iso]# 

and resize it to the required size:

[root@centos7 iso]# cd /var/lib/libvirt/images/tst
[root@centos7 tst]# qemu-img resize tst.qcow2 20G
Image resized.
[root@centos7 tst]# 


We’ll create a simple cloud-init configuration file and generate an iso image with cloud-localds. This iso image holds the cloud-init configuration and will be used to setup the system during the bootstrap.

Install cloud-utils

** It’s important to NOT install cloud-init on your KVM host machine. ** This creates a cloud-init service that runs during the boot and tries to reconfigure your host. Something that you probably don’t want on your KVM hypervisor host.

The cloud-util package has all the tool we need to convert the cloud-init configuration files to an iso image.

[root@centos7 tst]# yum install -y cloud-utils
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base:
 * extras:
 * updates:
Resolving Dependencies
--> Running transaction check
---> Package cloud-utils.x86_64 0:0.27-20.el7.centos will be installed
--> Processing Dependency: python-paramiko for package: cloud-utils-0.27-20.el7.centos.x86_64
--> Processing Dependency: euca2ools for package: cloud-utils-0.27-20.el7.centos.x86_64
--> Processing Dependency: cloud-utils-growpart for package: cloud-utils-0.27-20.el7.centos.x86_64
--> Running transaction check
---> Package cloud-utils-growpart.noarch 0:0.29-2.el7 will be installed
---> Package euca2ools.noarch 0:2.1.4-1.el7.centos will be installed
--> Processing Dependency: python-boto >= 2.13.3-1 for package: euca2ools-2.1.4-1.el7.centos.noarch
--> Processing Dependency: m2crypto for package: euca2ools-2.1.4-1.el7.centos.noarch
---> Package python-paramiko.noarch 0:2.1.1-9.el7 will be installed
--> Running transaction check
---> Package m2crypto.x86_64 0:0.21.1-17.el7 will be installed
---> Package python-boto.noarch 0:2.25.0-2.el7.centos will be installed
--> Finished Dependency Resolution

Dependencies Resolved

 Package                    Arch         Version                   Repository     Size
 cloud-utils                x86_64       0.27-20.el7.centos        extras         43 k
Installing for dependencies:
 cloud-utils-growpart       noarch       0.29-2.el7                base           26 k
 euca2ools                  noarch       2.1.4-1.el7.centos        extras        319 k
 m2crypto                   x86_64       0.21.1-17.el7             base          429 k
 python-boto                noarch       2.25.0-2.el7.centos       extras        1.5 M
 python-paramiko            noarch       2.1.1-9.el7               updates       269 k

Transaction Summary
Install  1 Package (+5 Dependent packages)

Total download size: 2.5 M
Installed size: 12 M
Downloading packages:
(1/6): cloud-utils-growpart-0.29-2.el7.noarch.rpm               |  26 kB  00:00:01     
(2/6): cloud-utils-0.27-20.el7.centos.x86_64.rpm                |  43 kB  00:00:01     
(3/6): euca2ools-2.1.4-1.el7.centos.noarch.rpm                  | 319 kB  00:00:01     
(4/6): m2crypto-0.21.1-17.el7.x86_64.rpm                        | 429 kB  00:00:01     
(5/6): python-boto-2.25.0-2.el7.centos.noarch.rpm               | 1.5 MB  00:00:02     
(6/6): python-paramiko-2.1.1-9.el7.noarch.rpm                   | 269 kB  00:00:03     
Total                                                     495 kB/s | 2.5 MB  00:05     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : python-boto-2.25.0-2.el7.centos.noarch                              1/6 
  Installing : python-paramiko-2.1.1-9.el7.noarch                                  2/6 
  Installing : cloud-utils-growpart-0.29-2.el7.noarch                              3/6 
  Installing : m2crypto-0.21.1-17.el7.x86_64                                       4/6 
  Installing : euca2ools-2.1.4-1.el7.centos.noarch                                 5/6 
  Installing : cloud-utils-0.27-20.el7.centos.x86_64                               6/6 
  Verifying  : m2crypto-0.21.1-17.el7.x86_64                                       1/6 
  Verifying  : cloud-utils-growpart-0.29-2.el7.noarch                              2/6 
  Verifying  : python-paramiko-2.1.1-9.el7.noarch                                  3/6 
  Verifying  : python-boto-2.25.0-2.el7.centos.noarch                              4/6 
  Verifying  : euca2ools-2.1.4-1.el7.centos.noarch                                 5/6 
  Verifying  : cloud-utils-0.27-20.el7.centos.x86_64                               6/6 

  cloud-utils.x86_64 0:0.27-20.el7.centos                                                                                                                                     

Dependency Installed:
  cloud-utils-growpart.noarch 0:0.29-2.el7      euca2ools.noarch 0:2.1.4-1.el7.centos      m2crypto.x86_64 0:0.21.1-17.el7      python-boto.noarch 0:2.25.0-2.el7.centos     
  python-paramiko.noarch 0:2.1.1-9.el7         

[root@centos7 tst]# 

Cloud-init configuration

A complete overview of cloud-init configuration directives is available at

We’ll create a cloud-init configuration file to update all the packages - which is always a good idea - and to add a user to the system.

A cloud-init configuration file has to start with #cloud-config, remember this is YAML so only use spaces…

We’ll create a password hash that we’ll put into your cloud-init configuration, it’s also possible to use a plain-text password in the configuration with chpasswd or to set the password for the default user. But it’s better to use a hash so nobody can see the password. Keep in mind that is still possible to brute-force the password hash.

Some GNU/Linux distributions have the mkpasswd utility this is not available on centos. The mkpasswd utility is part of the expect package and is something else…

I used a python one-liner to generate the SHA512 password hash

python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'

Execute the one-liner and type in your password:

[staf@centos7 ~]$ python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
<your hash>
[staf@centos7 ~]$ 

Create config.yaml - replace <your_user>, <your_hash>, <your_ssh_pub_key> - with your data:

package_upgrade: true
  - name: <your_user>
    groups: wheel
    lock_passwd: false
    passwd: <your_passord_hash>
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
      - <your_public_ssh_key>

And generate the configuration iso image:

root@centos7 tst]# cloud-localds config.iso config.yaml
wrote config.iso with filesystem=iso9660 and diskformat=raw
[root@centos7 tst]# 

Create the virtual system

Libvirt has predefined definitions for operating systems. You can query the predefined operation systems with the osinfo-query os command.

We use centos 7, we use osinfo-query os to find the correct definition.

[root@centos7 tst]# osinfo-query  os | grep -i centos7
 centos7.0            | CentOS 7.0                                         | 7.0      |            
[root@centos7 tst]# 

Create the virtual system:

virt-install \
  --memory 2048 \
  --vcpus 2 \
  --name tst \
  --disk /var/lib/libvirt/images/tst/tst.qcow2,device=disk \
  --disk /var/lib/libvirt/images/tst/config.iso,device=cdrom \
  --os-type Linux \
  --os-variant centos7.0 \
  --virt-type kvm \
  --graphics none \
  --network default \

The default escape key - to get out the console is ^[ ( Ctrl + [ )

** Have fun! **


Leave a comment