Containers are moving the world. Each passing day more and more organizations are embracing containers as first-class citizens for distribution and deployment of software components. Containers represent the core of the cloud native paradigm. We are witnessing the birth of new container runtimes such as CRI-O - a lightweight alternative to Docker that allows spinning up containers on top of Kubernetes. A while back, I blogged about container internals and the Linux pillars that power some of the leading container engines. I have to admit the code snippets were rather unfunctional and ugly. Interfacing with the underlying Linux subsystem through
libc crate with
unsafe scattered throughout the code looked terrifying. That’s why I decided to wrap up a small project called rabbitc - a minimal container runtime meant for learning purposes (keep in mind that my knowledge on Rust is modest and I’m still in process of educating myself). Rust toolchain is required to build it. Once you have the binary, grab a minimal rootfs (such as Alpine’s rootfs) and spawn a new containerized process.
By default, you’ll be presented with a shell process, but you can run a different process with
--cmd option. For example, let’s build a Go-based HTTP server and run it inside container. Save the following code snippet to
Instruct the Go compiler to produce statically linked binary by disabling
CGO. We also copy the resulting binary to our rootfs path.
rabbitc doesn’t support port forwarding (but it’s easily doable with some
iptables magic), we have to provide container IP address to send the request to HTTP server.
We’re running a full isolated process with its own network stack, file system and process tree in 400~ lines of Rust code. That’s awesome!
In the next blog post I’ll focus on explaining the remaining kernel namespaces, how resources are controlled by
cgroup subsystem and other container-specific nuances. Stay tuned.