Getting started with Vagrant and oVirt, from scratch

Recently, I've been playing with clustered virtualization technology. Specifically, a fully FLOSS stack powered by oVirt. I'm also a fan of Hashicorp's Vagrant tool, and figured that the hardware powering my virtualization cluster is going to be speedier than my laptop. Oh, how right I was!

Unfortunately, it's not immediately obvious how someone would get started, given a blank and empty cluster. So, how to you go from a freshly installed oVirt cluster to spinning up development VMs using Vagrant? Well, by using the following tools to automate most of the tedious work:

Preparing a template

There's some prep work for getting the oVirt vagrant plugin running. You need to prepare a template for the plugin to clone. It does not use the box format, so the Vagrant Cloud isn't much use. Normally, this would be the part that sucks a lot:

  1. Manually downloading the ISO
  2. Uploading it to the cluster
  3. Build a base box with all the settings
  4. Run the VM and step through the installer by hand
  5. Reboot the VM
  6. Clean up any artifacts from the installer and prep the system to be cloned
  7. Shutdown the VM and turn it into a template

Whew! It was tedious enough to write this out, doing so by hand is even worse. I did it once. Never again.

Thankfully, the oVirt team has a similar view (they may even agree with me) and provide modules for Ansible to help it manage oVirt clusters. From there, they have built roles to automate downloading a pre-existing image and creating a template from it, along with any settings you may want to set.

But where would you get a pre-existing image? you may ask. Thankfully, that's also already handled for you! Many Linux distributions not only provide an ISO to download, but also provide other options. For our case, a KVM image provided by the respective distribution teams is exactly what we want. Many thanks to the OpenStack team for maintaining a document on where these images exist for various Linux distributions, and other OSes.

Using Ansible to download the image

Thanks to the hard work of the distributions and the oVirt team, our task is made much simpler. All we need is a fairly simple Ansible playbook and the image-template role. My playbook looks like this:

---
- name: Create a template for Fedora 28
  hosts: ovirt-node-01
  gather_facts: false

  vars_files:
    - vars/vault_ovirt
    - vars/ovirt_roles

  vars:
    qcow_url: 'https://download.fedoraproject.org/pub/fedora/linux/releases/28/Cloud/x86_64/images/Fedora-Cloud-Base-28-1.1.x86_64.qcow2'
    template_name: fedora-28
    template_operating_system: rhel_7x64

  pre_tasks:
    - name: Download the ovirt CA certificate
      get_url:
        url: '{{ engine_base_url }}/services/pki-resource?resource=ca-certificate&format=X509-PEM-CA'
        dest: '{{ engine_cafile }}'
        mode: 0644
        validate_certs: False
    - name: Download the fedora 28 sha256sums file
      get_url:
        url: 'https://download.fedoraproject.org/pub/fedora/linux/releases/28/Cloud/x86_64/images/Fedora-Cloud-28-1.1-x86_64-CHECKSUM'
        dest: '/tmp/fedora-28-sha256sums'
        mode: 0644
    - name: Pull the individual SHA256SUM from the file
      command: awk '/^SHA256 \({{ qcow_url|basename }}\)/ { print $4 }' '/tmp/fedora-28-sha256sums'
      register: awk_output
    - name: Set image_checksum for the role
      set_fact:
        image_checksum: "sha256:{{ awk_output.stdout }}"
    - name: delete the sha256sums file
      file:
        path: '/tmp/fedora-28-sha256sums'
        state: absent

  roles:
    - ovirt.image-template

Where the files vars/ovirt_roles and vars/vault_ovirt contain a list of variables used by the template and tasks (some of them encrypted using Ansible Vault):

---
# path to download the engine self-signed ca pubkey to
engine_cafile: /root/ovirt-ca.pem
template_cluster: 'Default' # cluster name to put templates in
template_disk_storage: 'DISK_DOMAIN' # storage domain name to use

# Most Linux-es will fit within this, as a base default:
template_memory: 2GiB
template_cpu: 1
template_disk_size: 4GiB
image_path: /var/opt
template_nics:
  - name: nic1
    profile_name: ovirtmgmt
    interface: virtio
template_type: server

# The base URL of ovirt-engine
engine_base_url: 'https://engine.local/ovirt-engine'
# ...and the URL to it's API, usually just a subdirectory. 
engine_url: '{{ engine_base_url }}/api'

# The user name and password to authenticate to the engine. Recommended to
# encrypt these with vault!
engine_user: admin@internal
engine_password: 'MY_SECRET_PASSWORD'

Run that playbook, and it will download the KVM image for Fedora 28, as an example for this blog post, download the SHA256 checksum, and pass all the details to the image-template role. That role will handle uploading it to the cluster into the appropriate storage domain (aka the folder where VM disks are stored), registering it with oVirt, and configuring the template so that any freshly made clones have sane defaults.

Getting Vagrant configured

Now that you have the base KVM image, we need to tell Vagrant to use it. This is here the vagrant-ovirt4 gem comes in handy. Install it:

vagrant plugin install vagrant-ovirt4

And then create a Vagrantfile to launch our development VM:

Vagrant.configure("2") do |config|
  config.vm.box = 'ovirt4'
  config.vm.hostname = "fedora-dev"
  config.vm.box_url = 'https://github.com/myoung34/vagrant-ovirt4/blob/master/example_box/dummy.box?raw=true'

  config.vm.network :private_network, :ovirt__network_name => 'ovirtmgmt'

  config.vm.provider :ovirt4 do |ovirt|
    ovirt.url = 'https://engine.aether.earth/ovirt-engine/api'
    ovirt.username = "admin@internal"
    ovirt.password = 'MY_SECRET_PASSWORD'
    ovirt.insecure = true
    ovirt.debug = false
    ovirt.cluster = 'Default'
    ovirt.template = 'fedora-28'
    ovirt.console = 'spice'
    ovirt.memory_size = '768MB'
    ovirt.memory_guaranteed = '768MB'
    ovirt.cpu_threads = 2
    ovirt.cloud_init = <<EO_CLOUD_INIT
#cloud-config
user: vagrant
chpasswd: 
  list: |
    root:vagrant
    vagrant:vagrant
  expire: False
ssh_authorized_keys:
 - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key
runcmd:
 - echo 'Welcome to your Vagrant-built, oVirt-managed virtual machine.' > /etc/motd
 - dnf install -y ovirt-guest-agent-common
 - systemctl enable --now ovirt-guest-agent.service
EO_CLOUD_INIT
  end
end

There are a couple of things to note about this file. Make sure the string passed to ovirt.template matches the template_name ansible variable! Since the playbook I use downloads generic Fedora cloud image, I need to use cloud-init to configure the VM for Vagrant's use as a base box and install the oVirt guest agent.

Now you can run vagrant up and a couple of minutes later you have a development VM! 🎉

Expanding on this

I lied a bit, earlier. My playbook doesn't look exactly like that. I've copied and pasted it so that the role is run multiple times in the playbook, to download additional KVM images:

  • Ubuntu 18.04
  • CentOS 7
  • And my custom Arch Linux image, which unfortunately doesn't have cloud-init installed so this Vagrantfile won't work. That's coming soon, I hope.

Also, all of this automation is only possible through the enormous efforts of the oVirt, Ansible, Vagrant teams and all of the community members. Open source is awesome!

|