Creating Multipass Virtual Machines with Ansible

By | October 10, 2021

In this article I will automate creation of Multipass virtual machines using Ansible. The manual process is described in an earlier article. As in the previous article, the virtual machines created will be configured as to allow for password-less login using a private key.

Prerequisites

In order to be able to create Multipass virtual machines, you will need to have Multipass installed.
The automation part require Ansible. Instructions on how to install Ansible can be found in the official Ansible documentation.
Finally, I will use some shell scripting, specifically the command ssh-keygen, when creating the public and private key-pair.

Preparations

In a location of your choice, create a directory, named as desired, that will contain the Ansible playbook and the associated files. All subsequent files will be created in this directory unless explicitly stated. I will name my project directory “CreateMultipassVMsWithAnsible”.

Create Keys and Cloud-Init Configuration with Ansible

Before being able to automate the creation of the Multipass virtual machines, one key-pair and one cloud-init configuration file must be created. This part will also be automated using Ansible.

First, create a cloud-init template file named “cloud-init-template.j2” with the following contents:

users:
  - default
  - name: vmadmin
    sudo:  ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - {{ public_key }}

Note line 6, in which the contents of a variable named “public_key” will replace the “{{ public_key }}” block when a file is created from the template.

Next, create a file named “create-keys-and-cloudinit..yml” with the following contents:

---
# Creates vmadmin keypair that will be used to log into the Multipass VMs
# and the cloud-init file use when create the VMs.
# Deletes any existing keys and cloud-init file.
- name: Delete any existing public and private keys
  file:
    path: "{{ item }}"
    state: absent
  with_items:
    - user_key
    - user_key.pub
- name: Create vmadmin key-pair
  shell: ssh-keygen -C vmadmin -N "" -f user_key
  delegate_to: localhost
- name: Delete any existing cloud-init file
  file:
    path: cloud-init.yaml
    state: absent
- name: Create cloud-init file inserting the public key
  template:
    src: cloud-init-template.j2
    dest: cloud-init.yaml
  delegate_to: localhost
  vars:
    public_key: "{{lookup('file', 'user_key.pub')}}"

Note that:

  • This file is not an Ansible playbook but instead a list of Ansible tasks.
    The reason for this is alignment with other parts of this example.
  • The first task deletes any existing key-files with the names “user_key” and “user_key.pub”.
  • The second task creates a key-pair for the vmadmin user with an empty password.
    This task require the ssh-keygen shell command.
  • The next task deletes any existing could-init file.
  • The last task creates a cloud-init file by inserting the public key into the cloud-init template file.
  • The last line in the above file causes the contents of the file “user_key.pub” to be read and the contents of the file stored in the variable “public_key” in preparation for inserting it into the cloud-init template.

The above two files are sufficient to create a key-pair and a cloud-init file. Next, we’ll look into automating the creation of a Multipass virtual machine.

Create Multipass Virtual Machine with Ansible

With the ability to create a key-pair and a cloud-init file we can now create another Ansible task file that creates a Multipass virtual machine. The task list is to be contained in a file named “delete-and-create-new-vm-tasks.yml” with the contents below:

---
# Deletes any existing Multipass VM with the supplied name, then creates
# a new VM with the supplied name is created with the supplied parameters.
# Prerequisites:
# A SSH keypair that will be used as one option to log into the new VM must exist.
# A cloud-init configuration file named "cloud-init.yaml" that will be used
# when creating the new VM must exist.
# Variables:
# vmname - Name of the VM that is to be create.
# vmcreationparams - Parameters that will be used when creating the new VM with Multipass.
- name: Log VM creation start
  debug:
    msg: "About to create VM {{ vmname }}..."
- name: Delete existing VM
  shell: |
    multipass info {{ vmname }} > /dev/null 2> /dev/null
    if [ $? -eq 0 ]; then
      multipass delete {{ vmname }}
      multipass purge
    fi
  delegate_to: localhost
- name: Create new VM
  shell: multipass launch --name {{ vmname }} --cloud-init cloud-init.yaml {{ vmcreationparams }}
  delegate_to: localhost
- name: Log VM creation completion
  debug:
    msg: "Finished creating VM {{ vmname }}."

Note that:

  • Again, this file does not contain an Ansible playbook but a list of Ansible tasks.
    The reason for this being a task file is that this, as we will see later, allows for including the tasks and passing different parameters.
  • This task file uses two variables as parameters.
    The first variable, “vmname”, is to contain the name of the Multipass virtual machine that is to be created. The second variable, “vmcreationparams”, is to contain the parameters to the Multipass launch command specifying memory and disk size as well as which image from which to create the virtual machine.
  • The first task logs start of creating the virtual machine.
  • The second task deletes any existing virtual machine with the name in the vmname variable.
  • The third task creates the new Multipass virtual machine.
  • The final task logs the completion of the Multipass virtual machine creation.

Putting the Pieces Together

There are now only two things remaining to be able to accomplish what I set out to do. The first thing is a file containing the information about the virtual machines to create and the second is an Ansible playbook.

Virtual Machines File

The file containing the names and configurations of the virtual machines to create is named “k3s-nodes.yml” and has the following contents. The reason for the name containing “k3s” is that I am setting up the virtual machines for my K3S laboratory cluster.

