Recently, I’ve been looking into speeding up virtual machine deployment processes.

This has been inspired by learning and using Ansible, it’s helped me to look for ways to implement ansible.

One way I’d like to do this is automating virtual machine deployment.

In the cloud, it seems like virtual machines are deployed in seconds. Meanwhile in my environments I was taking old school approaches.

That seemed like an area of opportunity to utilize some automation.

I’ve been provisioning virtual machines on XCP-ng server and while I was deploying ubuntu server 20.04, I noticed cloud-init was there.

I’ve seen cloud-init in the past. I wasn’t sure what it was and curiousity lead me down a nice rabbit hole.

After researching, I realized this is the tool I need.

So to those who may not know what cloud init is, (according to Ubuntu docs), cloud-init is

cloud-init is the Ubuntu package that handles early initialization of a cloud instance. It is installed in the official Ubuntu live server images since the release of 18.04, Ubuntu Cloud Images and also in the official Ubuntu images available on EC2. Some of the things it configures are:

  • setting a default locale
  • setting hostname
  • generate ssh private keys
  • adding ssh keys to user’s .ssh/authorized_keys so they can log in
  • setting up ephemeral mount points

Cloud-init is not limited to ubuntu only. Your favorite flavor of Linux could support it. So far I’ve used cloud-init to deploy ubuntu and centos stream 8.

In order to configure the things listed above, you’ll need to configure two files:

user-data and meta-data

User-data is passed to cloud-init by the user to the cloud provider at launch time, usually as a parameter in the CLI, template, or cloud portal used to launch an instance.

meta-data usually holds system information, like hostname.

I was able to put these steps together after following a blog post from 2020. (see below for the blog post)

Step 1: Download cloud image

I use qemu-kvm / xen as my hypervisor. So this guide will focus on deploying cloud-init in that type of environment.

Ubuntu cloud images can be downloaded here: https://cloud-images.ubuntu.com

A Cloud image is pretty much a ‘’template" for said operating system. It is a bootable pre-installed image of the operating system.

Step 2: Prep and Create Disk Image

Now that you have a cloud image, the next step is to prepare and create a disk image from the cloud image.

One benefit of QCOW2 (Qemu copy on write) disk format is it allows you to utilize the base image in a copy on write mode. Wikipedia states it as,

The qcow format also allows storing changes made to a read-only base image on a separate qcow file by using copy on write. This new qcow file contains the path to the base image to be able to refer back to it when required. When a particular piece of data has to be read from this new image, the content is retrieved from it if it is new and was stored there; if it is not, the data is fetched from the base image.

Using the cloud image as our base, we can save a lot of space on our drives.

To prep the disk image run this command

$ qemu-img create -b focal-server-cloudimg-amd64.img -f qcow2 -F qcow2 ubuntu.qcow2 15G

That command utilized the ubuntu focal-server image as the base image for the new “virtual machine” image ubuntu.qcow2.

Name your virtual machine image whatever you like.

Step 3: Create Cloud-init files

As mentioned above, you’ll need two files user-data and meta-data . These files are in YAML format.

If you’ve never used YAML, here’s a youtube video to help you get started:

Yaml Tutorial | Learn YAML in 18 mins - YouTube

meta-data

The meta data file should have this information

instance-id: <name of vm>
local-hostname: <hostname of vm>

Substitute the values with what you’d like to name your vm. For example, I used instance-id: ubuntu_01 for my instance-id value.

user-data

Next, create the user-data file

#cloud-config

users:
  - name: ubuntu
    ssh_authorized_keys:
      - <ssh public key here>
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    groups: sudo
    shell: /bin/bash

user-data file has different formats. I used the #cloud-config format as it’s pretty to use.

This user-data file is creating a user named ubuntu, utilizes an ssh key to gain remote access into the virtual machine, allows passwordless sudo, adds ubuntu to the sudo group, and sets ubuntu’s shell as bash.

Step 4: Prep cidata iso image

The cidata iso image packs user-data and meta-data1 into an iso image, to be read during the deployment process.

genisoimage is the package used to do so.

genisoimage -output cidata.iso -V cidata -r -J user-data meta-data

Step 5: Deploy the VM

In my previous post, we’ll utilize the same steps to create vm’s, but we’ll make a few changes.

$ virt-install --name=ubuntu_01 --ram=2048 --vcpus=1 --import --disk path=ubuntu.qcow2,format=qcow2 --disk path=cidata.iso,device=cdrom --os-variant=ubuntu20.04 --network defaults --graphics spice,listen=0.0.0.0 --noautoconsole

# output 
Starting install...
Domain creation completed.
  • --import option is set so virt-install knows we’re importing an image with an operating system already installed

  • --network defaults is set for me because without it, I wasn’t getting an IP address assigned from DHCP.

  • --noautoconsole is to tell virt-install to exit out after vm creation and not to open a console. An important step for automating virtual machines.

Conclusion

The virtual machine should now be provisioned in 10-20 seconds. You’ll be able to ssh directly into that virtual machine using your private key and the virtual machine’s IP address.

Now there are times when I’m setting up new things, the old school approach is good before automating to ensure that things are working correctly.

However, when certain tasks are repatitive, it’s a good idea to take from the coding phrase DRY. (Don’t repeat yourself). To further build an agile / lean environments.

Hopefully this short guide gave a good start for generating the necessary images for deploying the virtual machines the cloud way.

This post was inspired and much credit to the author of this blog post for the practical steps Creating a VM using Libvirt, Cloud Image and Cloud-Init