This Ansible role configures a host to run as a QEMU/KVM host using libvirt and then provisions new user-defined VMs.
The role includes two sub-roles which can be used indepedently. The libvirt_host & virt_provisioner. The role has been tested on the following Linux images:
- debian-8.10.0-amd64-netinst
- debian-9.5.0-amd64-netinst
- ubuntu-16.04.5-server-amd64
- ubuntu-18.04.1-live-server-amd64
- CentOS-7-x86_64-Minimal-1810
- Checks the distribution release and if it is "Ubuntu", it installs required Ubuntu repos, since the virt-install package is missing by default.
- Installs the required packages per detected distro, and starts libvirt daemon
- Configures the minimum networking required by libvirt per detected release. It creates a bridge interface and includes the default interface in the bridge.
- Restarts network services in order to apply the new network settings
- Parses the VM definition file and sets various facts that are going to be used in the following tasks.
- Creates VM disk directories if they are not exist and moves any pre-existing VM disks in VM disk directory.
- Setup the boot image files. According to the boot-image type (url,file,path,compressed) it executes the necessary tasks so the boot-image file to be in the default or user-defined libvirt boot folder.
- Invokes the virt-install commands for each Defined VM.
There is an extra task included in the role's task folder which can be used indepedently for deleting vms. See the undefine.yml playbook file to see an example on how to use this task. The only requirement for the undefine_vm.yml task is to provide a vars dictionary in the form:
vms:
k1:
name: 'VMname1'
k2:
name: 'VMname2'
⋮
kx:
name: 'VMnamex'
By finishing the playbook, the nd result will be a libvirt host that runs all the vms which are defined in the vm definition file. The role has completed its job. If any installation needed from now on in each VM, it is out of the scope of this role. The user may connect with VNC to each VM and completes any required tasks.
If you want to setup a new VM host from scratch keep reading. If you already have a working VM host and just provision new VMs then you can skip to virt_provisioner.
Just add the following in the playbook in order to use the role.
roles:
- libvirt_host
The user may have to define/override the role's default variables to meet their needs. The list of the variables the user can set is:
- vm_host_br_ip:
This is the IP address of the vm host bridge interface following network setup.
Type: String
Default: The detected IP of the target vm host- vm_host_br_netmask:
The netmask of the vm host bridge interface.
Type: String
Default: The detected netmask of the target vm host- vm_host_br_gw:
The gateway IP address of the vm host bridge interface.
Type: String
Default: The detected GW IP address of the target vm host- vm_host_br_prefix_size:
The CIDR notation of the bridge interface netmask.
Type: String
Default: '24'- vm_host_br_dns:
The DNS IP addresses list. This var must be declared as a list even if there is one item only.
Type: List
Default: ['ansible_default_ipv4.gateway', '1.1.1.1' ]- vm_host_br_name:
The name of the bridge interface, e.g. 'br0'.
Type: String
Default: 'br0'- vm_host_search_domain:
The list containing domain names of your organization.
Type: List
Default:- vm_host_br_if:
The interface name that will be connected to the bridge, e.g 'eth0'
Type: String
Default: Ansible Detected Default Interface- vm_host_br_network:
The network address of the bridge ip address.
Type: String
Default: Calculated automatically from the bridge IP address using regex.- vm_host_br_broadcast:
The broadcast address of the bridge ip address.
Type: String
Default: Calculated automatically from the bridge IP address using regex.- vm_host_repos_ubuntu:
A list of repositories for Ubuntu.
Type: List
Default: Default universe archive repos.- packages:
A dictionary containing the necessary packages for each linux release.
Type: Dict
Default: Packages for Debian, Ubuntu and CentOS.
For more information on default values, please check defaults/main.yml file. It is advised to not make any changes there but to the vars/main.yml since the latter takes precedence.
If the user do not specify any variables, the role will execute succefully according to the target's detected network settings.
This is the part where new VMs are created based on the VM definition file. The file is located in vars/main.yml and the VMs are defined as key,value pairs of the 'vms' variable.
The commited version contains an example of how this file is structured. The variables that the user can override are the following:
- libvirt_boot_image_folder:
The folder path where the libvirt boot images will stored. These are also libvirt default.
Type: String
Default: '/var/lib/libvirt/boot/'- libvirt_vm_disk_folder
The folder path where the libvirt vm disks will stored. These are also libvirt default.
Type: String
Default: 'var/lib/libvirt/images/'- vms:
The VM definition dictionary. The values of the dict items are the VMs to be provisioned.
Type: Dict
Default:
Apart from disks,cdrom and networks all the following keys that can be used in a vm declaration are the same as the virt-install options and arguments. A user can include any option in the form of "option: 'argument". In case of options that take no argumemnts the user may set the key with an empty string as a value.
Disks:
This key is a list variable that the user declares to the vm definition and corresponds to the --disk option of the virt-install utility. The disks variable must be declared as a list even in the case of just one item. During the play the set_facts tasks will parse the variable and for each item in this list, it will construct the --disk "{{item}}" arguments that will pass to the resulted virt-install command.
The user can define new disks by adding the size option on the disk declaration, as per virt-install manual but they can also use existing disks by ommiting the size option. In this case the user must add the existing disk or a link to the existing disk in the virt_provisioner/files folder.
cdrom:
This key can accept different argument types so that's why is different from virt-install's. For user convinience the key can accept the following types as values:
- urls
- filenames
- paths
If the argument is a url, then the role will donwload the file, it will decompress it if is an unarchived compressed iso and it will put it in the boot images folder, ready to be used during VM installation.
If the argument is just a filename depicting that a local iso is present, such as boot-image.iso, then it will copy the file from the virt_provisioner/files folder to the boot-images folder to be used during VM installation. Warning: the user must have a copy or a link to the actual file in the files folder.
If the argument is a path, it will look for the defined file in that path on the target machine host. This assumes that the file is present on the vm host and it will get that path as a cdrom argument during run time. Beware in that case the file's permission. The safest way to use the boot image iso is to include it in the libvirt's default boot image directory.
networks:
This list contains the network definition arguments for the VM. Since, the vms variable is a dictionary and a dictionary cannot have the same name for multiple keys, in the case of networks, a user must declare them as item of that list. During the runtime the role will add the --network "{{item}}" to the virt-install command.
For more details on how to provision new VMs, check the virt_provisioner/vars/main.yml file where actual working examples are defined, since these were the exact that I used in my lab. If you run now the roles from the begining, without touching any variable you should get two running VMs, provided that you have 6GB of RAM or more.