Unikernel Vs Container Vs Operating System: Side-By-Side Comparison

The process of deploying software to production evolves constantly. Just a few decades ago, everyone used virtual machines to host and manage the infrastructure. Lately, the industry shifted towards using containers with systems such as Docker and Kubernetes. The next logical step in this progression is Unikernels, which combines the best of virtualization and containerization.

Why is deployment so hard?

The reason for the existence of all these tools is the problems that arise while deploying software to production. Some of the common issues that need to be addressed are the following:

  1. Environment consistency. It is much easier to write a piece of code that will run on a single platform. Thus, you need to minimize the platform differences across deployments.
  2. Scalability. You should be able to rapidly scale your infrastructure to meet demand spikes and minimize costs.
  3. Security. This is pretty much self-explanatory. You want your software to be prone to hackers at all levels.
  4. Monitoring. You want to effectively monitor the load on your infrastructure and extract all kinds of data to optimize your operations.
  5. Be developer-friendly. Software Developers want to do as little work as possible to publish their last build to customers. By keeping your developers happy, you will avoid many problems later.

This list is not exhaustive, but these are some common problems that had no easy solution until recently.

Deployment using virtual machines

Before containers came around, the virtualization stack was the to-go tool for large-scale application deployment. To understand the difference, you first have to understand what an OS is and what Kernel is.

Operating System is system software that manages hardware and controls the running software. Most importantly, it gives (and takes away) hardware access to processes.

A kernel is the main component of an OS. The rest of the OS is there to load the kernel. It resides constantly in memory and has complete control of both hardware and software.

Now, virtualization is a way to run multiple operating systems on shared hardware. One OS, called host, runs a hypervisor, which lets guest operating systems safely access the hardware. Sometimes there is no host OS. A Type-1 hypervisor runs directly on hardware and loads virtual machines.

A typical deployment stack consisted of a number of virtual machines running on multiple servers. You can see a simplified diagram of such a stack above. Sometimes it is a per-app VM, per-client VM, or per-concept VM (one for database, one for cache, etc.). You can notice how much resources goes to waste: every VM has to run its own instance of an operating system (Linux, most often), resulting in it a duplication of responsibility. You can also imagine how hard it is to manage such an infrastructure: you can effectively end up with 100+ servers (for example, a SaaS platform) which are all independent virtual machines.

The deployment process for a VM-based approach can go one of two ways. The build system can produce a complete image of a VM with software built-in and the VM just reboots once the update arrives. Or, the build system produces only the software bundle, which is uploaded to servers using a collection of scripts. The downside of both these approaches is complex setup and, eventually, inconsistencies between VMs. A real nightmare starts when you need to update the OS version. You would have to do it twice: firstly, the host OS, and then the guest OS.

This approach has its upsides, however. This way you get complete control of the environment for every aspect of the system and can configure it to suit your needs perfectly. It also simplifies debugging, as you can directly connect to a VM and use it as a regular workstation.

Deployment using containers

With the emergence of container runtimes such as Docker and container orchestration systems such as Kubernetes, a new era in software infrastructure manifested itself. You may be familiar with containers, and some of you even used them, so now I will explain how they work.

Containers try to achieve the same concept as virtual machines but eliminating duplication of effort between machines. Do you remember the concept of kernel from the previous section? Well, instead of loading an entire operating system for an app, Docker lets containers use the kernel of the host OS while allowing them to sideload app-specific libraries and programs. Since Linux kernel is so common and does not really change across distributions, complete platform independence is achieved. By tweaking the container and its image you can fine-tune the specific libraries and configuration your app will use, resulting in performance gains without the overhead of running an entire OS. A typical container stack can look like this:

CliveLess overhead from running an entire OS means you can run more containers (more apps) on the same hardware. It is important to note that Docker only works with Linux kernels, due to their robust namespace support. Namespaces standardize the available resources and can control what process has access to what resources. Thus, every container gets exactly the amount of CPU time, memory, storage, and networking as it needs. When you run Docker on macOS/Windows, it spins up a Linux VM under the hood, that is why it is so much slower on these platforms.

Of course, the container-based approach has its downsides. The software has to be adapted for usage in containers (containerized), and this can get tricky, especially with legacy codebases. Also, since Docker uses the Linux kernel, you cannot use it to run legacy software designed for Windows Server (before .NET Core was introduced). Containers have many more configurations for resource allocation and interop capabilities, so it is very easy to screw up setting up a moderate infrastructure. Lastly, since Docker containers share the same kernel, you cannot use a custom one (for example, a Realtime Linux kernel for transaction-based software).

One of the advantages of containers is the ability to easily run them on your development machine. Developers like this very much, so let’s keep that. The deployment process itself is also much easier, you just upload pre-built containers to a container repository and your production servers pull the updated version.

Deployment using unikernels

While containers are quickly becoming industry standard, unikernels still have a lot to go through. Unikernels try to push the concepts of containers even further, eliminating the need for an OS altogether. Unikernels achieve this by using library operating systems. Library operating systems provide similarly (but limited to single-user, single address space) features to the regular OS, but in the form of libraries that your app uses. So, instead of maintaining a resident kernel in memory, everything is managed through pre-built binary libraries. Unikernels do not handle resource allocation, though, so they still require a hypervisor:

You can get your head around it by thinking of hypervisor as a regular OS, and unikernels as processes running on it. The difference is that all app-specific system calls are pushed as close to the app as possible, while hypervisor handles only direct hardware interop.

As you can imagine, unikernels have even less overhead than containers and should be more performant. Also, by eliminating the use of a multi-user, multiple address space kernel, the security is improved drastically. Unikernels are a truly amazing technology, but they are far from being production-ready yet. Some of the issues that need to be addressed first are these:

  1. Debugging. Since a unikernel has no OS running whatsoever, you cannot directly connect to its shell and investigate. I am sure there will be an even easier approach to it, but not yet.
  2. Streamlining builds. Producing unikernel images is complicated and requires deep knowledge on the subject. Until the process is simplified and standardized, adoption will go very slow.
  3. Framework support. Most of the current application frameworks will have to adapt and produce documentation on usage in Unikernels.

Some notable Unikernel projects include ClickOS, runtime.js, and Clive.

Closing notes

Thank you for reading, I hope I got you interested in the subject of Unikernels. Stay tuned for more posts on this revolutionary technology!


Get new content delivered to your mailbox:

leave a comment