Binary Compatibility In Unikraft

Binary Compatibility In Unikraft

Let’s hack and run our favorite applications on top of Unikernels using the Unikraft toolkit. Unikernel can enhance the performance by increasing the speed and lowering the response time and at the same time providing high-level security. To know more about Unikernels, kindly visit my previous blogs: https://chococandy63.github.io/chococandy-blog/tags/unikernel/

In this blog, I will teach you about binary compatibility in Unikraft.

We can port applications to Unikraft(future blogs will show you how you can port applications to Unikraft) but that will take time and might be a little complex if you don’t like to play with low-level C stuff.

No worries, we have an alternate solution for this which is to take the pre-complied application binaries of existing applications/command line utilities and run them on top of Unikernel using Unikraft tooling.

I have done the same for the grep command line utility. You can see my work here: https://github.com/unikraft/dynamic-apps/pull/71

Note: These applications are Linux executables and they are not ported to Unikraft. They just run it on top of unikraft as Unikernels.

Let us go through the steps that I followed in order to run grep on Unikraft.

Commands to follow

which grep

Outputs: /usr/bin/grep

file /usr/bin/grep

Outputs: /usr/bin/grep: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=492b58120241302624ff65d4f8867f3e82713545, for GNU/Linux 3.2.0, stripped

Let’s take another example of a little more complex application like-

Suppose we want to run a Rust application on Unikraft, we check the file information using file command -> outputs as 64 bit ELF.

Internals

We run ldd app-name which shows the shared object dependencies. In order for app-name to run properly we need these dependencies. We can also see the file systems in a more structured way by using a tree command.

We found a file named ld-linux-x86-64.so.2 which is basically a dynamic linker/loader. This is a system loader app that Linux calls when loading stuff in memory. Every time you run any executable, Linux will run this program and load all the dependencies that the executable has. So, we also want to do the same when we are running an application on Unikraft.

We have run.sh script inside run-app-elfloader repository which is a wrapper on top of a QEMU, this script just makes it easier to run the application on top of QEMU without mentioning/configuring the QEMU flags(because those flags are being handled internally by run.sh script).

To run any application- ./run.sh -r ../dynamic-apps/lang/rust/ -k ./run-app-elfloader_qemu-x86_64_plain /filename

You probably know by now what run.sh script does, moving to the -r flag which is used to link the location of the app

Building Nginx Application Using Unikraft

Hacking with Unikraft 01

Project workflow

unikraft/lib : Internal core libraries but we don’t necessarily need all of them for our application so when we built our application then at that time we configure the unikraft core libraries to suit our needs. Similary, we add the external libraries which is not directly maintained by unikraft community. We link those libraries to our application while building it. These repository are present outside of unikraft core repo. For example: lib-musl, lib-lwip these are few C standard external libraries.

Build Nginx App

Step: Run the following commands in sequence-

git clone unikraft/app-nginx.git

cd app-nginx

cat Makefile

We will need to install these three libraries(dependencies) in order to built an nginx server. lib-lwip, lib-musl, lib-nginx

mkdir .unikraft

cd .unikraft

mkdir unikraft

mkdir libs

cd libs

Let’s clone those three dependencies-

git clone /lib-lwip

git clone /lib-musl

git clone /lib-nginx

cd ..

git clone unikraft-repo

cd ..

make menuconfig: Create configuration tree of unikraft and give us an interactive method of configuring our app.

Inside the library config-

You can see all the libraries that were inside the unikraft/lib github folder.

Our Task

We need to build our nginx application for x86 architecture for KVM platform.

  • Select the architecture → x_86

  • Go to library config → select libnginx.

Most options got selected but we also need to select the main function. Select the main function so that we run our application directly once the unikernel starts running.

Tip- Square brackets mean that those options can be changed, click on the spacebar key to select/unselect the option.

  • We have to use 9pfs so we need to mount the root file system.

  • Go to library config-> vfcore config Select automatically mount a root filesystem(/) -> default root filesystem -> select 9pfs

  • Go to Platform config-> select KVM guest

