Building app-helloworld
with Make
The Unikraft build system is based on Make
and KConfig
.
Configuration and build commands of the kraft
companion tool are wrappers over make
.
For more control, you can configure, build and run your unikernel using the Makefile
and Config.uk
files instead of kraft.yaml
, and with the make
and qemu-system
tools instead of the kraft
companion tool.
Note that this is more complex than using kraft
, since you will have to deal with more options.
We want to build app-helloworld
using make
, with no dependency on kraft
.
In order to do that, we need to follow the same steps we take when using kraft
:
- Set up the working environment
- Configure the application
- Build the application
- Run the application
- Clean the build files
Set Up the Working Environment
To manually set up the build environment for the helloworld
app, we first have to create a specific directory structure:
my-unikernel/
|-- apps/
| `-- app-helloworld <- helloworld application
|-- libs/ <- additional libraries go here
`-- unikraft/ <- Unikraft core
We can do this by executing the following sequence of commands:
$ mkdir my-unikernel
$ cd my-unikernel
$ git clone https://github.com/unikraft/unikraft
$ mkdir libs
$ mkdir apps
$ cd apps
$ git clone https://github.com/unikraft/app-helloworld
$ cd app-helloworld
All following configuration and build operations are done from within the app-helloworld/
application directory.
Let’s take a look in the directory:
$ tree
.
|-- CODING_STYLE.md
|-- Config.uk
|-- CONTRIBUTING.md
|-- COPYING.md
|-- kraft.yaml
|-- main.c
|-- MAINTAINERS.md
|-- Makefile
|-- Makefile.uk
|-- monkey.h
`-- README.md
The .md
files are documentation files.
The main.c
and monkey.h
files are source code files.
Makefile
, Makefile.uk
, Config.uk
and kraft.yaml
are files required for the build.
See more about kraft.yaml
in the usage page.
See more about Makefile
, Makefile.uk
and Config.uk
in the porting page.
These last three files are relevant to a Make
-based build.
Configure the Application
With the Makefile
present and the directory structure correctly set up, we are able to configure the unikernel as the next step:
$ make menuconfig
This command will prompt a text-based user interface that will allow us to select the target architecture (e.g., aarch64
or x86-64
) and platform (e.g., KVM
or Xen
).
Use the up
and down
arrow keys to navigate, Enter
key to enter menus and use menu buttons, Space
key to make selections, ?
key for help.
By default, the build system selects x86-64
as the target architecture, so we only have to choose a target platform.
We will decide for KVM
by navigating to the Platform Configuration | KVM guest
option and selecting it, as shown in the picture below:

Afterward, we save and exit the configuration menu by using the button options in the lower part of the text user interface screen.
Build the Application
We can then build the application:
$ make
make[1]: Entering directory '[...]/my-unikernel/unikraft'
MKDIR lxdialog
MAKE kconfig
CP config
[...]
LD app-helloworld_kvm-x86_64.ld.o
OBJCOPY app-helloworld_kvm-x86_64.o
LD app-helloworld_kvm-x86_64.dbg
SCSTRIP app-helloworld_kvm-x86_64
GZ app-helloworld_kvm-x86_64.gz
LN app-helloworld_kvm-x86_64.dbg.gdb.py
GEN uk-gdb.py
make[1]: Leaving directory '[...]/my-unikernel/unikraft'
As we can see, the build resulted in an application image named app-helloworld_kvm-x86_64
that we can run.
The app-helloworld_kvm-x86_64
image is stripped;
if we want to use debugging symbols, we need to use the app-helloworld_kvm-x86_64.dbg
image.
In order to speed up the build process, we can add the -j X
, flag with X
typically assigned to the number of CPUs on the build machine (e.g. make -j 8
).
Run the Application
When the build completes, we will have an Unikraft-based kernel image (build/app-helloworld_kvm-x86_64
) that we can start with QEMU/KVM
using the -kernel
option:
$ sudo qemu-system-x86_64 -enable-kvm -cpu host -nographic -kernel build/app-helloworld_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
Alternatively, we can use the qemu-guest
script provided in the unikraft
core repository:
$ ../../unikraft/support/scripts/qemu-guest -k build/app-helloworld_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
If we are working on a machine without KVM
/ full virtualization support, the -enable-kvm
option (and the -cpu host
option) must be left out.
If using the qemu-guest
script, we will use the -W
flag to disable KVM
/ full virtualization support.
This will lower the performance, since everything will be emulated.
$ qemu-system-x86_64 -nographic -kernel build/app-helloworld_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
$ ../../unikraft/support/scripts/qemu-guest -W -k build/app-helloworld_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
See here for further information on running the unikernel with KVM
.
Clean the Build Files
To remove the build files of the application, we can run:
$ make clean
make[1]: Entering directory '[...]/my-unikernel/unikraft'
CLEAN libkvmplat
CLEAN libkvmpci
CLEAN libkvmvirtio
[...]
This will keep the fetched files and delete everything else that was generated during the build step. If we want to also remove the fetched files we can use:
$ make properclean
make[1]: Entering directory '[...]/my-unikernel/unikraft'
RM build/
make[1]: Leaving directory '[...]/my-unikernel/unikraft'
We can also remove the generated configuration files by using:
$ make distclean
make[1]: Entering directory '[...]/my-unikernel/unikraft'
RM build/
RM config
make[1]: Leaving directory '[...]/my-unikernel/unikraft'
Extending the Build
The build above was the simplest one we can do with Unikraft - configuring, building and running app-helloworld
.
Often, we want to extend the build:
- Add external libraries (e.g.
lwip
,musl
). - Add new source files to the build.
- Use different configuration options.
Add External Libraries to the Build
Notice that the app-helloworld/
directory already contains a Makefile
that looks like this:
UK_ROOT ?= $(PWD)/../../unikraft
UK_LIBS ?= $(PWD)/../../libs
LIBS :=
all:
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
$(MAKECMDGOALS):
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS) $(MAKECMDGOALS)
In the first two lines, we provide the paths to the Unikraft core repository and the directory where we can put additional libraries.
Since the helloworld
application uses nolibc
as the C library, which comes with the Unikraft core, we do not have any external dependencies.
We thus do not have to download or reference any libraries in the Makefile
and can leave the LIBS
variable empty.
When the application becomes more complex, it is likely that not all functionality is included in the Unikraft core repository.
As you might have noticed already from the code organization in the core repository (lib/*
), Unikraft puts an emphasis on encapsulating functionality in libraries that can be easily added, removed, or replaced to tailor the unikernel to the application’s needs.
To add a library, clone it into the libs/
directory created earlier.
In this example, we add the lightweight IP
(lwip
) network stack lib-lwip
:
$ cd ../../libs/
$ git clone https://github.com/unikraft/lib-lwip
Next, we reference the library in the Makefile
of app-helloworld
, making it available for selection in the configuration:
UK_LIBS ?= $(PWD)/../../libs
LIBS := $(UK_LIBS)/lib-lwip
We can now re-run
$ make menuconfig
and select lwip
in the Library Configuration
sub-menu.
Don’t forget to also select the KVM
platform:
in the Platform Configuration
submenu select KVM guest
.
We can then rebuild the unikernel:
$ make clean
$ make prepare
$ make
The prepare
step gives the build system the chance to pull the actual source code for lwip
and apply necessary patches to make it work with Unikraft.
This has to be done only once after adding a new library.
Add New Source Files to the Build
To add new source files to the build, we add them to the Makefile.uk
file.
We can choose to add source files only if certain configuration options are set.
$(eval $(call addlib,apphelloworld))
APPHELLOWORLD_SRCS-y += $(APPHELLOWORLD_BASE)/main.c
APPHELLOWORLD_SRCS-$(CONFIG_APPHELLOWORLD_LOGGER) += $(APPHELLOWORLD_BASE)/logger.c
This will add the logger.c
source file to the build system only if the CONFIG_APPHELLOWORLD_LOGGER
option is enabled in the configuration step.
We will discuss how to add the new configuration option below.
Use Different Configuration Options
We can edit the Config.uk
file to add additional dependencies and configuration options.
### Invisible option for dependencies
config APPHELLOWORLD_DEPENDENCIES
bool
default y
select PLAT_KVM
select LIBLWIP
config APPHELLOWORLD_LOGGER
bool "Add the logger to the build system"
default n
This will automatically select the KVM
platform and the lwip
external library and create a new configuration option that can be set in the Application Options
screen in the menuconfig
.

For more detailed information about using and configuring the build system, refer to the porting page.
Example: Using Make with app-helloworld-cpp
Following the steps above, we will set up, configure, build and run the app-helloworld-cpp
application.
To create the setup, we will start with the following directory structure:
my-unikernel/
|-- apps/
| `-- app-helloworld-cpp/ <- helloworld-cpp application
|-- libs/ <- additional libraries go here
`-- unikraft/ <- Unikraft core
We can do this by executing the following sequence of commands:
$ mkdir my-unikernel
$ cd my-unikernel
$ git clone https://github.com/unikraft/unikraft
$ mkdir libs
$ cd libs
$ git clone https://github.com/unikraft/lib-musl musl
$ git clone https://github.com/unikraft/lib-libunwind libunwind
$ git clone https://github.com/unikraft/lib-libcxx libcxx
$ git clone https://github.com/unikraft/lib-libcxxabi libcxxabi
$ git clone https://github.com/unikraft/lib-compiler-rt compiler-rt
$ cd ..
$ mkdir apps
$ cd apps
$ git clone https://github.com/unikraft/app-helloworld-cpp
$ cd app-helloworld-cpp
We can see that there is no Makefile
, so we create one and add the external dependencies:
UK_ROOT ?= $(PWD)/../../unikraft
UK_LIBS ?= $(PWD)/../../libs
LIBS := $(UK_LIBS)/musl:$(UK_LIBS)/libunwind:$(UK_LIBS)/libcxx:$(UK_LIBS)/libcxxabi:$(UK_LIBS)/compiler-rt
all:
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
$(MAKECMDGOALS):
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS) $(MAKECMDGOALS)
There is already a Makefile.uk
file present in the repository, containing the rule that adds the application to the build system and the source files:
$(eval $(call addlib,apphelloworldcpp))
APPHELLOWORLDCPP_SRCS-y += $(APPHELLOWORLDCPP_BASE)/helloworld.cpp
After that, we configure the application.
To do that, we can create a Config.uk
file that contains the application dependencies:
config APPHELLOWORLD_CPP_DEPENDENCIES
bool
default y
select PLAT_KVM
select LIBMUSL
select LIBCXX
select LIBCOMPILER_RT
Then we can run make menuconfig
, and just save the configuration.
Nothing else is needed, because required options are loaded from the Config.uk
file.
In case there is no Config.uk
file, we can still create the configuration, but manually, by running make menuconfig
and selecting the required options.
After we configured the application, we can build it:
$ make
make[1]: Entering directory '[...]/my-unikernel/unikraft'
MKDIR lxdialog
MAKE kconfig
[...]
GZ app-helloworld-cpp_kvm-x86_64.gz
LN app-helloworld-cpp_kvm-x86_64.dbg.gdb.py
GEN uk-gdb.py
make[1]: Leaving directory '[...]/my-unikernel/unikraft'
To run the application, we use the qemu-guest
script:
$ ../../unikraft/support/scripts/qemu-guest -k build/app-helloworld-cpp_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
or the qemu-system-x86_64
command:
$ sudo qemu-system-x86_64 -enable-kvm -cpu host -nographic -kernel build/app-helloworld-cpp_kvm-x86_64
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8A0+07ECC8A0 CA00
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Phoebe 0.10.0~ee2a37cf
Hello world!
Building and Running Complex Applications with Make
Let’s say we decide to use already ported applications.
There are many to choose from, so we will go for app-sqlite
, app-redis
or app-nginx
.
This tutorial is here to guide us through the first steps towards successfully launching our (more complex) unikernel.
For every application, we’ll present 2 scenarios: building for x86_64
and AArch64
.
app-sqlite
for x86_64
The first step is to clone our dependencies:
We should be left with a file hierarchy that looks like this:
workdir/
|---apps/
| |---app-sqlite/
|---libs/
| |---lib-musl/
| |---lib-sqlite/
|---unikraft/
We must now create a Makefile
in the app-sqlite
directory:
UK_ROOT ?= $(PWD)/../../unikraft
UK_LIBS ?= $(PWD)/../../libs
LIBS := $(UK_LIBS)/lib-musl:$(UK_LIBS)/lib-sqlite
all:
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
$(MAKECMDGOALS):
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS) $(MAKECMDGOALS)
We should also create a Makefile.uk
in the app-sqlite
directory:
$(eval $(call addlib,appsqlite))
Now, onto configuring our app:
$ make menuconfig
-
Choose the
x86
architecture fromArchitecture Selection
. -
Choose the
KVM
platform fromPlatform Configuration
. -
Choose all
Virtio
options fromPlatform configuration->KVM guest->Virtio
. -
Choose the
SQLite
,musl
andvfscore
libraries from theLibrary Configuration
screen. -
Under the
vfscore
library, choose theDefault root filesystem
to be9pfs
.
We’ll build our app:
$ make -j $(ncpus)
We should now create a subdirectory named fs0/
in the app-sqlite/
directory.
Inside it we’ll create this script.sql
script:
CREATE TABLE tab (d1 int, d2 text);
INSERT INTO tab VALUES (random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text)),
(random(), cast(random() as text));
The last step is running our app:
$ sudo qemu-system-x86_64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-kernel "build/app-sqlite_kvm-x86_64" \
-enable-kvm \
-nographic
And testing it:
Booting from ROM..Powered by
o. .o _ _ __ _
Oo Oo ___ (_) | __ __ __ _ ' _) :_
oO oO ' _ `| | |/ / _)' _` | |_| _)
oOo oOO| | | | | (| | | (_) | _) :_
OoOoO ._, ._:_:_,\_._, .__,_:_, \___)
Epimetheus 0.12.0~4c7352c0-custom
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .read script.sql
sqlite> select * from tab;
-7215681294144457603|-7881854117727259003
2117848104624103428|-1622087965530640476
-3409481001054928427|-6710007935272102091
-8864771657867452020|4690809563575391968
799097535707348776|-9015585007893225196
-6011599994684870502|5715089397278453373
-861992803967016915|1583603794332302036
-5556545375765846451|-8514616268392191775
6518576960059048853|6826969221610647599
-2505304097900087778|-1087908404908377458
sqlite> .exit
qemu-guest
.app-sqlite
for AArch64
The process of building app-sqlite
for AArch64
is quite similar to the one we’ve layed out for the x86_64
architecture.
The only thing that needs changes is the architecture, which should be switched to Armv8
in the Architecture Selection
menu, via make menuconfig
.
$ make properclean
For AArch64
, the command with which we launch the app is:
$ sudo qemu-system-aarch64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-kernel "build/app-sqlite_kvm-arm64" \
-machine virt \
-cpu cortex-a57 \
-nographic
app-redis
for x86_64
The first step is to clone our dependencies:
We should be left with a file hierarchy that looks like this:
workdir/
|---apps/
| |---app-redis/
|---libs/
| |---lib-lwip/
| |---lib-musl/
| |---lib-redis/
|---unikraft/
We must now create a Makefile
in the app-redis
directory:
UK_ROOT ?= $(PWD)/../../unikraft
UK_LIBS ?= $(PWD)/../../libs
LIBS := $(UK_LIBS)/lib-musl:$(UK_LIBS)/lib-lwip:$(UK_LIBS)/lib-redis
all:
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
$(MAKECMDGOALS):
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS) $(MAKECMDGOALS)
We should also create a Makefile.uk
in the app-redis
directory:
$(eval $(call addlib,appredis))
We’ll now create a subdirectory named fs0/
in the app-redis/
directory, download this file, a redis
configuration file, and move it there.
It will help us design a redis
server close to our liking.
Configuring our app should go smoothly:
$ make menuconfig
-
Choose the
x86
architecture fromArchitecture Selection
. -
Choose the
KVM
platform fromPlatform Configuration
. -
Choose the
Redis
,lwip
andmusl
libraries fromLibrary Configuration
. -
In the
Redis
library choose theProvide main function
option. -
In the
lwip
library make sure to haveIPv4
,UDP support
,TCP support
,ICMP support
,DHCP support
,Socket API
enabled. -
Under the
vfscore
library, choose theDefault root filesystem
to be9pfs
.
We’ll build our app:
$ make -j $(ncpus)
Because we’ll use networking, we have to set up a bridge:
$ sudo brctl addbr virbr0
$ sudo ip a a 172.44.0.1/24 dev virbr0
$ sudo ip l set dev virbr0 up
We’ll check our setup:
$ ip a s virbr0
6: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 8a:08:a1:69:85:31 brd ff:ff:ff:ff:ff:ff
inet 172.44.0.1/24 scope global virbr0
valid_lft forever preferred_lft forever
inet6 fe80::8808:a1ff:fe69:8531/64 scope link
valid_lft forever preferred_lft forever
We are ready to run our app:
$ sudo qemu-system-x86_64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-netdev bridge,id=en0,br=virbr0 \
-device virtio-net-pci,netdev=en0 \
-kernel "build/app-redis_kvm-x86_64" \
-append "netdev.ipv4_addr=172.44.0.2 netdev.ipv4_gw_addr=172.44.0.1 netdev.ipv4_subnet_mask=255.255.255.0 -- /redis.conf" \
-cpu host \
-enable-kvm \
-nographic
We will test our app by using a benchmark. Download this executable and run it using this command:
$ ./redis-cli -h 172.44.0.2 -p 6379
172.44.0.2:6379> PING
PONG
172.44.0.2:6379> exit
Cleaning up our work is always good practice, so let’s delete the bridge we’ve created earlier:
$ sudo ip l set dev virbr0 down
$ sudo brctl delbr virbr0
app-redis
for AArch64
Building Redis
for the AArch64
architecture looks very similar to how we did it for x86_64
.
The only thing that needs changing is the architecture, which should be switched to Armv8
in the Architecture Selection
menu, via make menuconfig
.
$ make properclean
For AArch64
, the command used to launch the app is:
$ sudo qemu-system-aarch64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-netdev bridge,id=en0,br=virbr0 \
-device virtio-net-pci,netdev=en0 \
-kernel "build/app-redis_kvm-arm64" \
-append "netdev.ipv4_addr=172.44.0.2 netdev.ipv4_gw_addr=172.44.0.1 netdev.ipv4_subnet_mask=255.255.255.0 -- /redis.conf" \
-machine virt \
-cpu cortex-a57 \
-nographic
app-nginx
for x86_64
The first step is to clone our dependencies:
We should be left with a file hierarchy that looks like this:
workdir/
|---apps/
| |---app-nginx/
|---libs/
| |---lib-lwip/
| |---lib-musl/
| |---lib-nginx/
|---unikraft/
We must create a Makefile
in the app-nginx
directory:
UK_ROOT ?= $(PWD)/../../unikraft
UK_LIBS ?= $(PWD)/../../libs
LIBS := $(UK_LIBS)/lib-musl:$(UK_LIBS)/lib-lwip:$(UK_LIBS)/lib-nginx
all:
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
$(MAKECMDGOALS):
@$(MAKE) -C $(UK_ROOT) A=$(PWD) L=$(LIBS) $(MAKECMDGOALS)
We should also create a Makefile.uk
in the app-nginx
directory:
$(eval $(call addlib,appnginx))
Onto configuring our app:
$ make menuconfig
-
Choose the
x86
architecture fromArchitecture Selection
. -
Choose the
KVM
platform fromPlatform Configuration
. -
Choose the
uksignal
,libnginx
,lwip
,musl
libraries fromLibrary Configuration
. -
In the
libnginx
library, choose theProvide main function
option. -
In the
lwip
library, make sure to haveIPv4
,UDP support
,TCP support
,ICMP support
,DHCP support
,Socket API
enabled. -
Under the
vfscore
library, choose theDefault root filesystem
to be9pfs
.
We’ll build our app:
$ make -j $(ncpus)
Because we’ll use networking, we have to set up a bridge:
$ sudo brctl addbr virbr0
$ sudo ip a a 172.44.0.1/24 dev virbr0
$ sudo ip l set dev virbr0 up
We’ll check our setup:
$ ip a s virbr0
6: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 8a:08:a1:69:85:31 brd ff:ff:ff:ff:ff:ff
inet 172.44.0.1/24 scope global virbr0
valid_lft forever preferred_lft forever
inet6 fe80::8808:a1ff:fe69:8531/64 scope link
valid_lft forever preferred_lft forever
Running our app is done using this command:
$ sudo qemu-system-x86_64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-netdev bridge,id=en0,br=virbr0 \
-device virtio-net-pci,netdev=en0 \
-kernel "build/app-nginx_kvm-x86_64" \
-append "netdev.ipv4_addr=172.44.0.2 netdev.ipv4_gw_addr=172.44.0.1 netdev.ipv4_subnet_mask=255.255.255.0 --" \
-cpu host \
-enable-kvm \
-nographic
If you’re met with this error:
failed to parse default acl file `/etc/qemu/bridge.conf'
qemu-system-x86_64: bridge helper failed
make sure to add this to your /etc/qemu/bridge.conf
file (if it doesn’t exist, create it):
allow all
For testing our app, we’ll open a new terminal, or use tmux
, and give this command to retrive data from the server:
$ wget 172.44.0.2
--2021-08-18 16:47:38-- http://172.44.0.2/
--2022-07-19 21:17:17-- http://172.44.0.2/
Connecting to 172.44.0.2:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 180 [text/html]
Saving to: ‘index.html’
2022-07-19 21:17:17 (14,7 MB/s) - ‘index.html’ saved [180/180]
$ cat index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
<p>Powered by <a href="http://unikraft.org">Unikraft</a>.</p>
</body>
</html>
Don’t forget to clean up our work: let’s delete the bridge we’ve created earlier:
$ sudo ip l set dev virbr0 down
$ sudo brctl delbr virbr0
app-nginx
for AArch64
Just like we’ve stated for SQLite
and Redis
, for AArch64
the build steps follow the tutorial for x86_64
.
The only thing that needs changes is the architecture, which should be switched to Armv8
in the Architecture Selection
menu, via make menuconfig
.
$ make properclean
Let’s launch the unikernel:
$ sudo qemu-system-aarch64 -fsdev local,id=myid,path=$(pwd)/fs0,security_model=none \
-device virtio-9p-pci,fsdev=myid,mount_tag=rootfs,disable-modern=on,disable-legacy=off \
-netdev bridge,id=en0,br=virbr0 \
-device virtio-net-pci,netdev=en0 \
-kernel "build/app-nginx_kvm-arm64" \
-append "netdev.ipv4_addr=172.44.0.2 netdev.ipv4_gw_addr=172.44.0.1 netdev.ipv4_subnet_mask=255.255.255.0 --" \
-machine virt \
-cpu cortex-a57 \
-nographic
With this last command, our tutorial ends, it was a long ride but the results should be rewarding enough.