Overview

Support Files

Session support files are available in the repository. If you already cloned the repository, update it and enter the session directory:

$ cd path/to/repository/clone

$ git pull --rebase

$ cd content/en/community/hackathons/sessions/complex-applications/


$ ls -F
images/  index.md  sol/  work/

If you haven’t cloned the repository yet, clone it and enter the session directory:

$ git clone https://github.com/unikraft/docs

$ cd docs/

$ cd content/en/community/hackathons/sessions/complex-applications/

$ ls -F
images/  index.md  sol/  work/

Useful scripts

Qemu Wrapper

As we saw during the other sessions, qemu-guest is a wrapper script over the qemu-system-x86_64 executable, to make the use of binary less painful. In the following session, it will be very handy to use it. To see the options for this wrapper you can use qemu-guest -h.

It is possible to run a lot of complex applications on Unikraft. In this session we analyze 3 of them:

  • SQLite
  • Redis
  • Nginx

01. SQLite

The goal of this tutorial is to get you to set up and run SQLite on top of Unikraft. Find the support files in the work/01-set-up-and-run-sqlite/ folder of the session directory.

SQLite is a C library that implements an encapsulated SQL database engine that does not require any setting or administration. It is one of the most popular in the world and it differs from other SQL database engines because it is simple to administer, use, maintain, and test. Thanks to these features, SQLite is a fast, secure, and (most crucial) simple application.

The SQLite application is built using an external library, lib-sqlite, that depends on another library that is ported for Unikraft: Musl (a standard C library). To successfully compile and run the SQLite application for the KVM platform on the x86-64 architecture, we follow the steps below.

Setup

First, we make sure we have the directory structure to store the local clones of Unikraft, library and application repositories. The structure should be:

workdir
|-- unikraft/
|-- libs/
`-- apps/

We clone the lib-sqlite repository in the libs/ folder. The libraries on which lib-sqlite depends (Musl) are also to be cloned in the libs/ folder.

We clone the app-sqlite repository in the apps/ folder. In this directory, we need to create two files:

  • Makefile: containing rules for building the application, as well as specifying the libraries that the application needs
  • Makefile.uk: used to define variables needed to compile the application or to add application-specific flags

In the Makefile, the order in which the libraries are mentioned in the LIBS variable is important to avoid the occurrence of compilation errors.

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)

For the moment, Makefile.uk should look like this:

$(eval $(call addlib,appsqlite))

Configure

We configure the application by running:

$ make menuconfig

We select the SQLite library from the configuration menu, Library Configuration section. For starters, we select the option to generate the main source file used to run the application.

To import or export databases or CSV/SQL files, the SQLite application needs to configure a filesystem. The filesystem we use is 9pfs. Hence, in the Library Configuration section, we select the 9pfs filesystem within the vfscore library options.

As we are going to use the qemu wrapper to launch the app, we’ll need to name the Default root device fs0 (Library Configuration -> vfscore -> Default root device). This is due to how the qemu-guest script automatically tags the FS devices attached to qemu.

9pfs options

Make sure that both options Virtio PCI device support and Virtio 9P device are selected. They can be found in Platform Configuration -> KVM guest -> Virtio.

virtio options

Build

We build the application by running:

$ make

Test

For testing we can use the following SQLite script, which inserts ten values into a table:

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));

Up next, create a folder in the application folder called sqlite_files/ and write the above script into a file. When you run the application, you can specify the path of the newly created folder to the qemu-guest script as following:

$ ./qemu-guest -k ./build/app-sqlite_qemu-x86_64 \
               -e ./sqlite_files \
               -m 500

The SQLite start command has several parameters:

  • k indicates the executable resulting from the build of the entire system together with the SQLite application
  • e indicates the path to the shared directory where the Unikraft filesystem will be mounted
  • m indicates the memory allocated to the application

To load the SQLite script, we use the following command .read <sqlite_script_name.sql>. In the end, we run select * from tab to see the contents of the table.

If everything runs as expected, then we’ll get the following output:

SeaBIOS (version 1.13.0-1ubuntu1.1)
Booting from ROM...
Powered by
o.   .o       _ _               __ _
Oo   Oo  ___ (_) | __ __  __ _ ' _) :_
oO   oO ' _ `| | |/ /  _)' _` | |_|  _)
oOo oOO| | | | |   (| | | (_) |  _) :_
 OoOoO ._, ._:_:_,\_._,  .__,_:_, \___)
                  Phoebe 0.10.0~9bf6e63
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
sqlite> .read script.sql
sqlite> select * from tab;
-1758847760864160102|2718837905630364326
-1339730570270182734|-413022835704168293
899003099627700560|-5446400296487656477
3986823405912376844|-3683968660549484071
5750141151993138490|-949527979363852620
2608659443316808689|3543024197312456352
-2195896775588749426|6838623081517951948
8293933456345343304|6460961935619776014
6827842764477913763|7025795551657688644
4026439721321663478|8364502757469924828
sqlite> .exit

