Make hardened, lightweight container images with Kubler

cross-posted from: lemmy.srcfiles.zip/post/3841

What is Kubler?

Kubler is a generic, extendable build orchestrator, written in Bash. It can be used to take advantage of Portage’s features to build lightweight Docker or Podman images without needing to mess with crossdev, or as a tool to assist with ebuild development.

Why should you use it?

  • You like lightweight, easy-to-create, containers
  • You want to reduce the attack surface by including only what’s required
  • You want to take advantage of https://wiki.gentoo.org/wiki/USE_flag to manage package features
  • You want the awesome package library offered by the Gentoo ebuild repository (and other ebuild repos)
  • You want up-to-date containers
  • You want a containerised environment for building and testing ebuilds

A real-world example

I recently needed to integrate a containerised application with a vendor-managed openldap instance that uses mTLS authentication. Unfortunately the containerised application does not work with mTLS and the vendor managed openldap instance can’t be easily configured to use anything else.

I came up with the solution of using openldap’s lloadd LDAP load balancer daemon to proxy connections from an encrypted internal network to the LDAP server but was left with the issue that I didn’t have a working openldap container that contained lloadd - of the existing containers that I tried the only one that actually had an lloadd bin didn’t actually include required dependencies!

Glossing over a recent ebuild update to openldap to enable the building of lloadd, enter Kubler - It’s turned out to be an incredibly flexible and hands-off tool compared to trying to accomplish the same thing with (e.g.) Dockerfiles.

Kubler in action

This (lightly sanitised) real-world example creates create a new namespace called ‘larry’ which may contain multiple images.

