A Quick Look at LinuxKit Packaging System

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.

Screen Shot 2017-06-03 at 9.44.56 AM

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.

Screen Shot 2017-06-03 at 9.41.00 AM

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.

Screen Shot 2017-06-03 at 7.42.13 AM

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:

git clone https://github.com/linuxkit/linuxkit
cd linuxkit/pkg/

 

Screen Shot 2017-06-03 at 6.56.14 AM

 

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.

Screen Shot 2017-06-03 at 9.38.57 AM

Ajeets-MacBook-Air:openntpd ajeetraina$ pwd;ls
/Users/ajeetraina/linuxkit/pkg/openntpd
Dockerfile Makefile etc

As shown above, OpenNTPD contains a Dockerfile, Makefile & etc directory to hold NTP configurational changes.

Dockerfile:

Screen Shot 2017-06-03 at 7.54.05 AM

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:

FROM linuxkit/alpine:630ee558e4869672fae230c78364e367b8ea67a9
 

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.

RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/

This creates a directory where it is going to install the  contents of the packages.

RUN apk add --no-cache --initdb -p /out \
    alpine-baselayout \
    busy box \
    musl \
    openntpd \
    && true

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.

RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache

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:

Screen Shot 2017-06-03 at 9.10.29 AM

 

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.

Screen Shot 2017-06-03 at 9.24.45 AM

File: Makefile

Screen Shot 2017-06-03 at 8.43.44 AM

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.

Ajeets-MacBook-Air:openntpd ajeetraina$ sudo make
Password:
docker build --squash --no-cache --network=none -t linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5 .
Sending build context to Docker daemon  5.632kB
Step 1/12 : FROM linuxkit/alpine:630ee558e4869672fae230c78364e367b8ea67a9 AS mirror
 ---> 72ff54f67634
Step 2/12 : RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
 ---> Running in 175f2b917c9a
 ---> 08b114fc941c
Removing intermediate container 175f2b917c9a
Step 3/12 : RUN apk add --no-cache --initdb -p /out     alpine-baselayout     busybox     musl     openntpd     && true
 ---> Running in 3109ff2301fa
(1/7) Installing musl (1.1.16-r9)
(2/7) Installing busybox (1.26.2-r4)
Executing busybox-1.26.2-r4.post-install
(3/7) Installing alpine-baselayout (3.0.4-r0)
Executing alpine-baselayout-3.0.4-r0.pre-install
Executing alpine-baselayout-3.0.4-r0.post-install
(4/7) Installing libressl2.5-libcrypto (2.5.4-r0)
(5/7) Installing libressl2.5-libssl (2.5.4-r0)
(6/7) Installing libressl2.5-libtls (2.5.4-r0)
(7/7) Installing openntpd (6.0_p1-r3)
Executing openntpd-6.0_p1-r3.pre-install
Executing busybox-1.26.2-r4.trigger
OK: 3 MiB in 7 packages
 ---> 2336220414ec
Removing intermediate container 3109ff2301fa
Step 4/12 : RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache
 ---> Running in c39c2d86c898
 ---> 9248b40c1e31
Removing intermediate container c39c2d86c898
Step 5/12 : FROM scratch
 ---> 
Step 6/12 : ENTRYPOINT
 ---> Running in a1475ce25417
 ---> 89a4eefb0f2e
Removing intermediate container a1475ce25417
Step 7/12 : CMD
 ---> Running in 3299eb03002b
 ---> e0761d845e86
Removing intermediate container 3299eb03002b
Step 8/12 : WORKDIR /
 ---> dc50915b1354
Removing intermediate container 37292d5db9f6
Step 9/12 : COPY --from=mirror /out/ /
 ---> 60c358443d49
Removing intermediate container 4c746169a103
Step 10/12 : COPY etc/ /etc/
 ---> 049744fe228f
Removing intermediate container f9f21176ca4d
Step 11/12 : CMD /usr/sbin/ntpd -d -s
 ---> Running in 94c8a776666d
 ---> 56d618a1fa76
Removing intermediate container 94c8a776666d
Step 12/12 : LABEL org.mobyproject.config ‘{“capabilities”: [“CAP_SYS_TIME”, “CAP_SYS_NICE”, “CAP_SYS_CHROOT”, “CAP_SETUID”, “CAP_SETGID”]}’
 ---> Running in c1514342c0fb
 ---> 46987e8c78bc
Removing intermediate container c1514342c0fb
Successfully built c1dbda899687
Successfully tagged linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5
DOCKER_CONTENT_TRUST=1 docker pull linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5 || \
DOCKER_CONTENT_TRUST=1 docker push linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5
Pull (1 of 1): linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5@sha256:26e88ffd48262f4a03ed678d2edee35b807b4d7fe561a3aa6577eef325e317c4
sha256:26e88ffd48262f4a03ed678d2edee35b807b4d7fe561a3aa6577eef325e317c4: Pulling from linuxkit/openntpd
Digest: sha256:26e88ffd48262f4a03ed678d2edee35b807b4d7fe561a3aa6577eef325e317c4
Status: Image is up to date for linuxkit/openntpd@sha256:26e88ffd48262f4a03ed678d2edee35b807b4d7fe561a3aa6577eef325e317c4
Tagging linuxkit/openntpd@sha256:26e88ffd48262f4a03ed678d2edee35b807b4d7fe561a3aa6577eef325e317c4 as linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5

Let us verify if it built required image or not:

Screen Shot 2017-06-03 at 9.21.56 AM

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:

Screen Shot 2017-06-03 at 9.29.40 AM

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.