02. SQLite New Filesystem

In the previous work item, we have chosen to use 9PFS as the filesystem. For this work item, we want to change the filesystem to InitRD and load the SQLlite script as we have done in the previous work item. Find the support files in the work/02-change-filesystem-sqlite/ folder of the session directory. Make sure to create the Makefile and Makefile.uk files in a similar manner as with the previous work item before proceeding in configuring the application.

First, we need to change the filesystem to InitRD. We can obtain that by using the command make menuconfig and from the vfscore: Configuration option, we select the default root filesystem as InitRD.

filesystems menu

The InitRD filesystem can load only cpio archives. In order to load our SQLite script into InitRD, we need to create a cpio out of it. This can be achieved in the following way:

Create a folder, move the SQLite script in it, and cd into it.

After that we run the following command:

$ find -type f | bsdcpio -o --format newc > ../archive.cpio

We’ll obtain a cpio archive called archive.cpio in the parent directory.

Next, we run the following qemu-guest command to run the instance:

$ ./qemu-guest -k build/app-sqlite_qemu-x86_64 -m 100 -i archive.cpio

If everything runs as expected, then we’ll get the following output:

SeaBIOS (version 1.13.0-1ubuntu1.1)
Booting from ROM...
Powered by
o.   .o       _ _               __ _
Oo   Oo  ___ (_) | __ __  __ _ ' _) :_
oO   oO ' _ `| | |/ /  _)' _` | |_|  _)
oOo oOO| | | | |   (| | | (_) |  _) :_
 OoOoO ._, ._:_:_,\_._,  .__,_:_, \___)
                  Phoebe 0.10.0~9bf6e63
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
sqlite> .read script.sql
sqlite> select * from tab;
-2854471077348014330|8890688652355553061
6848326607576863720|8057668357382476232
-4851485256049611772|1080284340194216118
3617801119133923790|-3742008368926465716
-8000990739986823138|603753214333179605
-1492560099439825568|-8062818652230049204
8818728981714743313|-1714591670076544373
-1304043959596685652|557566099797623154
-9196798118140052834|3433881783117867716
-4291436294037928857|6810153594571143752
sqlite> .exit

03. Redis

The goal of this tutorial is to get you to set up and run Redis on top of Unikraft. Find the support files in the work/03-set-up-and-run-redis/ folder of the session directory.

Redis is one of the most popular key-value databases, with a design that facilitates the fast writing and reading of data from memory, as well as the storage of data on disk in order to be able to reconstruct the state of data in memory in case of a system restart. Unlike other data storage systems, Redis supports different types of data structures such as lists, maps, strings, sets, bitmaps, streams.

The Redis application is built using an external library, lib-redis, that depends on other ported libraries for Unikraft (musl and lwip library), all of which you should be familiar with by now. To successfully compile and run the Redis application for the KVM platform and x86-64 architecture, we follow the steps below.

Setup

As above, we make sure we have the directory structure to store the local clones of Unikraft, library and application repositories. The structure should be:

workdir
|-- unikraft/
|-- libs/
`-- apps/

We clone the lib-redis repository in the libs/ folder. We also clone the library repositories which lib-redis depends on (musl and lwip) in the libs/ folder.

We clone the app-redis repository in the apps/ folder. In this directory, we need to create two files:

  • Makefile: it contains rules for building the application, as well as specifying the external libraries that the application needs
  • Makefile.uk: used to define variables needed to compile the application or to add application-specific flags

In the Makefile, the order in which the libraries are mentioned in the LIBS variable is important to avoid the occurrence of compilation errors.

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)

For the moment, Makefile.uk should look like this:

$(eval $(call addlib,appredis))

Configure

We configure the application by running:

$ make menuconfig

We select the Redis library from the configuration menu, in the Library Configuration section. For starters, we select the option to generate the main source file used to run the application.

redis selection menu

To connect to the Redis server, the network features should be configured. Hence, in the configuration menu in the Library Configuration section, within the lwip library the following options should be selected:

  • IPv4
  • UDP support
  • TCP support
  • ICMP support
  • DHCP client
  • Socket API

lwip selection menu

The Redis application needs a configuration file to start. Thus, a filesystem should be selected in the configuration menu. The filesystem we previously used was 9PFS.

As such, in the Library Configuration section of the configuration menu, the following selection chain should be made in the vfscore library: VFSCore Interface -> vfscore Configuration -> Automatically mount a root filesystem -> Default root filesystem -> 9PFS. Same as before, since we’ll be using the qemu-guest script, we’ll need to name the Default root device fs0 (Library Configuration -> vfscore -> Default root device).

Nevertheless, don’t forget to select the posix-event library: Library Configuration -> posix-event.

Build

We build the application by running:

$ make

Test

Following the steps above, the build of the entire system, together with the Redis application will be successful. We can create a redis_files directory in which we can place our configuration file for Redis, redis.conf. We used a script to run the application in which a bridge and a network interface (kraft0) are created. The network interface has an IP associated with it used by clients to connect to the Redis server. Also, the script takes care of starting the Redis server, but also of stopping it, deleting the settings created for the network.

