Building an image with Buildah
What is Buildah
I've been following the Buildah posts on projectatomic.com for some time now, but I never got to try it out in detail. Earlier this week, Red Hat announced the 1.0 release of Buildah, the perfect reason to try it out for real.
In short, Buildah is a tool to build container images. Why a new container image builder if we have docker build and others already?
To start, Buildah doesn't need a daemon running all the time, which makes it very lightweight in terms of resources. This makes it very suitable for pipeline-use. The build server doesn't need to have Docker installed to build new container images.
The container images are not built by Docker, but they can be used with Docker, just as a normal Docker image. Buildah builds OCI-compatible images. This makes them usable by any runtime that can handle OCI-images.
A container image can be built with a simple bash script. Yes, bash scripting again, but at least you can use normal bash skills, without having to handle with the Dockerfile format.
If you don't want to rewrite all of your Dockerfiles, but you want to get rid of that big fat Docker daemon, there is good news. Buildah can build container images from any Dockerfile, which is good if you want a slow transition to Buildah over time.
Buildah from scratch
In the past, I've created a simple Docker image to use for building rpm packages. The following Dockerfile is used:
FROM centos:centos7
MAINTAINER Thomas Cassaert "[email protected]"
RUN yum update -y \
&& yum -y install ruby-devel rubygems gcc make rpmdevtools \
&& yum clean all \
&& rm -rf /var/cache/yum
RUN gem install --no-ri --no-rdoc fpm
WORKDIR /src
CMD "/src/fpm.sh"
This image expects a bash script called fpm.sh in the src directory in the container.
I start this container with
docker run -v "$(pwd)":/src tcassaert/fpm-builder
The fpm.sh script needs to be available in pwd and can be as simple as
#!/bin/bash
set -ex
fpm -s dir -t rpm -n demo.rpm .
Now I want to rebuild this image with Buildah.
To get started with Buildah, it needs to be installed of course. On CentOS, it's as easy as
yum install buildah
The easiest way to get started is by building a bash script step by step.
#!/bin/bash
set -ex
ctr=$(buildah from scratch)
ctrmnt=$(buildah mount ${ctr})
This contains only two real Buildah steps, but there is already happening a lot. In the first instruction, we ask Buildah to start from scratch. This really is an empty container, with just some metadata.
In the second instruction, we ask Buildah to mount that empty container. That mountpoint should be located at /var/lib/containers/storage/overlay.
[root@buildah ~]# echo $ctrmnt
/var/lib/containers/storage/overlay/be44d91ce2ab9971ff75f1b0c22e8bf7b3e727b44cdf23b7571cee26079d1d45/merged
Let's add a new command to our script:
yum install -y --installroot ${ctrmnt} bash coreutils gcc ruby-devel make rpm-build --releasever 7 --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8
This step installs the minimum requirements into the $ctrmount. Note that this command is run from the Buildah host, as there is no yum in the image. The extra command like --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8 are inspired by pixdrift his buildah scripts and on Buildah GitHub issue #532. They are used to make the image as small as possible.
The function of --releasever is
[root@buildah ~]# yum help | grep releasever
--releasever=RELEASEVER
set value of $releasever in yum config and repo files
The next step should be to clean the yum cache.
if [ -d "${ctrmnt}" ]; then
rm -rf "${ctrmnt}"/var/cache/yum
fi
The Docker image is using the /src directory as workingdir, so it needs to be available in the new image as well. This is easy.
mkdir -p "${ctrmnt}"/src
Now it's time to install fpm in the image. Just as with the installation of rpm packages, it is possible to use the Buildah host's gem install. The downside is that you need to have the packages installed in the Buildah host to do this.
gem install -i "${ctrmnt}"/usr/share/gems/ --bindir "${ctrmnt}"/usr/bin --no-ri --no-rdoc fpm
We should have everything installed in the image right now. The only thing left is some configuration of the image.
buildah config --label name=fpm-buildah ${ctr}
buildah config --workingdir /src ${ctr}
buildah config --cmd /src/fpm.sh ${ctr}
In these instructions we set the name of the image, the workingdir and the command the container should execute.
Everything is ready to be unmounted and to be committed.
buildah unmount ${ctr}
buildah commit ${ctr} tcassaert/buildah-fpm
The resulting script looks like this.
#!/bin/bash
set -ex
# start new container from scratch
ctr=$(buildah from scratch)
ctrmnt=$(buildah mount ${ctr})
# install the packages
yum install --installroot ${ctrmnt} bash coreutils gcc ruby-devel make rpm-build --releasever 7 --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8 -y
# clean up yum cache
if [ -d "${ctrmnt}" ]; then
rm -rf "${ctrmnt}"/var/cache/yum
fi
# make the /src directory
mkdir -p "${ctrmnt}"/src
# install fpm
gem install -i "${ctrmnt}"/usr/share/gems/ --bindir "${ctrmnt}"/usr/bin --no-ri --no-rdoc fpm
# configure container label and command
buildah config --label name=buildah-fpm ${ctr}
buildah config --workingdir /src $ctr
buildah config --cmd /src/fpm.sh ${ctr}
# commit the image
buildah unmount ${ctr}
buildah commit ${ctr} tcassaert/fpm-buildah
Executing this script builds the requested image. I've time the time it took to build this immage.
real 3m2.321s
user 1m19.610s
sys 0m25.120s
Now, let's build the same image using Docker and see how long it takes.
real 2m15.953s
user 0m0.093s
sys 0m0.094s
Buildah appears to be slower, which is not unnormal, as it builds everything from scratch.
Full Dockerless
Next to Buildah, RedHat is also building Podman. It uses the same #nobigfatdaemons philosophy as Buildah, promoted by @rhatdan. Podman is a CLI tool to run OCI-compatible containers using pods. Podman uses by default runc to run the containers. Most of the Docker cli commands are the same in Podman.
To make this blog Dockerless, we'll use Podman to test the image.
Installing Podman is as easy as installing Buildah.
yum install -y podman
You can simply list the current available images with podman images.
[root@buildah ~]# podman images
docker.io/tcassaert/fpm-buildah latest 0f80890a7406 About a minute ago 328MB
We can see the image we built earlier just as we would if we pulled it from the Docker Store. The image is 328MB in size. Let's compare it with the image built from the Dockerfile.
[root@buildah ~]# podman pull tcassaert/fpm-builder
[root@buildah ~]# podman images
docker.io/tcassaert/fpm-builder latest b1ffce8113cf 2 weeks ago 357MB
docker.io/tcassaert/fpm-buildah latest 0f80890a7406 About a minute ago 328MB
The Buildah image is a little bit smaller, 29MB to be exact.
The only thing left now is to test the image...
[root@buildah ~]# podman run -v $(pwd):/src fpm-buildah
{:timestamp=>"2018-06-08T09:56:12.062668+0000", :message=>"Created package", :path=>"demo.rpm-1.0-1.x86_64.rpm"}
Conclusion
Buildah is totally ready to make the #nobigfatdaemons a thing. The images are smaller, easy to build and work with any OCI-compatible runtime and most importantly, the don't require a daemon to be running. So even when using Kubernetes with CRI-O, you don't need to adjust anything at all to use the images built by Buildah.
With a template script, it's just about filling in the needed packages and the container name.
Podman is a nice CLI-tool to replace the need for Docker. The transition to Podman from Docker is just changing every docker instruction to podman.