This is a set of tools for use with the Firecracker MicroVM hypervisor. These scripts can be used to:
- Build a kernel image suitable for use with Firecracker.
- Build a root filesystem image suitable for use with Firecracker.
- Start a VM using Firecracker.
These scripts are suitable for non-interactive use, e.g. in a cronjob that rebuilds images or launches MicroVMs.
These scripts generally do not have particularly exotic dependencies and should simply run on most modern Linux systems. The dependencies are listed here for completeness and for the benefit of those, who run non-mainstream systems or containers.
- bash
- GNU Coreutils or BusyBox
- GNU wget
- curl
- gnupg >= 2.1.12
- GNU grep
- tar (GNU, BSD or BusyBox)
- xz
- gzip (optional)
- bzip2 (optional)
- file
- binutils (or BusyBox)
- patch (or BusyBox) (optional)
- tput (ncurses, optional)
- Requirements to compile the Kernel
- bash
- GNU Coreutils or BusyBox
- GNU wget
- curl
- gnupg >= 2.1.12
- util-linux
- newuidmap, newgidmap (shadow)
- awk (or BusyBox)
- GNU grep
- GNU tar or BSD tar (or BusyBox, but with limited functionality)
- e2fsprogs
- btrfs-progs (optional)
- tput (ncurses, optional)
- Python >= 3.8
- packaging (optional)
- Firecracker (and Jailer)
- iproute2 (optional)
All scripts provide usage information when called with the parameter --help
or -h
.
This section provides additional information.
This scripts builds a kernel image that can be used by Firecracker.
Refer to build-kernel.sh --help
for a list of parameters.
All parameters are optional.
When called without any parameters, build-kernel.sh
does the following:
- Determine the latest stable kernel version.
- Download the source code for this version.
- Verify the PGP signature on the archive.
- Extract the source code to a temporary directory.
- Copy the configuration file template that is most specific for the given version to the kernel build directory.
- Completes the kernel configuration. If this requires user input and the script does not run from a terminal, this step fails.
- Build the kernel image.
- Copy the kernel image to the current working directory with a filename indicating the version.
- Clean up the temporary directory.
Optional parameters can influence this sequence to some degree and eliminate some of the automatic decisions. It is also possible to apply custom patches to the kernel source before compiling.
Running this script as root
is neither required nor recommended.
This script builds one or more root filesystem images that can be used with Firecracker.
Refer to build-rootfs.sh --help
for a list of parameters.
All parameters are optional.
When called without parameters, build-rootfs.sh
builds all rootfs images described by .rootfs
files in the current directory and places the resulting images in the current directory.
To build only specific images, specify the respective .rootfs
files on the command line.
Note that images may expand on other images.
This only works if the base image(s) are built in the same call to build-rootfs.sh
and are ordered before the depending image(s).
Having the resulting image file available is not sufficient.
Running this script as root
is neither required nor recommended.
build-rootfs.sh
automatically establishes namespace confinement.
It uses an environment similar to a container when running commands "in" the rootfs image, such as installing software using the rootfs image's package manager.
This means, however, that subuids and subgids need to be set up for the user running build-rootfs.sh
.
To allocate a range of 65536 subuids and subgids run the following command (or similar):
usermod --add-subuid 1065536-1131071 --add-subgids 1065536-1131071 <user>
It is important to ensure that the ranges allocated to different users do not overlap!
Files with the extension .rootfs
(by convention) define, how to programmatically build a rootfs image.
These files use a simple description language that is documented in the following.
Empty lines and lines starting with #
are ignored.
All other lines need to start with a command, followed by a number of parameters specific to the command.
Parameters are separated by whitespace.
To include a whitespace in a parameter value, it needs to be escaped by \
, e.g. \
. To include a literal \
in a parameter, use \\
.
The following commands are implemented:
Sets the umask value used when creating the filesystem image. Expects one parameter and the is the umask value. For example, to restrict all access to the image for 'other users':
UMASK 027
The default value is inherited from the environment from which build-rootfs.sh
is called.
Every .rootfs
file needs to contain this command. It describes the base used for the image.
Multiple forms are supported:
FROM scratch
Start with a completely empty root filesystem.FROM some.img
Populate the rootfs image with the contents ofsome.img
. Note that this only works ifsome.rootfs
was already built bybuild-rootfs.sh
in the same script invocation. The resulting.img
file alone is not sufficient.FROM some.tar
Populate the rootfs image with the contents of a (possibly compressed) tar archivesome.tar
. Accepted file extensions are.tar
,.tar.*
,.tgz
,.tbz
,.tbz2
,.tlz
,.txz
,.tZ
and.tzst
.FROM url [signature_url [key [...]]]
Download a (possibly compressed) tar archive fromurl
and extract it into the image. The URL of a detached PGP signature for the archive can be specified assignature_url
. If one or morekey
s are given, the signature is verified against these keys. If nokey
s are given, the signature is verified against the default gnupg keyring of the user runningbuild-rootfs.sh
.FROM name[:version]
This is a convenient short-hand for certain known bases. At this time, the following values are accepted forname
:alpine
: the Alpine Linux minimal root filesystemgentoo-*
: a Gentoo Linux stage3 archive. Gentoo provides various stage3 archives. To use a specific archive, include its "keywords" inname
, e.g.gentoo-hardened-nomultilib-selinux-openrc
. The namegentoo
is short forgentoo-nomultilib-openrc
. Ifversion
is set, that specific version is used. If it is not set,latest
is assumed, which makesbuild-rootfs.sh
detect the latest (stable) version of the given base.
Note: It is possible to use multiple FROM
directives.
Bases are merged in the order of appearance, i.e. files from later FROM
commands overwrite files from previous ones.
Set the type of filesystem used to format the rootfs image.
Expects one parameter and that is the name of the filesystem.
For example, to use ext2
:
FILESYSTEM ext2
Valid values are:
ext2
(default)ext3
ext4
btrfs
If FILESYSTEM
is use multiple times, only the last instance is taken into account.
There are no restrictions on where this command may appear, in particular it does not need to (but may) be before the first FROM
directive.
Sets the maximum size of the resulting rootfs image. Expects one parameter and that is the size in MiB. For example, to limit the filesystem to 128 MiB (which is the default) value:
MAX_SIZE 128
It is recommended setting this directive before the first FROM
command, but this is not strictly necessary.
If MAX_SIZE
is used later, the limit is adjusted accordingly.
However, until then, the default value is in effect, which would cause issues if the base set up by FROM
needs more space.
build-rootfs.sh
builds the rootfs image in memory.
Limiting the size of the filesystem therefore serves as a limit on the amount of memory used in the process.
Sets the minimum size of the resulting rootfs image. Expects one parameter and that is the size in MiB. For example, to request at least 0 MiB (which is the default) value:
MIN_SIZE 0
The filesystem will take up at least as much space as needed by it's contents.
MIN_SIZE
can be used to ensure that there is some free space to work with during runtime.
For read-only use, a value of 0
is recommended.
Run a command in the rootfs image.
Everything after the RUN
directive is passed to /bin/sh
in the rootfs.
Therefore, a working /bin/sh
needs to be present before the first RUN
command.
For example to update the package database in an Alpine image:
RUN apk update
RUN
can not be used before FROM
.
But a second FROM
may appear after RUN
.
Copy one or more files from the host to the rootfs image. Takes at least two arguments.
All but the last argument are files or directories on the host (relative paths are relative to the location of the .rootfs
file).
These arguments can be patterns as described in the section 'Pathname Expansion' in the bash documentation.
To match *
, ?
or [
literally, these characters need to be enclosed in []
, e.g. [*]
.
The last argument denotes a path in the root filesystem and is the target of the copy operation (relative paths are relative to /
in the rootfs).
The file ownership is set to root
in the rootfs.
Example:
COPY files/custom_issue /etc/issue
COPY
can not be used before FROM
.
But a second FROM
may appear after COPY
.
This script runs a MicroVM using Firecracker.
It handles populating the chroot environment with the necessary files, running jailer
(which in turn starts firecracker
) and cleaning up the chroot after Firecracker terminates.
The only mandatory argument to firestarter.py
is the path to a config file that describes the VM.
See [Config File Format](#Config File Format) below for further information.
firestarter.py
also accept some optional parameters.
Refer to firestarter.py --help
for a list.
firestarter.py
needs to be started as root
, since this is a requirement of jailer
.
The first time firestarter.py
receives a terminating signal (SIGINT
, SIGTERM
, SIGQUIT
, SIGHUP
), it sends Ctrl+Alt+Del
to the VM, so the guest can shut down gracefully.
This does, however, require the cooperation of the guest.
If firestarter.py
does not terminate in an acceptable time span, it is recommended to send the same signal again.
Graceful guest shutdown is attempted only once.
The second signal is passed to the Firecracker process, followed by SIGKILL
after a short delay.
firestarter.py
expects a JSON formatted config file that contains all information about the VM.
The file format is mostly identical to the file format accepted by Firecracker with the following extensions:
- The
boot-source
section is not mandatory. If it is not present, a sensible default (i.e. the latest kernel image as created bybuild-kernel.sh
) is assumed. - Paths to the kernel, initrd and filesystems simply refer to files on the host.
firestarter.py
handles rewriting these paths for use in the chroot. Relative paths are considered to be relative to the location of the config file (or a path specified as a parameter tofirestarter.py
). - Paths to the kernel, initrd and filesystems support globbing. If multiple files match a globbing expression the most recently modified or (if the
packaging
module is available) the one containing the highest version number is chosen. - The
boot-source
anddrives
sections support the keyglob_order
to override the default choice of algorithm. Valid values aremost_recent
andlatest_version
(if available). - The
network-interfaces
section has limited support for IP address configuration:host_dev_name
is not mandatory. If it is omitted buthost_bridge_name
is set, a tap device is created and connected to the bridge devicehost_bridge_name
. The bridge needs to be fully set up on the host; it is not configured byfirestarter.py
.- The parameter
ip_address
can be added to a network interfaces. It may specify an IP address in CIDR notation or one of the special keywordsdhcp
,bootp
,rarp
orany
.dhcp
,bootp
andrarp
use the respective protocol to obtain a network configuration,any
uses any of these protocols. This only works if the kernel was compiled with support for IP autoconfiguration (CONFIG_IP_PNP
, possiblyCONFIG_IP_PNP_DHCP
,CONFIG_IP_PNP_BOOTP
,CONFIG_IP_PNP_RARP
) and only works for one network interface. - If
ip_address
is set, the optional parametergateway
can be set to the address of the gateway (or router) for the interface. - If
ip_address
is set, the optional parameterdns
can be set to a list of up to two nameserver addresses. Note that the kernel provides the nameserver information in/proc/net/pnp
. It is up to the rootfs to set up/etc/resolv.conf
accordingly, for example by making it a symlink to/proc/net/pnp
.
Here is an example that showcases some of the extended options:
{
"boot-source": {
"kernel_image_path": "vmlinux-*",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off quiet i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd"
},
"drives": [
{
"drive_id": "rootfs",
"path_on_host": "rootfs_*.img",
"glob_order": "most_recent",
"is_root_device": true,
"is_read_only": true
}
],
"machine-config": {
"vcpu_count": 1,
"mem_size_mib": 128
},
"network-interfaces": [
{
"iface_id": "eth0",
"host_bridge_name": "fcbr0",
"ip_address": "192.168.0.2/24",
"gateway": "192.168.0.1",
"dns": ["192.168.0.1"]
}
]
}
MIT