Save the new configuration

  • Run make or make -j8 (to compile faster by setting the number of cores you want to use)

It started compiling. It compiles in an order we set in config file -> first lwip-> musl -> nginx and then link everything together.

See the results -> Run ls build -> We can see the app image app-nginx_qemu-x86_64 .

Running Hello Worlds On Unikernel

Project Environment Setup

Installation process for kraftkit-

Run curl --proto '=https' --tlsv1.2 -sSf https://get.kraftkit.sh | sh

Breaking down the above command:

curl: curl is a command-line tool used for transferring data with URLs. In this case, it's being used to fetch content from a specified URL.

--proto '=https': This option specifies the protocol that curl should use for the request. It sets the protocol to HTTPS, which is a secure version of HTTP.

--tlsv1.2: This option specifies that curl should use TLS version 1.2 for secure communication. TLS (Transport Layer Security) is a cryptographic protocol used for securing data transmission over the internet.

-sSf: These are options that modify how curl behaves:
    -s or --silent: This option makes curl operate in silent mode, which means it won't show progress information or error messages.
    -S or --show-error: This option tells curl to show error messages if the request encounters an error.
    -f or --fail: This option makes curl exit with an error status if the requested URL returns an HTTP error response.

https://get.kraftkit.sh: This is the URL from which curl will fetch content. In this case, it's fetching a script hosted at 'https://get.kraftkit.sh'.

| sh: This part of the command is using a pipe (|) to pass the content fetched by curl to the sh command for execution. It essentially runs the downloaded script as a shell script.

image

To check the location of kraft if it is installed correctly or not:

image

Assuming you have docker installed and successfully running on your Linux machine.

Run the following command:

docker run -it --rm -v $(pwd):/workspace --entrypoint bash kraftkit.sh/base:latest

Breaking down the above command:

docker run: This is the command to run a Docker container.

-it: These are options that control the interaction with the container:
    -i or --interactive: This option allows you to interact with the container by providing an interactive shell.
    -t or --tty: This option allocates a pseudo-TTY (terminal) for the container, allowing you to see and interact with the shell.

--rm: This option specifies that the container should be automatically removed when it exits. This is useful for cleaning up containers after they are done running.

-v $(pwd):/workspace: This is the volume mounting option, which maps a directory on your host system to a directory inside the container. In this case:
    $(pwd) is a command substitution that gets the current working directory on your host system.
    /workspace is the directory inside the container where the current working directory on the host will be mounted.

--entrypoint bash: This option specifies the entry point for the container. It overrides the default entry point defined in the Docker image and sets it to the Bash shell (bash), allowing you to start an interactive Bash session inside the container.

kraftkit.sh/base:latest: This is the name and tag of the Docker image used to create the container. It indicates that you are using the kraftkit.sh/base image with the latest tag. This image likely contains some specific configuration or environment for a development or execution environment.

image

Hello World App

Create a file named Kraftkit that contains the following information:

libraries{musl(stable)}, targets{name, architecture for which you are creating this unikernel(x86_64), platform(qemu)}

You can edit the following targets and libraries according to your needs.

image

Enter the root directory of your project. Create main.c file and write a basic hello world program in C.

image

Then run nano Makefile.uk that creates a Makefile. Enter the following contents in the Makefile.uk.

image

Make sure to install all the dependencies required to run the unikernel, failing to install the dependencies will cause build errors like this-

image

Resolving the above error by installing musl-libc on my fedora system.

Error resolved!

Tip: You can run kraft clean to remove the existing builds and rebuild the unikernel again to resolve some errors.

Run update pkg and then compile the unikernel using the command kraft build:

image

Finally Run your hello world app using kraft run:

image

References

Unikraft-Day 2

Unikernel Research

Unikraft is a unikernel SDK, meaning it offers you the blocks (source code, configuration and build system, runtime support) to build and run unikernels. A unikernel is a single image file that can be loaded and run as a separate running instance, most often a virtual machine.

Ran applications using given config

Building applications using Unikraft.

How to config these applications ?