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.
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
Similarly to docker
, bunny
takes as an in put a file following the
bunnyfile
format. It is based on yaml and it includes all the information for
building an application as a Unikernel, along with instructions on how to
package the Unikernel, along with other files that the application requires.
#syntax=harbor.nbfc.io/nubificus/bunny:latest # [1] Set bunnyfile syntax for automatic recognition from docker.
version: v0.1 # [2] Bunnyfile version.
platforms: # [3] The target unikernel 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, where the unikernel will run on top.
architecture: x86 # [3d] The host architecture where the unikernel will run.
rootfs: # [4] (Optional) Specifies the rootfs of the unikernel.
from: local # [4a] (Optional) The source of the initrd.
path: initrd # [4b] (Required if from is not scratch) The path in the source, where a prebuilt rootfs file resides.
include: # [4c] (Optional) A list of files to include in the rootfs
- src:dst
kernel: # [5] Specify a prebuilt kernel to use
from: local # [5a] Specify the source of an existing prebuilt kernel.
path: local # [5b] Specify the path to the kernel image inside the KernelSource.
cmdline: hello # [6] The cmdline of the app
The fields of bunnyfile
in more details:
- A docker syntax directive. It is required to let the buildkit choose
bunny
as a frontend, instead of the default dockerfile frontend. - The version of the
bunnyfile
format. It is required to prevent incompatibilities with different versions. - The platform to target for building/packaging the unikernel. 3a. The unikernel framework to target. 3b. The version of unikernel framework to target. 3c. The hypervisor/VMM or any other runtime/monitor where the unikernel will run on top. Current supported values are: qemu, firecracker, hvt, Spt. 3d. The architecture of the host where the unikernel will run.
- (Optional) The rootfs of the unikernel. Currently,
bunny
is able to either package existing rootfs files, or to build the rootfs in the form of an initrd. 4a. The source where an existing rootfs file is stored. Current supported values are: i)scratch
, meaning that the rootfs should get built from scratch, and ii)local
, meaning that an existing rootfs file resides somewhere locally. The default value isscratch
. 4b. The path for the file in the aformentioned source. This field is required, iffrom
has a value other thanscratch
..In the case where thefrom
field has the valuelocal
, then thepath
should be relative to the build context. 4c. A list of files to include in the rootfs. This field takes effect only when thefrom
field has the valuescratch
. The files can be defined in the following format:- <path_in_the_build_context>:<path_inside_rootfs>
. The<path_inside_rootfs>
can be omitted and then the same path as the one in<path_in_the_build_context>
will be used. - Information regarding an existing prebuilt kernel to use. For the time
being,
bunny
supports only prebuilt unikernels andbunny
will package everything as an OCI image with the respective annotations for urunc. 5a. The source where an existing kernel is stored. Currently, only thelocal
value is supported, meaning that the file resides in somewhere locally. 5b. The path in the source where an existing kernel resides. In the case where thekernelSource
field has the valuelocal
, then thekernelPath
should be relative to the build context.
NOTE: Except of the
bunnyfile
,bunny
supports the Dockerfile-like file that pun and bima takes as input.
The easiest and more 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 package an existing Unikraft unikernel that we
obtained from http-c example in Unikraft's
catalog.
For more examples, please take a look in the examples directory..