k3s-master: "--mem 3G --disk 5G 20.04"
k3s-agent-01: "--mem 4G --disk 5G 20.04"
k3s-agent-02: "--mem 5G --disk 5G 20.04"

The key of each row is the name of the virtual machine and the value is the configuration string that will be passed to Multipass when the virtual machine is created. The above configuration strings consists of memory size, disk size and Ubuntu version to be used in the virtual machine.

Main Playbook

The main playbook brings all the above pieces together. It is located in a file named “main-playbook.yml” and looks like this:

---
- name: Create Multipass VMs for a K3S cluster
  hosts: localhost

  tasks:
  - name: Read K3S cluster node VM configuration from file
    include_vars:
      file: k3s-nodes.yml
      name: k3snodes
  - name: Create keypair and cloud-init
    include_tasks: create-keys-and-cloudinit.yml
  - name: Create the K3S cluster VMs
    include_tasks: delete-and-create-new-vm-tasks.yml
    vars:
      vmname: "{{ item.key }}"
      vmcreationparams: "{{ item.value }}"
    loop: "{{ k3snodes | dict2items }}"

Note that:

  • The file contains an Ansible playbook.
  • The first task reads the contents of the “k3s-nodes.yml” file and stores the key-value pairs in a variable named “k3snodes”.
  • The next task includes the tasks from the file “create-keys-and-cloudinit.yml”.
    This task creates the SSH keys and the cloud-init file, as the filename indicates.
    Note that this task-file is only included once per execution of the playbook.
  • The final task includes the tasks from the file “delete-and-create-new-vm-tasks.yml”.
    This task loops over the items in the k3snodes variable setting the vmname variable to the key and the vmcreationparams variable to the value of each item. For each item in the k3snodes variable, the “delete-and-create-new-vm-tasks.yml” file is included, which each time creates one virtual machine.

Final Words

With all the above files in place, the Ansible playbook can now be launched from a terminal window being located in the project directory using the following command:

ansible-playbook main.yml

Output similar to the following should appear in the terminal window:

PLAY [Create Multipass VMs for a K3S cluster] **************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [localhost]

TASK [Read K3S cluster node VM configuration from file] ****************************************************************
ok: [localhost]

TASK [Create keypair and cloud-init] ***********************************************************************************
included: /Users/ivan/CreateMultipassVMsWithAnsible/create-keys-and-cloudinit.yml for localhost

TASK [Delete any existing public and private keys] *********************************************************************
changed: [localhost] => (item=user_key)
changed: [localhost] => (item=user_key.pub)

TASK [Create vmadmin key-pair] *****************************************************************************************
changed: [localhost]

TASK [Delete any existing cloud-init file] *****************************************************************************
changed: [localhost]

TASK [Create cloud-init file inserting the public key] *****************************************************************
changed: [localhost]

TASK [Create the K3S cluster VMs] **************************************************************************************
included: /Users/ivan/CreateMultipassVMsWithAnsible/delete-and-create-new-vm-tasks.yml for localhost => (item={'key': 'k3s-master', 'value': '--mem 3G --disk 5G 20.04'})
included: /Users/ivan/CreateMultipassVMsWithAnsible/delete-and-create-new-vm-tasks.yml for localhost => (item={'key': 'k3s-agent-01', 'value': '--mem 4G --disk 5G 20.04'})
included: /Users/ivan/CreateMultipassVMsWithAnsible/delete-and-create-new-vm-tasks.yml for localhost => (item={'key': 'k3s-agent-02', 'value': '--mem 5G --disk 5G 20.04'})

TASK [Log VM creation start] *******************************************************************************************
ok: [localhost] => {
    "msg": "About to create VM k3s-master..."
}

TASK [Delete existing VM] **********************************************************************************************
changed: [localhost]

TASK [Create new VM] ***************************************************************************************************
changed: [localhost]

TASK [Log VM creation completion] **************************************************************************************
ok: [localhost] => {
    "msg": "Finished creating VM k3s-master."
}

TASK [Log VM creation start] *******************************************************************************************
ok: [localhost] => {
    "msg": "About to create VM k3s-agent-01..."
}

TASK [Delete existing VM] **********************************************************************************************
changed: [localhost]

TASK [Create new VM] ***************************************************************************************************
changed: [localhost]

TASK [Log VM creation completion] **************************************************************************************
ok: [localhost] => {
    "msg": "Finished creating VM k3s-agent-01."
}

TASK [Log VM creation start] *******************************************************************************************
ok: [localhost] => {
    "msg": "About to create VM k3s-agent-02..."
}

TASK [Delete existing VM] **********************************************************************************************
changed: [localhost]

TASK [Create new VM] ***************************************************************************************************
changed: [localhost]

TASK [Log VM creation completion] **************************************************************************************
ok: [localhost] => {
    "msg": "Finished creating VM k3s-agent-02."
}

PLAY RECAP *************************************************************************************************************
localhost                  : ok=22   changed=10   unreachable=0    failed=0 skipped=0    rescued=0    ignored=0

From the above output, we can see that three virtual machines were indeed created. If using the “multipass list” command, all three virtual machines should be up and running.
Adding or removing virtual machines is as simple as modifying the “k3s-nodes.yml” file.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *