Welcome to bunny
, a tool that promises to make the building of unikernels as
easy an building containers.
- Introduction
- The
bunnyfile
- Trying it out
- Supported frameworks
- Execution modes
- Contributing
- License
- Contact
Unikernels is a promising technology that can achieve extremely fast boot times, a smaller memory footprint, increased performance, and more robust security than containers. Therefore, Unikernels could be very useful in cloud deployments, especially for microservices and function deployments. However, Unikernels are notorious for being difficult to build and even deploy them.
In order to provide a user-friendly build process for unikernels and let the
users build and use them as easy as containers we build bunny
. The main goal
of bunny
is to provide a unified and simplified process of building and
packaging unikernels. With bunny
, a user can simply build an application as a
unikernel and even package it as an OCI image with all
necessary metadata to run it with urunc.
bunny
is based on pun a tool that packages
already built unikernels in OCI images for urunc
. This functionality is and
will be supported from bunny
as well. However, bunny
is also able to build
unikernels from scratch.
In order to instruct bunny
how to build and package unikernels, we have
defined a yaml-based file, called bunnyfile
. You can think of bunnyfile
as
the equivalent of Dockerfile
for containers. The currently supported fields of
the bunnyfile
are the following:
#syntax=harbor.nbfc.io/nubificus/bunny:latest # [1] Set bunnyfile syntax for automatic recognition from buildkit.
version: v0.1 # [2] Bunnyfile version.
platforms: # [3] The target platform for building/packaging.
framework: unikraft # [3a] The unikernel framework.
version: v0.15.0 # [3b] The version of the unikernel framework.
monitor: qemu # [3c] The hypervisor/VMM or any other kind of monitor.
architecture: x86 # [3d] The target architecture.
rootfs: # [4] (Optional) Specifies the rootfs of the unikernel.
from: local # [4a] (Optional) The source or base of the rootfs.
path: initrd # [4b] (Required if from is not scratch) The path in the source, where the prebuilt rootfs file resides.
type: initrd # [4c] (optional) The type of rootfs (e.g. initrd, raw, block)
include: # [4d] (Optional) A list of local files to include in the rootfs
- src:dst
kernel: # [5] Specify a prebuilt kernel to use
from: local # [5a] Specify the source of a prebuilt kernel.
path: local # [5b] The path where the kernel image resides.
cmdline: hello # [6] The cmdline of the app.
The fields of bunnyfile
in more details:
Description | Required | Value type | Default value | |
---|---|---|---|---|
1 | instruct Buildkit to use bunny for parsing this file |
yes | buildkit directive | - |
2 | API version of bunnyfile format. Current version is v0.1 |
yes | string in major version format (vX) | - |
3 | Information about targeting platform | yes | - | - |
3a | The unikernel/libOS to target | yes | string | - |
3b | The unikernel/libOS version | no | string | latest |
3c | The VMM or any kind of monitor, where the unikernel will run on top | no | string | framework-dependent |
3d | The target architecture | no | string | bunny 's host arch |
4 | Instructions about unikernel's rootfs | no | - | - |
4a | The base image or an image/location that contains a rootfs | no | "scratch", "local", "OCI image" | "scratch" |
4b | The path relative to the from field where a rootfs file resides |
yes, if from == "local" |
file path | - |
4c | The type of the rootfs | no | "raw", "initrd" | platform-dependent |
4d | Files from the build context to include in the rootfs | no | list of : | - |
5 | Information about a prebuilt kernel | no | - | - |
5a | The location where the prebuilt kernel resides | no | "local", "OCI image" | - |
5b | The path relative to the from field where a kernel binary resides |
yes, if from is set |
"local", "OCI image" | - |
6 | The command line of the application | no | string | - |
The unikernel and libOS landscape is very diverse and each framework/technology
comes with its own support for storage. The users can easily get lost on the
various storage technologies that each framework supports. For that purpose, bunny
aims to provide a common interface to let the users easily define the contents
of the unikernel's rootfs. Hence, let's take a closer look on this part of
bunnyfile
.
In this field, we can instruct bunny
to use an existing rootfs file or create
one. It is similar to the Dockerfile's FROM
instruction, but in bunny
it is
also used to define the location of an existing rootfs file.
The from
field can have one of the following values:
- scratch:
bunny
will build the rootfs from scratch (starting with an empty directory). - local": we can use this value to use an existing rootfs file that resides in
the local build context of
bunny
. - OCI image: an OCI image to use as a base for the rootfs, or an OCI image that contains an existing rootfs file.
This field makes sense only when we define "local" or "OCI image" in the from
field. In the path
field users should define the path where an existing rootfs
file resides. Tsuch cases could be a rootfs file (e.g. initrd, virtio-block,
etc.) created locally or reusing one from another OCI image.
Some unikernel frameworks, or similar technologies, support a single type of
storage and hence this field is quite useless. If that is the case, bunny
will
print the respective error message. On the other hand, some frameworks support
more than one storage types. For instance, Linux can use an initramfs, a
virtio-block or even shared-fs as the rootfs. These are the cases were this
field is useful. Users can choose (if they wish) a specific type of rootfs and
bunny
will build it. Currently, bunny
can create the following types:
- initrd: A typical cpio file that guests can use as an initial rootfs.
- raw: In this case
bunny
does not build any specific kind of file, but instead copies the files, that the user specifies, directly in the OCI image's rootfs. This type is useful when we want to pass the entire OCI image's rootfs to the guest, either through share-fs or devmapper.
In this field users can define the files from the local build context that they
want to include in the rootfs. It is equivalent to the COPY
instruction in
Dockerfile. The field accepts a list of entries with the following format:
- <path_in the_local_build_context>:<path_inside_the_rootfs>
NOTE: Except of the
bunnyfile
,bunny
supports the Dockerfile-like file that pun and bima takes as input. However, the Dockerfile-like syntex is limited only to packaging existing unikernels. With thebunnyfile
,bunny
is able to provide much more functionalities and features.
The easiest and most effortless way to try out bunny
would be using it as a
buildkit's frontend. Therefore, assuming that we have an existing Unikraft
unikernel with its initrd already prebuilt, we can package everything as an OCI
image for urunc with the following
bunnyfile
:
#syntax=harbor.nbfc.io/nubificus/bunny:latest
version: v0.1
platforms:
framework: unikraft
version: 0.17.0
monitor: qemu
architecture: x86
rootfs:
from: local
path: initramfs-x86_64.cpio
kernel:
from: local
path: kernel
cmdline: /server
We can then package everything with the following command:
docker build -f bunnyfile -t harbor.nbfc.io/nubificus/urunc/httpc-unikraft-qemu:test .
The above bunnyfile
will perform the following steps:
- Copy the file
initramfs-x86_64.cpio
from the local build context to an empty OCI image at/boot/rootfs
. - Copy the file
kernel
from the local build context to the OCI image we used in the previous step at/boot/kernel
. - Set up
urunc
's annotations using all the information in the file (e.g. framework, version, cmdline, binary, initrd). - Produce the final OCI image.
For more examples, please take a look in the examples directory..
At the moment, bunny
is available on GNU/Linux for x86_64 and arm64 architectures.
The main goals of bunny
is to build and package unikernels as OCI images.
Packaging unikernels is agnostic of the frameworks and hence bunny
can be used
for any unikernel framework and similar technology. However, for
building unikernels, bunny
only provides experimental support which is not
merged in the main branch yet. We are working on it. However, bunny
can still be
used to build the rootfs
for some unikernels. The below table provides an
overview of the currently supported unikernels and frameworks:
Unikernel | Build | Rootfs |
---|---|---|
Rumprun | 🔨 | raw |
Unikraft | 🔨 | Initrd |
We plan to add support for more and more unikernel frameworks and similar technologies.
Feel free to contact us for a specific unikernel framework or similar
technologies that you would like to see in bunny
.
bunny
supports two modes of execution: either a) local execution, printing a
LLB, or b) as a frontend for Docker's buildkit. The easiest way is to use it as
a frontend.
In order to use bunny
as Buildkit's frontend, we just need to add a new
line in the top of the Dockerfile. In particular, every file that we want to
use for 'bunny' needs to start with the following line.
#syntax=harbor.nbfc.io/nubificus/bunny:latest
Then, we can just execute docker build as usual. Buildkit will fetch an image
containing bunny
and it will use it as a frontend. Therefore, anyone can use
bunny
directly without even building or installing its binary.
In order to use bunny
with buildctl, we have to build it locally, run it and then feed
its output to buildctl.
We can easily build bunny
by simply running make:
make
NOTE:
bunny
was created with Golang version 1.23.4
In the case of buildctl, bunny
does not produce any artifact by itself.
Instead, it outputs a LLB that we can then feed in buildctl.
Therefore, in order to use bunny
, we need to firstly install buildkit.
For more information regarding building and installing buildkit, please refer
to buildkit
instructions.
As long as buildkit is running in our system, we can use bunny
with the following command:
./bunny --LLB -f bunnyfile | sudo buildctl build ... --local context=<path_to_local_context> --output type=<type>,<type-specific-args>
bunny
takes as an argument the bunnyfile
a
yaml file with instructions to build and package unikernels. For more
information regarding bunnyfile please check the respective
section
Regarding the buildctl arguments:
--local context
specifies the directory of the local context. It is similar to the build context in the docker build command. Therefore, if we specify want to copy any file from the host, the path should be relative to the directory of this argument.--output type=<type>
specifies the output format. Buildkit supports various outputs. Just for convenience we mention thedocker
output, which produces an output that we can pass todocker load
in order to place our image in the local docker registry. We can also specify the name of the image, using thename=<name
in the<type-specific-option>
.
For instance:
./bunny --LLB -f bunnyfile | sudo buildctl build ... --local context=/home/ubuntu/unikernels/ --output type=docker,name=harbor.nbfc.io/nubificus/urunc/built-by-bunny:latest | sudo docker load
We will be very happy to receive any feedback and any kind of contributions for
bunny
. For more details please take a look in bunny
's contributing
document.
Feel free to contact any of the authors directly using their emails in the commit messages.