Introduction to Multipass
--
Ubuntu Virtual Machines (VMs) On-Demand
multipass
is a Command Line Interface (CLI) tool for the quick deployment of Virtual Machines (VMs) dominated by Ubuntu but other Operating Systems and applications are available. With a general trend of migrating away from VMs to containers (with the exception of microvms
like Firecracker) why would we need another VM management tool?
It’s all about the developer experience. My focus is often on Ubuntu based Open Container Initiative (OCI) or docker
format containers, but how do you determine the commands to put into either a ContainerFile
or DockerFile
? Often, this is achieved by running on a local host either natively (if running Linux), in a VM or integrated VM like Windows Subsystem for Linux (WSL). Multipass promises to abstract away all the manual steps for creating a VM on-the-fly — from testing out that curl | bash
command or compiling something from source:
So multipass
offers a better environment for honing your container file commands, building software in isolation or testing untested tools with a significantly easier interface than typical VMs. In a Linux environment the VMs are constructed with Kernel-based Virtual Machine (KVM) but that comes with traditional penalties such as disk use and allocated memory — some of which can be mitigated with advanced features described below.
The purpose of the article is to provide an introduction to multipass
.
Installation
Install Snap
The primary method of installation is using the snap
store:
# Install vis snap
sudo snap install multipasssudo: snap: command not found
Unless you are running Ubuntu, its unlikely you have Snapcraft installed. Follow the Snapcraft instructions to install on your operating system:
# Install snap (if Ubuntu derived distro)
sudo apt update && sudo apt install snapd<snip>Selecting previously unselected package snapd.
Preparing to unpack .../snapd_2.55.3+22.04ubuntu1_amd64.deb ...
Unpacking snapd (2.55.3+22.04ubuntu1) ...
Setting up squashfs-tools (1:4.5-3build1) ...
Setting up snapd (2.55.3+22.04ubuntu1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.apparmor.service → /lib/systemd/system/snapd.apparmor.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.autoimport.service → /lib/systemd/system/snapd.autoimport.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.core-fixup.service → /lib/systemd/system/snapd.core-fixup.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.recovery-chooser-trigger.service → /lib/systemd/system/snapd.recovery-chooser-trigger.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.seeded.service → /lib/systemd/system/snapd.seeded.service.
Created symlink /etc/systemd/system/cloud-final.service.wants/snapd.seeded.service → /lib/systemd/system/snapd.seeded.service.
Unit /lib/systemd/system/snapd.seeded.service is added as a dependency to a non-existent unit cloud-final.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.service → /lib/systemd/system/snapd.service.
Created symlink /etc/systemd/system/timers.target.wants/snapd.snap-repair.timer → /lib/systemd/system/snapd.snap-repair.timer.
Created symlink /etc/systemd/system/sockets.target.wants/snapd.socket → /lib/systemd/system/snapd.socket.
Created symlink /etc/systemd/system/final.target.wants/snapd.system-shutdown.service → /lib/systemd/system/snapd.system-shutdown.service.
snapd.failure.service is a disabled or a static unit, not starting it.
snapd.snap-repair.service is a disabled or a static unit, not starting it.
Processing triggers for gnome-menus (3.36.0-1ubuntu3) ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for dbus (1.12.20-2ubuntu4) ...
Processing triggers for dbus-broker (29-4build1) ...
Processing triggers for mailcap (3.70+nmu1ubuntu1) ...
Processing triggers for desktop-file-utils (0.26-1ubuntu3) ...
Test the snap store is working with example snap
:
# Install hello-world snap
sudo snap install hello-world2022-06-02T13:45:20+01:00 INFO Waiting for automatic snapd restart...
hello-world 6.4 from Canonical✓ installed# List snaps
snap listName Version Rev Tracking Publisher Notes
core 16-2.55.5 13250 latest/stable canonical✓ core
hello-world 6.4 29 latest/stable canonical✓ -# Test application
hello-worldHello World!# Check path of binary
which hello-world/snap/bin/hello-world
Install Multipass
# Install multipass
sudo snap install multipassmultipass 1.9.2 from Canonical✓ installed# List snaps
Name Version Rev Tracking Publisher Notes
core 16-2.55.5 13250 latest/stable canonical✓ core
core20 20220512 1494 latest/stable canonical✓ base
hello-world 6.4 29 latest/stable canonical✓ -
multipass 1.9.2 7174 latest/stable canonical✓ -# Check version
multipass --versionmultipass 1.9.2
multipassd 1.9.2
Multipass Usage
List available Ubuntu specific appliances / images:
multipass findImage Aliases Version Description
snapcraft:core18 18.04 20201111 Snapcraft builder for Core 18
snapcraft:core20 20.04 20210921 Snapcraft builder for Core 20
snapcraft:core22 22.04 20220426 Snapcraft builder for Core 22
snapcraft:devel 20220602 Snapcraft builder for the devel series
core core16 20200818 Ubuntu Core 16
core18 20211124 Ubuntu Core 18
18.04 bionic 20220523 Ubuntu 18.04 LTS
20.04 focal,lts 20220530 Ubuntu 20.04 LTS
21.10 impish 20220309 Ubuntu 21.10
22.04 jammy 20220531 Ubuntu 22.04 LTS
daily:22.10 devel,kinetic 20220601.1 Ubuntu 22.10
appliance:adguard-home 20200812 Ubuntu AdGuard Home Appliance
appliance:mosquitto 20200812 Ubuntu Mosquitto Appliance
appliance:nextcloud 20200812 Ubuntu Nextcloud Appliance
appliance:openhab 20200812 Ubuntu openHAB Home Appliance
appliance:plexmediaserver 20200812 Ubuntu Plex Media Server Appliance
anbox-cloud-appliance latest Anbox Cloud Appliance
charm-dev latest A development and testing environment for charmers
docker latest A Docker environment with Portainer and related tools
minikube latest minikube is local Kubernetes
Going to use a basic VM for software compilation:
# Launch a 20.04 image
multipass launch 20.04Launched: wealthy-lungfish# List images
multipass listName State IPv4 Image
wealthy-lungfish Running 10.137.0.110 Ubuntu 20.04 LTS# Check version
mp exec wealthy-lungfish -- lsb_release -aNo LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.4 LTS
Release: 20.04
Codename: focal
Practical Example: Compile ijq
Access the instance using the shell
command:
# Connect to instance
multipass shell wealthy-lungfishWelcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-113-generic x86_64)* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantageSystem information as of Thu Jun 2 14:07:00 BST 2022System load: 0.01 Processes: 105
Usage of /: 28.9% of 4.67GB Users logged in: 0
Memory usage: 20% IPv4 address for ens3: 10.137.0.110
Swap usage: 0%1 update can be applied immediately.
To see these additional updates run: apt list --upgradableLast login: Thu Jun 2 14:04:49 2022 from 10.137.0.1
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.ubuntu@wealthy-lungfish:~$
The excellent ijq
CLI tool is used to interactively explore json
files and provide the string for jq
.
Prepare the environment:
# Install make for building
sudo apt install makeReading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
make-doc
The following NEW packages will be installed:
make
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 162 kB of archives.
After this operation, 393 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu focal/main amd64 make amd64 4.2.1-1.2 [162 kB]
Fetched 162 kB in 1s (318 kB/s)
Selecting previously unselected package make.
(Reading database ... 63807 files and directories currently installed.)
Preparing to unpack .../make_4.2.1-1.2_amd64.deb ...
Unpacking make (4.2.1-1.2) ...
Setting up make (4.2.1-1.2) ...
Processing triggers for man-db (2.9.1-1) ...# Install C compiler
sudo apt install gccReading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc-9 gcc-9-base libasan5 libatomic1
libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 libctf0 libgcc-9-dev libgomp1
libisl22 libitm1 liblsan0 libmpc3 libquadmath0 libtsan0 libubsan1 linux-libc-dev manpages-dev
Suggested packages:
binutils-doc cpp-doc gcc-9-locales gcc-multilib autoconf automake libtool flex bison gdb gcc-doc
gcc-9-multilib gcc-9-doc glibc-doc
The following NEW packages will be installed:
binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc gcc-9 gcc-9-base libasan5 libatomic1
libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 libctf0 libgcc-9-dev libgomp1
libisl22 libitm1 liblsan0 libmpc3 libquadmath0 libtsan0 libubsan1 linux-libc-dev manpages-dev
0 upgraded, 28 newly installed, 0 to remove and 0 not upgraded.
Need to get 34.0 MB of archives.
After this operation, 150 MB of additional disk space will be used.
Do you want to continue? [Y/n]
Ensure build dependencies are in place by downloading and installing go
:
# Create directory for go
mkdir -p ~/source/go && cd ~/source/go# Download copy of source files
wget https://go.dev/dl/go1.18.3.linux-amd64.tar.gz--2022-06-02 14:20:42-- https://go.dev/dl/go1.18.3.linux-amd64.tar.gz
Resolving go.dev (go.dev)... 216.239.34.21, 216.239.32.21, 216.239.38.21, ...
Connecting to go.dev (go.dev)|216.239.34.21|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://dl.google.com/go/go1.18.3.linux-amd64.tar.gz [following]
--2022-06-02 14:20:43-- https://dl.google.com/go/go1.18.3.linux-amd64.tar.gz
Resolving dl.google.com (dl.google.com)... 142.250.187.206, 2a00:1450:4009:817::200e
Connecting to dl.google.com (dl.google.com)|142.250.187.206|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 141748419 (135M) [application/x-gzip]
Saving to: ‘go1.18.3.linux-amd64.tar.gz’go1.18.3.linux-amd64.tar.g 100%[=====================================>] 135.18M 10.5MB/s in 16s2022-06-02 14:20:59 (8.31 MB/s) - ‘go1.18.3.linux-amd64.tar.gz’ saved [141748419/141748419]# Extract and install (note the additional sudo)
rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz# Add go binary to PATH
export PATH=$PATH:/usr/local/go/bin# Either restart shell or source PATH variable and then test
go versiongo version go1.18.3 linux/amd64
Download and configure scdoc
used for the man
pages:
# Create directory
mkdir -p ~/source/scdoc && cd ~/source/scdoc# Download source
wget https://git.sr.ht/~sircmpwn/scdoc/archive/1.11.2.tar.gz--2022-06-02 14:27:06-- https://git.sr.ht/~sircmpwn/scdoc/archive/1.11.2.tar.gz
Resolving git.sr.ht (git.sr.ht)... 173.195.146.142
Connecting to git.sr.ht (git.sr.ht)|173.195.146.142|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/tar+gzip]
Saving to: ‘1.11.2.tar.gz’1.11.2.tar.gz [ <=> ] 12.45K --.-KB/s in 0.001s2022-06-02 14:27:07 (18.9 MB/s) - ‘1.11.2.tar.gz’ saved [12746]# Untar and enter directory
tar xzf 1.11.2.tar.gz && cd scdoc-1.11.2# Compile scdoc
makecc -std=c99 -pedantic -c -o .build/main.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/main.c
cc -std=c99 -pedantic -c -o .build/string.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/string.c
cc -std=c99 -pedantic -c -o .build/utf8_chsize.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_chsize.c
cc -std=c99 -pedantic -c -o .build/utf8_decode.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_decode.c
cc -std=c99 -pedantic -c -o .build/utf8_encode.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_encode.c
cc -std=c99 -pedantic -c -o .build/utf8_fgetch.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_fgetch.c
cc -std=c99 -pedantic -c -o .build/utf8_fputch.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_fputch.c
cc -std=c99 -pedantic -c -o .build/utf8_size.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_size.c
cc -std=c99 -pedantic -c -o .build/util.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/util.c
cc -static -o scdoc .build/main.o .build/string.o .build/utf8_chsize.o .build/utf8_decode.o .build/utf8_encode.o .build/utf8_fgetch.o .build/utf8_fputch.o .build/utf8_size.o .build/util.o
./scdoc < scdoc.1.scd > scdoc.1
./scdoc < scdoc.5.scd > scdoc.5
sed -e 's:@prefix@:/usr/local:g' -e 's:@version@:1.11.2:g' < scdoc.pc.in > scdoc.pc# Install
sudo make installmkdir -p //usr/local/bin //usr/local/share/man/man1 //usr/local/share/man/man5 //usr/local/share/pkgconfig
install -m755 scdoc //usr/local/bin/scdoc
install -m644 scdoc.1 //usr/local/share/man/man1/scdoc.1
install -m644 scdoc.5 //usr/local/share/man/man5/scdoc.5
install -m644 scdoc.pc //usr/local/share/pkgconfig/scdoc.pc# Check installation
scdoc -hUsage: scdoc < input.scd > output.roff
Prepare compilation of ijq
:
# Create directory for source code
mkdir -p ~/source/ijq && cd ~/source/ijq# Download copy of source files
wget https://git.sr.ht/~gpanders/ijq/archive/v0.3.8.tar.gz--2022-06-02 14:11:18-- https://git.sr.ht/~gpanders/ijq/archive/v0.3.8.tar.gz
Resolving git.sr.ht (git.sr.ht)... 173.195.146.142
Connecting to git.sr.ht (git.sr.ht)|173.195.146.142|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/tar+gzip]
Saving to: ‘v0.3.8.tar.gz’v0.3.8.tar.gz [ <=> ] 23.18K --.-KB/s in 0.001s2022-06-02 14:11:18 (26.2 MB/s) - ‘v0.3.8.tar.gz’ saved [23739]# Untar and change directory
tar xzvf v0.3.8.tar.gz && cd ijq-v0.3.8# Compile
makego build -ldflags="-s -w -X main.Version=0.3.8" -o ijq
go: downloading github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
go: downloading github.com/kyoh86/xdg v1.2.0
go: downloading github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
go: downloading golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
go: downloading github.com/gdamore/encoding v1.0.0
go: downloading github.com/lucasb-eyer/go-colorful v1.2.0
go: downloading github.com/mattn/go-runewidth v0.0.13
go: downloading golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
go: downloading golang.org/x/text v0.3.6
go: downloading github.com/rivo/uniseg v0.2.0
scdoc < ijq.1.scd > ijq.1# Install
sudo make installinstall -d /usr/local/bin /usr/local/share/man/man1
install -m 0755 ijq /usr/local/bin
install -m 0644 ijq.1 /usr/local/share/man/man1# Check version
ijq -Vijq 0.3.8
As a check we should compare our binary with that of the author:
# Create location for original
mkdir ~/source/ijq/original && cd ~/source/ijq/original# Get binary
https://git.sr.ht/~gpanders/ijq/refs/download/v0.3.8/ijq-0.3.8-linux-amd64.tar.gz# Extract
tar xzvf ijq-0.3.8-linux-amd64.tar.gz && cd ijq-0.3.8ijq-0.3.8/
ijq-0.3.8/COPYING
ijq-0.3.8/ijq.1
ijq-0.3.8/ijq# Generate SHA256 for original
5c3695bf344f39fc300671811a34a26d6770427c532fd339ff737b70aa02bef5 ijq# Generate SHA256 for compiled
cfbc281735765458623a8621a2506f1d9777821c829dc10700823cfe9b2b01be ijq
They are different and we’re unable to determine why as the build environment the author used isn’t specified. Nonetheless, we have demonstrated how quickly a Ubuntu VM can be launched and accessed, in this instance for software compilation.
Advanced Usage
Managing a VM is relatively straight forward; more advanced features include:
- Support for
cloud-init
- Customisable VMs in terms of CPUs and RAM
- Remote access to
multipass
— use more powerful / dedicated hardware for the VMs
The first feature is designed to support the development of cloud-init
files prior to usein the cloud. The second feature provides more control of the VM capability and the latter allows team use on dedicated hardware, without the usual remote access challenges.
Summary
Supposing we wanted to explore a different go
library for compilation or other changes, then installing these applications and dependencies on the same host would created significant issues. The problem can also be solved with other VM or container tools, so what makes multipass
different? It’s the ease of use, where a single command gives you a working installation, the VMs can be networked to different networking scenarios as well as integrating into the host. For example, multipass
comes with its own mount
command to mount a host folder in the VM itself.
Ultimately, its biased towards users of Ubuntu rather than other Linux distributions. While there are many VM tools there are no real alternatives to how well polished multipass
is in providing a seamless experience.
Attribution
All gists
, notebooks and terminal casts are by the author. All of the artwork is based on assets explicitly CC0, Public Domain license or SIL OFL and is therefore non-infringing. Theme is inspired by and based on my favourite vim
theme: Gruvbox.