Use the new command to take care of the boilerplate; choose ‘multi’ when asked for the namespace type:`

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ kubler new namespace larry
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»»» </span><span style="font-weight:bold;color:#a71d5d;"><</span><span style="color:#323232;">enter</span><span style="font-weight:bold;color:#a71d5d;">></span><span style="color:#323232;"> to accept default value
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»»» Working dir type</span><span style="font-weight:bold;color:#a71d5d;">?</span><span style="color:#323232;"> Choices:
</span><span style="color:#323232;">»»»   single - You can</span><span style="color:#183691;">'t add further namespaces to the created working dir, it only holds images
</span><span style="color:#183691;">»»»   multi  - Creates a working dir that can hold multiple namespaces
</span><span style="color:#183691;">»[?]» Type (single): multi
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»» Top level directory name for new namespace '</span><span style="color:#323232;">larry</span><span style="color:#183691;">'? The directory is created at /data/development/gentoo-containers/
</span><span style="color:#183691;">»[?]» Namespaces Dir (kubler-images):
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»»»» Initial image tag, a.k.a. version?
</span><span style="color:#183691;">»[?]» Image Tag (20230706):
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»[!]» New namespace location:  /data/development/gentoo-containers/kubler-images/larry
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»»»» Who maintains the new namespace?
</span><span style="color:#183691;">»[?]» Name (Your Name): Larry the Cow
</span><span style="color:#183691;">»[?]» EMail (your@mail.org): Larry.the.Cow@gentoo.zip
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»»»» Default build engine?
</span><span style="color:#183691;">»[?]» Engine (docker):
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»[✔]» Successfully created "larry" namespace at /data/development/gentoo-containers/kubler-images
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»[!]» Configuration file: /data/development/gentoo-containers/kubler-images/larry/kubler.conf
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»[!]» To manage the new namespace with GIT you may want to run:
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»» $ git init /data/development/gentoo-containers/kubler-images/larry
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»[!]» To create images in the new namespace run:
</span><span style="color:#183691;">»»»
</span><span style="color:#183691;">»»» $ cd /data/development/gentoo-containers/kubler-images/larry
</span><span style="color:#183691;">    $ kubler new image larry/<image_name>
</span>

Although not strictly required, installing Kubler’s example images is a good idea.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ cd larry/
</span><span style="color:#323232;">$ kubler update
</span>

It is worthwhile to begin tracking this new namespace with Git so that images can be tracked as they are created and updated. Kubler has already placed a prepopulated a .gitignore file for convenience.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">pushd /data/development/gentoo-containers/kubler-images/larry
</span><span style="color:#323232;">git init .
</span><span style="color:#323232;">git add .
</span><span style="color:#323232;">git commit -m </span><span style="color:#183691;">"Initial commit"
</span><span style="color:#323232;">popd
</span>

Create the new ‘openldap’ within the existing ‘larry’ namespace, based on the ‘kubler/busybox’ image.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">kubler new image larry/openldap
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»»» </span><span style="font-weight:bold;color:#a71d5d;"><</span><span style="color:#323232;">enter</span><span style="font-weight:bold;color:#a71d5d;">></span><span style="color:#323232;"> to accept default value
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»»» Extend an existing Kubler managed image</span><span style="font-weight:bold;color:#a71d5d;">?</span><span style="color:#323232;"> Fully qualified image id (i.e. kubler/busybox) or scratch
</span><span style="color:#323232;">»[?]» Parent Image (scratch)</span><span style="color:#62a35c;">:</span><span style="color:#323232;"> kubler/busybox
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»»» Add test template(s)? Possible choices:
</span><span style="color:#323232;">»»»   hc  - Add a stub for Docker</span><span style="color:#183691;">'s HEALTH-CHECK, recommended for images that run daemons
</span><span style="color:#183691;">»»»   bt  - Add a stub for a custom build-test.sh script, a good choice if HEALTH-CHECK is not suitable
</span><span style="color:#183691;">»»»   yes - Add stubs for both test types
</span><span style="color:#183691;">»»»   no  - Fck it, we'</span><span style="color:#323232;">ll do it live!
</span><span style="color:#323232;">»[?]» Tests (hc)</span><span style="color:#62a35c;">:</span><span style="color:#323232;"> yes
</span><span style="color:#323232;">»»»
</span><span style="color:#323232;">»[✔]» Successfully created new image at /data/development/gentoo-containers/kubler-images/larry/images/openldap
</span><span style="color:#323232;">»»»
</span>

Note: This step is ‘‘not’’ required; it is possible to directly edit the build.sh file if you are familiar with Portage.

Kubler brings a unique feature to the table when constructing an container image: the –interactive build argument. As the name implies, this launches the build container in an interactive manner, enabling users to investigate the current / inherited configuration.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ kubler build larry/openldap -i
</span>

This will build any missing parent images/builders; the first run may take quite a bit of time - once the local binary package cache and build containers are seeded future runs will be much faster. Once the prerequisite images are ready the build container will present a shell.

For first-time users it may be convenient to search for the openldap package to ensure that the correct atom is selected and investigate any USE flags that are of interest:

<pre style="background-color:#ffffff;">
<span style="font-style:italic;color:#969896;"># eix openldap|output=
</span><span style="color:#323232;">* net-nds/openldap
</span><span style="color:#323232;">     Available versions:  2.4.59-r2^t 2.5.14(0/2.5)^t 2.6.3-r7(0/2.6)^t ~2.6.4-r1(0/2.6)^t ~2.6.4-r2(0/2.6)^t {argon2 autoca +berkdb +cleartext crypt cxx debug experimental gnutls iodbc ipv6 kerberos kinit minimal odbc overlays pbkdf2 perl samba sasl selinux sha2 smbkrb5passwd ssl static-libs +syslog systemd tcpd test ABI_MIPS=</span><span style="color:#183691;">"n32 n64 o32"</span><span style="color:#323232;"> ABI_S390=</span><span style="color:#183691;">"32 64"</span><span style="color:#323232;"> ABI_X86=</span><span style="color:#183691;">"32 64 x32"</span><span style="color:#323232;">}
</span><span style="color:#323232;">     Homepage:            https://www.openldap.org/
</span><span style="color:#323232;">     Description:         LDAP suite of application and development tools
</span>

Edit the image’s build script:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">nano /config/build.sh
</span>

Note: The /config directory in the build container is the host mounted image directory at larry/images/openldap/. Feel free to use a local IDE/editor to edit build.sh instead.

Add the net-nds/openldap and net-misc/curl packages to the _packages variable in build.sh, update cURL USE flags, enable the ~arch (~amd64 - the Gentoo ‘testing’ keyword) for packages we care about:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">_packages</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"net-nds/openldap net-misc/curl"
</span><span style="color:#323232;">...
</span><span style="font-weight:bold;color:#795da3;">configure_rootfs_build</span><span style="color:#323232;">()
</span><span style="color:#323232;">{
</span><span style="color:#323232;">    </span><span style="font-style:italic;color:#969896;"># Update a Gentoo package use flag.
</span><span style="color:#323232;">    update_use </span><span style="color:#183691;">'net-misc/curl' '+ldap'
</span><span style="color:#323232;">    </span><span style="font-style:italic;color:#969896;"># ..or a Gentoo package keyword
</span><span style="color:#323232;">    update_keywords </span><span style="color:#183691;">'net-misc/curl' '+~amd64'
</span><span style="color:#323232;">    update_keywords </span><span style="color:#183691;">'net-nds/openldap' '+~amd64'
</span><span style="color:#323232;">...
</span><span style="color:#323232;">}
</span>

Note: If using the busybox image as a parent, unset the su USE flag from sys-apps/util-linux in the build.sh.

Perform a test run of the first build phase:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ kubler-build-root
</span>

Once this completes successfully exit the interactive builder using exit.

Building the image

Assuming that build.sh has been configured as described above, it should be safe to attempt to build the image.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ kubler build larry/openldap -nF
</span><span style="color:#323232;">»[✘]»[larry/openldap]» fatal: build-test.sh for image larry/openldap:20230704 failed with exit signal: 1
</span>

Note: The arguments are short hand for –no-deps and –force-full-image-build, omitting -n would also rebuild all parent images, which is waste of time in this case.

The build will fail, as expected, due to the build-test.sh script not being implemented. This is a good time to implement the build-test.sh script, which will be used to verify that the image is functional.

Note: pipefail will cause build-test.sh to fail on busybox-based images

<pre style="background-color:#ffffff;">
<span style="font-style:italic;color:#969896;">#!/usr/bin/env sh
</span><span style="color:#323232;">
</span><span style="color:#62a35c;">set</span><span style="color:#323232;"> -eo
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Do some tests and exit with either 0 for healthy or 1 for unhealthy
</span><span style="font-style:italic;color:#969896;"># Check that the openldap bin launches and provides some expected output
</span><span style="color:#323232;">/usr/lib/openldap/lloadd -VV  </span><span style="color:#0086b3;">2</span><span style="font-weight:bold;color:#a71d5d;">>&</span><span style="color:#0086b3;">1 </span><span style="font-weight:bold;color:#a71d5d;">| </span><span style="color:#323232;">grep </span><span style="color:#183691;">"OpenLDAP" </span><span style="font-weight:bold;color:#a71d5d;">|| </span><span style="color:#62a35c;">exit</span><span style="color:#323232;"> 1
</span><span style="color:#323232;">
</span><span style="color:#62a35c;">exit</span><span style="color:#323232;"> 0
</span>

Unfortunately this image is not suitable for a build-time docker health check via the docker-healthcheck.sh mechanism, so must be disabled in larry/images/openldap/build.conf:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">POST_BUILD_HC</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">false
</span>

A health check suitable for your environment should be provided using standard docker syntax in the image’s Dockerfile.template instead. Ensure that the provided docker-healthcheck.sh script iS updated (or commented out of the dockerfile) as the default will fail.

Modify the image’s Dockerfile.template to add any finishing touches, such as the ENTRYPOINT or CMD directives. In this example the container will act as an LDAP proxy via lloadd; additional configuration will be provided at runtime by mounting the configuration into the container.

<pre style="background-color:#ffffff;">
<span style="color:#323232;">FROM ${IMAGE_PARENT}
</span><span style="color:#323232;">LABEL maintainer="${MAINTAINER}"
</span><span style="color:#323232;">
</span><span style="color:#323232;">ADD rootfs.tar /
</span><span style="color:#323232;">
</span><span style="color:#323232;">COPY docker-healthcheck.sh /usr/bin/docker-healthcheck
</span><span style="color:#323232;">HEALTHCHECK --interval=60s --timeout=5s --start-period=5s --retries=3 CMD ["docker-healthcheck"]
</span><span style="color:#323232;">
</span><span style="color:#323232;">CMD ["/usr/lib/openldap/lloadd"]
</span>

Re-run the build:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">$ kubler build larry/openldap -nF
</span><span style="color:#323232;">»[✔]»[larry/openldap]» done.
</span>

At this point the image should exist in the local Docker/Podman registry and be ready for use:

<pre style="background-color:#ffffff;">
<span style="color:#323232;">docker images
</span><span style="color:#323232;">REPOSITORY                                              TAG                       IMAGE ID       CREATED          SIZE
</span><span style="color:#323232;">larry/openldap                                          20230704                  09347c55282b   2 minutes ago    56.4MB
</span><span style="color:#323232;">larry/openldap                                          latest                    09347c55282b   2 minutes ago    56.4MB
</span>

Hopefully this has been useful and you are now ready to build your own images! I’ve been incredibly impressed with how easy it is to use the tool (and it it’ll run from any distro with a recent version of Docker/Podman), and the quality of the resulting images. I’m a recent convert, but have updated the Gentoo Wiki with the above information (and some extra info on using it for ebuild development) and will be using Kubler in future wherever I need to create images.

Happy containering!

  • All
  • Subscribed
  • Moderated
  • Favorites
  • gentoo@reddthat.com
  • GTA5RPClips
  • DreamBathrooms
  • thenastyranch
  • magazineikmin
  • tacticalgear
  • cubers
  • Youngstown
  • mdbf
  • slotface
  • rosin
  • osvaldo12
  • ngwrru68w68
  • kavyap
  • InstantRegret
  • JUstTest
  • everett
  • Durango
  • cisconetworking
  • khanakhh
  • ethstaker
  • tester
  • anitta
  • Leos
  • normalnudes
  • modclub
  • megavids
  • provamag3
  • lostlight
  • All magazines