sudo brctl addbr kraft0
sudo ifconfig kraft0 172.44.0.1
sudo ifconfig kraft0 up

sudo dnsmasq -d \
             -log-queries \
             --bind-dynamic \
             --interface=kraft0 \
             --listen-addr=172.44.0.1 \
             --dhcp-range=172.44.0.2,172.44.0.254,255.255.255.0,12h &> dnsmasq.logs &

./qemu-guest.sh -k ./build/app-redis_qemu-x86_64 \
                -a "/redis.conf" \
                -b kraft0 \
                -e ./redis_files \
                -m 100

The Redis server start command has several parameters:

  • k indicates the executable resulting from the build of the entire system together with the Redis application
  • e indicates the path to the shared directory where the Unikraft filesystem will be mounted
  • b indicates the network interface used for external communication
  • m indicates the memory allocated to the application
  • a allows the addition of parameters specific to running the application

The following image is presenting an overview of our setup:

lwip selection menu

Consequently, after running the script, the Redis server will start and dnsmasq will act as a DHCP server and, therefore it will dynamically assign to our Unikraft instance an IP address. The IP can be seen in the output of qemu as bellow:

Booting from ROM...
en1: Added
en1: Interface is up
Powered by
o.   .o       _ _               __ _
Oo   Oo  ___ (_) | __ __  __ _ ' _) :_
oO   oO ' _ `| | |/ /  _)' _` | |_|  _)
oOo oOO| | | | |   (| | | (_) |  _) :_
 OoOoO ._, ._:_:_,\_._,  .__,_:_, \___)
                  Phoebe 0.10.0~9bf6e63
1:C 27 Aug 2022 12:37:07.023 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 27 Aug 2022 12:37:07.026 # Redis version=5.0.6, bits=64, commit=c5ee3442, modified=1, pid=1, just started
1:C 27 Aug 2022 12:37:07.031 # Configuration loaded
1:M 27 Aug 2022 12:37:07.049 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 5.0.6 (c5ee3442/1) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 1
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

1:M 27 Aug 2022 12:37:07.090 # Server initialized
1:M 27 Aug 2022 12:37:07.092 * Ready to accept connections
en1: Set IPv4 address 172.44.0.242 mask 255.255.255.0 gw 172.44.0.1

Another way of inspecting the received IP is through the dnsmasq.logs file:

$ cat dnsmasq.logs
[...]
dnsmasq-dhcp: DHCPOFFER(kraft0) 172.44.0.242 52:54:00:20:37:c1
dnsmasq-dhcp: DHCPDISCOVER(kraft0) 52:54:00:20:37:c1
dnsmasq-dhcp: DHCPOFFER(kraft0) 172.44.0.242 52:54:00:20:37:c1
dnsmasq-dhcp: DHCPREQUEST(kraft0) 172.44.0.242 52:54:00:20:37:c1
dnsmasq-dhcp: DHCPACK(kraft0) 172.44.0.242 52:54:00:20:37:c1

Using the received IP, it will be possible to connect clients to it using redis-cli (the binary redis-cli is the folder for this work item):

$ ./redis-cli -h 172.44.0.242 -p 6379
172.44.0.242:6379> PING
PONG
172.44.0.242:6379>

Nevertheless, after completing this task, you will need to check that the dnsmasq process is not running anymore, as it messes up your other connections. You can also disable and delete the bridge interface by running the following commands:

$ sudo ip l set dev kraft0 down
$ sudo brctl delbr kraft0

04. Nginx

The aim of this work item is to set up and run Nginx, a popular open-source web server. Find the support files in the work/06-set-up-and-run-nginx/ folder of the session directory.

From the point of view of the library dependencies, the Nginx app has the same dependencies as the Redis app. Of course, instead of lib-redis, the Nginx app depends on an external library called lib-nginx. You can clone this library in the libs/ directory in a similar manner as with the previous work items. It’s your choice how you assign the IP to the VM.

In the support folder of this work item there is a subfolder called nginx with the following structure:

nginx_files
`-- nginx/
    |-- conf/
    |   |-- fastcgi.conf
    |   |-- fastcgi_params
    |   |-- koi-utf
    |   |-- koi-win
    |   |-- mime.types
    |   |-- nginx.conf
    |   |-- nginx.conf.default
    |   |-- scgi_params
    |   |-- uwsgi_params
    |   `-- win-utf
    |-- data/
    |   `-- images/
    |       `-- small-img100.png
    |-- html/
    |   |-- 50x.html
    |   `-- index.html
    `-- logs/
        |-- error.log
        `-- nginx.pid

The path to the nginx_files folder should be given as a parameter to the -e option of the qemu-guest. The html/ folder stores the files of the website you want to be run.

If everything works as expected, you should see the following web page in the browser.

nginx output