I stumbled upon a great project from Canonical called Multipass which allows you to run lightweight virtual machines on your laptop/workstation and it's super fast!

It's using the native hypervisors of all supported platforms, so you can run Multipass on Mac, Linux and Windows.

I've found Multipass super helpful with development work, running a mini local cloud, running proof of concepts, and the best of all is that I don't have to spend money as it's all running on your workstation.

What we will be doing

In this tutorial we will be doing the following:

  • Install Multipass on Mac
  • Setup our cloud-init.yaml that we will use to bootstrap our VM
  • Overview of Mutlipass commands
  • Exec into our VM and look at the outputs of our bootstrap
  • Using the Multipass CLI for scripting from our host

Install Multipass

I will be installing Multipass for Mac, but if you are looking to install on Linux or Windows, you can have a look at this document

$ brew cask install multipass

Verify that our installation succeeded:

$ multipass version
multipass  1.0.0+mac

Preparing the bootstrap process

The awesome thing about multipass is that we can use cloud-init to streamline our bootstrap process.

First create a ssh key:

$ sh-keygen -b 2048 -f ~/.ssh/multipass -t rsa -q -N ""

Create the cloud-init file that we will be using:

$ touch ./cloud-init.yaml

We need to provide the ssh public key to our cloud-init file in order for us to ssh to our multipass vm with our ssh private key.

$ cat ~/.ssh/multipass.pub
ssh-rsa AAAAB3.......hh32R [ruan@mbp]

Copy the contents of your public key and add the full content to your cloud-init file:

$ cat ./cloud-init.yaml
#cloud-config
ssh_authorized_keys:
  - ssh-rsa AAAAB3.......hh32R ruan@mbp

Next we want our bootstrap process to update our package repositories and install 2 packages:

$ cat ./cloud-init.yaml
...

package_update: true

packages:
 - curl
 - jq

I will also provide an example on how to write content to a file, bootcmd and rundcmd.

Bootcmd will happen early in the bootprocess and runcmd will run after. So when you will be running commands such as installing packages for example you will be using runcmd:

$ cat ./cloud-init.yaml
...

write_files:
  - content: |-
      my dummy content

    owner: ubuntu:ubuntu
    path: /home/ubuntu/file.txt
    permissions: '0644'

bootcmd:
  - echo $(whoami) > /root/boot.txt

runcmd:
  - cp /root/boot.txt /home/ubuntu/boot_moved.txt
  - chown ubuntu:ubuntu /home/ubuntu/boot_moved.txt
  - curl -s http://dummy.restapiexample.com/api/v1/employees | jq '.data[] | select(.id == "1") | .employee_name' > /home/ubuntu/api_output.txt
The full content of our cloud-init.yaml will be as follows:

#cloud-config
ssh_authorized_keys:
  - ssh-rsa ssh-rsa AAAAB3.......hh32R ruan@mbp

package_update: true

packages:
 - curl
 - jq

write_files:
  - content: |-
      my dummy content

    owner: ubuntu:ubuntu
    path: /home/ubuntu/file.txt
    permissions: '0644'

bootcmd:
  - echo $(whoami) > /root/boot.txt

runcmd:
  - cp /root/boot.txt /home/ubuntu/boot_moved.txt
  - chown ubuntu:ubuntu /home/ubuntu/boot_moved.txt
  - curl -s http://dummy.restapiexample.com/api/v1/employees | jq '.data[] | select(.id == "1") | .employee_name' > /home/ubuntu/api_output.txt

In summary the VM will boot, the ssh configuration will be updated to allow us to use our private ssh key to ssh.

Update our package repository, install curl and jq.

Write contents to a file located at /home/ubuntu/file.txt , write the output of the command whoami to /root/boot.txt, then copies the boot file to /home/ubuntu/boot_moved.txt, updates the permissions for the ubuntu user

Then run's a curl command to a API endpoint and redirects the output to /home/ubuntu/api_output.txt

Boot your VM with Multipass

I will be using Ubuntu Bionic image for my VM, if you are looking for something else, you can find other images by doing:

$ multipass find
Image                   Aliases           Version          Description
snapcraft:core          core16            20200221         Snapcraft builder for Core 16
snapcraft:core18                          20200221         Snapcraft builder for Core 18
16.04                   xenial            20200218.1       Ubuntu 16.04 LTS
18.04                   bionic,lts        20200218         Ubuntu 18.04 LTS

I will be creating a VM with 1 CPU, 512MB of Memory and a Disk on 1GB. You do not have to specify the values if you want the defaults:

$ multipass launch bionic \
  --name multipass-demo \
  --cpus 1 \
  --mem 512m \
  --disk 1G \
  --cloud-init ./cloud-init.yaml

Launched: multipass-demo

Overview of your VM

Let's have a look at our running multipass VMs:

$ multipass list
Name                    State             IPv4             Image
multipass-demo          Running           192.168.64.8     Ubuntu 18.04 LTS

To get information about your VMs load, disk usage memory and IP address:

$ multipass info multipass-demo
Name:           multipass-demo
State:          Running
IPv4:           192.168.64.8
Release:        Ubuntu 18.04.4 LTS
Image hash:     3c3a67a14257 (Ubuntu 18.04 LTS)
Load:           0.24 0.23 0.09
Disk usage:     1.1G out of 2.0G
Memory usage:   71.4M out of 481.7M

We can also run commands from the host on the VM:

$ multipass exec multipass-demo -- df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       2.0G  1.2G  842M  58% /

Access your Multipass VM

You can access your VM by using exec or ssh, to exec into your VM:

$ multipass exec multipass-demo -- /bin/bash
[multipass-demo]:~$

To SSH into your VM, get the IP address:

$ multipass info multipass-demo | grep 'IPv4'
IPv4:           192.168.64.8

Then use your private key to SSH to your VM:

$ ssh -i ~/.ssh/multipass ubuntu@192.168.64.8
[ubuntu@multipass-demo]:~$

Once you are logged in, have a look at the content that was published by our commands from the cloud-init:

$ cat file.txt
my dummy content

$ cat boot_moved.txt
root

$ cat api_output.txt
"Tiger Nixon"

Scripting with Multipass

We will assign 3 variables, host's hostname, VM hostname and VM IP:

$ MY_HOSTNAME=$(hostname)

$ MULTIPASS_VM_HOSTNAME=$(multipass exec multipass-demo -- cat /etc/hostname)

$ MULTIPASS_VM_IP=$(multipass info multipass-demo | grep 'IPv4' | awk '{print $2}')

See if you can access those environment variables:

$ echo $MY_HOSTNAME
Ruan-MPB

$ echo $MULTIPASS_VM_HOSTNAME
multipass-demo

$ echo $MULTIPASS_VM_IP
192.168.64.8

Run a basic script to write a file with the environment variables content to the VM:

$ multipass exec multipass-demo -- bash -c "echo -e \"- Hostname: ${MY_HOSTNAME} \n\- VM Hostname: ${MULTIPASS_VM_HOSTNAME} \n- VM IP: ${MULTIPASS_VM_IP}\" > /tmp/output.txt"

Read the file that we created:

$ multipass exec multipass-demo -- cat /tmp/output.txt
- Hostname: Ruan-MPB
- VM Hostname: multipass-demo
- VM IP: 192.168.64.8

Delete the VM

To delete the VM:

$ multipass stop multipass-demo
$ multipass delete multipass-demo
$ multipass purge

Thank You

Hope you found this useful, I really enjoy Multipass and helped me a lot to do proof of concepts and development work, where I did not have to spend money on cloud instances.