One of the most compelling feature of LinuxKit is “Everything replaceable and customisable”. You can now build up your own customised Linux Operating System so as to run it across various platforms like Bare Metal Hardware, Virtual Machines, Cloud Instances etc. As of today, LinuxKit supports various platform like OSX/Hypervisor, Google Cloud Platform, Packets.net, QEMU/KVM & VMware ESXi vCenter Server. Effort has already been started for enabling this solution on AWS, Azure, Bluemix etc. For Raspberry Pi aspirants, there is already a feature request raised for enabling LinuxKit on ARM64 platform.
LinuxKit is built with containers to run the containers. It involves easy tooling and that too with easy iteration.Under LinuxKit, we have runc and containerd which are used to run containers.Containers under LinuxKit are managed by runc and containerd and not by Docker.Everything apart from the init process and runc/containerd run in a container. Under this blog post, I will take you through Packaging System under LinuxKit. This might help you build your own customised OS for your infrastructure requirement.
Building Packages Under LinuxKit:
Packages under LinuxKit are just container images stored on DockerHub or in private registries. They are stored under Dockerhub under LinuxKit org.
Base packages for LinuxKit are stored under /pkg directory under LinuxKit repository. To view it, you can clone LinuxKit GITHUB repository and look under linuxkit/pkg directory as shown below:
There are different ways for building LinuxKit base packages. You can either use DockerHub images directly or build your own wrapper around DockerHub images in the form of shell script. You have flexibility to use customised base images or custom package with just 1 binary.
It is important to note that Packaging System under LinuxKit uses extensive use of Multi-Stage Builds to Keep package Smaller. To demonstrate, let us pick up one base package – OpenNTPD and see what does it contain.
As shown above, OpenNTPD contains a Dockerfile, Makefile & etc directory to hold NTP configurational changes.
There are multiple FROMs in this Dockerfile which indicates that packages are built using Multi-Stage Build environment. Let us closely look line by line:
In the first stage, we are trying to pull ALPINE base image which is part of LinuxKit project. LinuxKit has specific versions of all the packages and hence you can see that specific version is hard-coded.
This creates a directory where it is going to install the contents of the packages.
It uses Alpine Package Manager(APK) to install standard base layout for standard root filesystem, busy box for SHELL environment, musl in order to have C libraries which is used by all of these components and finally OpenNTP related packages.
At the end of first stage, a container image based on Alpine base image having a directory holding required files and packages with root filesystem enough to run OpenNTP container is successfully built.
This removes temporary/unwanted files from OUT directly so as to keep the container smaller in size.
Under the 2nd Stage, the content of Dockerfile look like this:
This starts with empty container image. We copy it from the first stage (i.e.. OUT directory) to our root filesystem. Then OpenNTPD daemon configuration & OpenNTP scripts are added from external /etc directory to the container image. Next, we specify the command that whenever container starts, we need OpenNTP daemon to be started.This provides us a minimal package which runs OpenNTPD service.
The last line is very important and recently been introduced under LinuxKit project. If you remember while building YAML file for LinuxKit, we used to specify CAPABILITIES section. For this OpenNTP service, we won’t require it now because we have now LABEL section added during the build process. We have bind mounts so that /etc is mounted, network namespace can be specified, CAP_NET_BIND_SERVICE which helps in binding the container to the interface.
It contains docker build command, squash parameter is passed to squash the layer of the image into a single image to make it smaller in the 2nd stage, –network=none to indicates that while booting the container , it has no access to the network. Other essential entries includes the HASH value provides you with the content hash of all the files in the directory and hence it becomes important to ensure that only the required files are being used to build up container image.
Let us try to run make command and see if this builds up right container image for us.
Let us verify if it built required image or not:
The image is just 3.62MB which is quite smaller in size.
Now, you should be able to use this OpenNTPD base image under your YAML file so that moby tool can build up LinuxKit OS image:
The above YAML content has been picked up from docker.yml file which contains ntpd service container created out of OpenNTPD base image.
In the future blog post, I will showcase how to build your own customised Kernel for LinuxKit.
Did you find this blog helpful? Feel free to share your experience. Get in touch @ajeetsraina.
If you are looking out for contribution/discussion, join me at Docker Community Slack Channel.
Know more about the latest LinuxKit project activities clicking on this link.