03 März 2020

Buildah – Container Images ohne Docker und root

Lange Zeit war Docker das Standard-Tool zum Erstellen und Betreiben von Containern.

Durch die starke Verbreitung der Technologie gibt es mittlerweile deutlich mehr Tools, die einzelne Teilbereiche des Gespannes aus Docker-Daemon und Docker-CLI übernehmen. Auch im Hinblick auf den Aspekt der Sicherheit ist der root-Rechte benötigende Docker-Daemon für viele Nutzer nicht unbedingt die erste Wahl.

Buildah ist neben Podman und Skopeo Teil der RedHat-Containertools und übernimmt den Abschnitt für Erstellung und Modifizierung von Containern und Images.

Die Tools wurden dabei nach dem bekannten Unix-Prinzip entwickelt, so dass es kleinere Programme beinhaltet, die jedoch gut zusammenarbeiten und jeweils einen eigenen Teilbereich abdecken.

Entwicklung

Buildah wurde in der Version 1.0 am 07.05.2018 veröffentlicht und ist unter Apache 2.0 lizenziert. Die Implementierung erfolgt in Golang und wird in erster Instanz von der „containers organisation“ vorangetrieben. Diese beinhaltet sowohl RedHat-Mitarbeiter, wie auch externe Entwickler.

Der Code kann auf Github eingesehen werden. Die Entwicklung erfolgt dabei in keinem festen Releasezyklus. So können sowohl Monate wie auch Wochen zwischen den Versionen liegen, je nachdem ob genügend neue Features für einen Release implementiert wurden.

Container im Userspace

Eine zentrale Komponente von Buildah und auch Podman ist die libpod, die es ermöglicht Container im Userspace zu starten und Images zu erstellen. Diese setzt auf slirp4netns, fuse-overlayfs und die /etc/sub(u|g)id auf.

slirp4netns

Seit Kernelversion 3.8 sind network- bzw. usernamespaces nutzbar. Jedoch werden für die Erstellung eines Interfaces, das diese nutzen kann, immer noch root-Rechte benötigt.

slirp4netns umgeht diese Problematik, in dem es ein TAP-Device innerhalb des neuen Namespaces mit dem usermode TCP-IP-Stack verbindet. Details dazu sind im Projekt auf Github zu finden.

fuse-overlayfs

fuse-overlayfs stellt eine Implementierung von overlay+shiftfs in FUSE (Filesystem in Userspace) bereit. Dieses ermöglicht Nutzern Container-Dateisysteme auch ohne root-Rechte zu erstellen und anzupassen. Details dazu finden sich auf dem Projekt-Github.

/etc/sub(u|g)id

Sowohl /etc/subuid als auch /etc/subgid sind Teil der shadow-utils auf aktuellen Systemen und definieren welche uids und gids ein Nutzer des Systems beanspruchen darf.

Als Standard bekommt jeder neu angelegte Nutzer eines Systems 65536 ids zugeteilt (sowohl uid, wie auch gid). Es sind jedoch auch größere oder mehrere Ranges möglich. Diese müssen allerdings manuell angepasst werden und es ist darauf zu achten, dass es keine Überschneidungen gibt. Auch ist es möglich, dass beiden Dateien auf älteren Systemen manuell angelegt / angepasst werden müssen. Mindestens seit Fedora 30 sind diese Teil der Standardinstallation.

Diese IDs werden beim Betrieb von libpod für die uid/gid innerhalb von Containern genutzt.

Folgend ein Beispiel:

[builder@buildah ~]$ cat /etc/subuid
builder:100000:65536
wracker:165536:65536

[builder@buildah ~]$ cat /etc/subgid
builder:100000:65536
wracker:165536:65536

Hier ist zu sehen, dass der Nutzer builder uids/gids im Bereich von 100000 bis 165535 nutzen darf. Neue Nutzer bekommen (ohne manuelle Anpassungen) den entsprechend nächsten Block zugeteilt.

Installation

Buildah ist auf den gängigen Distributionen entweder direkt paketiert (z.B. Fedora, Archlinux, Suse, Gentoo) oder aber per Dritt-Repo verfügbar (Debian und Derivate).

Unter Fedora ist es zum Zeitpunkt der Erstellung dieses Artikel noch notwendig zusätzlich das Paket seccomp zu installieren, da es hier in der letzten Version Probleme mit den Abhängigkeiten gibt. So wäre der Aufruf sudo dnf install buildah libseccomp unter Fedora und sudo pacman -S buildah unter Archlinux.

Für die Debian-Derivate gibt es das Kubic-Repository, in dem aktuelle Pakete verfügbar sind.

Debian selbst enthält das Paket zunächst nur in den unstable-Repositories. Für Ubuntu oder Debian stable muss Kubic genutzt werden.

Selbstredend ist eine manuelle Installation über die Quellen ebenfalls möglich. Eine Komplette Liste der Installationsmöglichkeiten findet sich auf Github.

Nach der Installation von Buildah sollte der Aufruf in etwa die folgende Ausgabe liefern:

[builder@buildah ~]$ buildah
A tool that facilitates building OCI images

Usage:
  buildah [flags]
  buildah [command]
# ...

Konfiguration

Die Konfiguration findet in einzelnen Dateien im Ordner /etc/containers für die globale Konfiguration statt, oder in $HOME/.config/containers
für die Konfiguration der einzelnen Nutzer. Die Nutzerkonfiguration überschreibt dabei wie immer die globale.

Folgende Dateien sind dabei in den Ordnern, bzw. mindestens global, zu finden:

  • storage.conf – Optionen und Konfiguration für Storage
  • mounts.conf – automatischer Mount in einen Container bei der Erstellung
  • registries.conf – Registrykonfiguration V1
  • registries.d – Registrykonfiguration V2

storage.conf

Hier wird definiert mit welchem Treiber und in welchen Ordner die Images bzw. der interne Storage der Container gespeichert werden soll.

Beispiel der globalen Konfiguration:

[root@buildah containers]# grep -v "^#\|^$" /etc/containers/storage.conf 
[storage]
driver = "overlay"
runroot = "/var/run/containers/storage"
graphroot = "/var/lib/containers/storage"
# ...

Beispiel einer Konfiguration eines Nutzers:

[builder@buildah containers]$ cat ~/.config/containers/storage.conf

[storage]
  driver = "overlay"
  runroot = "/var/tmp/1000"
  graphroot = "/home/builder/.local/share/containers/storage"
# ...

mounts.conf

Hier wird pro Zeile ein Mount definiert, der immer in jeden auf dem System bzw. unter dem Nutzer gestarteten Container eingebunden werden soll.

Diese Möglichkeit wird meist genutzt um häufig genutzt Credentials oder Zertifikate in einen Container zu mounten (z.B: /usr/share/rhel/secrets:/run/secrets für RHEL-Subscriptions). Hier ist ggf. darauf zu achten, dass die Mount-Wünsche nicht mit bestehenden SELinux-Konfigurationen kollidieren. Im Problemfall wäre hier das /var/log/audit/audit.log zu prüfen.

registries.conf

Konfiguration der verfügbaren Container-Registries im V1-Format. Hier können analog zu Docker-Konfiguration die genutzten Registries freigegeben oder auch ungewollte Blockiert werden. Bei Mehrfachangaben werden die Registries der Reihe nach angefragt.

Auch wenn es sich hier um „V1“ handelt und „V2“ bereits verfügbar ist, so ist dies die stabile und auch empfohlene Variante seine Repositories zu definieren.

Beispiel einer registries.conf:

[root@buildah containers]# grep -v "^#\|^$" /etc/containers/registries.conf
[registries.search]
registries = ['registry.access.redhat.com', 'registry.fedoraproject.org', 'registry.centos.org', 'docker.io']
[registries.insecure]
registries = []
[registries.block]
registries = []

registries.d

Stellt die aktuell noch in der Beta befindlichen Version 2 der Registry-Konfiguration dar. Im Ordner registries.d finden sich verschiedene Dateien,
welche jeweils Registries konfigurieren.

In dieser Version wird auf das Feature der sigstores (Signature-Stores) gesetzt, welches jedoch aktuell nur von wenigen Registries überhaupt unterstützt wird.

Es wird dabei zwischen sigstore-staging (nur schreibend) und sigstore (lesend und schreibend) unterschieden. So soll das Image z.B. im sigstore-staging nur erstellt werden um es anschließend im sigstore zu signieren und zu veröffentlichen.

Folgend eine Beispielkonfiguration:

docker:
  privateregistry.com:
  sigstore: http://privateregistry.com/sigstore/
  sigstore-staging: /mnt/nfs/privateregistry/sigstore

Da es sich bei dem Feature jedoch um ein Beta-Feature ohne breite Unterstützung handelt soll hier an dieser Stelle nicht weiter darauf eingegangen werden.

Details dazu finden sich auf Github.

Buildah – CLI

Viele Befehle der Buildah-CLI sind mit der Docker-CLI soweit kompatibel und nur um eigene Optionen ergänzt. So z.B. ps, images, pull, push, rm, usw.

Im Unterschied zu Docker kann jedoch auch die CLI genutzt werden um neue Container und Images zu erstellen. Dafür stehen dann Optionen wie run,add,copy o.ä. bereit. Dies erlaubt eine sehr gute Automatisierbarkeit bzw. Parametrisierbarkeit der Erstellung, da hier mittels einfacher Shell-Befehle statt eines Dockerfile-Images gebaut werden können.

Es werden bei der Erstellung zwei verschiedene Formate unterstützt, die sich jedoch nur im Manifest unterscheiden: DockerV2 und OCI (standard).

Erstellung eines Images mittels CLI

Der erste Schritt um ein Image zu erstellen lautet z.B. buildah from centos, welches analog zum Dockerfile FROM das Basisimage definiert. Buildah wird das Image herunterladen und einen entsprechenden Container erstellen.

Um nun Anpassungen am Basisimage vorzunehmen kann z.B. buildah run $newcontainer dnf install wget ausgeführt werden. Der run Befehl führt ebenfalls, wieder analog zu Docker, einen Befehl innerhalb des Container aus. Um ENV-Variablen wie einen Proxy o.ä. zu definieren kann buildah config
genutzt werden.

So z.B. buildah config --created-by "DaniloE" $newcontainer um den Autor zu definieren. Mit config lassen sich diverse Optionen im Container setzen wie z.B. env, cmd, author, port, user. Details dazu unter man buildah-config.

Um Dateien in einen Container zu kopieren kann copy/add genutzt werden. z.B. buildah copy/add $newcontainer ./hello /hello.

copy kopiert dabei Dateien eines lokalen Pfades in den Container während add einen Pfad oder eine URL akzeptiert.

Um aus dem angepassten Container ein neues Image zu erstellen wird nach Abschluss der Anpassungen z.B. buildah commit $newcontainer hellocontainer oder buildah commit $newcontainer docker://localhost:5000/hellocontainer genutzt. Dieses erstellt ein Image aus 2 Layern (Basis-Image + alle Änderungen).

Sollte es aus z.B. Platzgründen nötig sein dies auf einen Layer zu reduzieren kann der Parameter --squash genutzt werden. --rm löscht bei diesem Aufruf auch gleich den Zugrunde liegenden Arbeitscontainer.

Zusammenfassend sähe ein Skript um einen Container zu bauen z.B. aus wie folgt:

newcontainer=$(buildah from centos)
buildah run $newcontainer touch /test
buildah copy $newcontainer ./hello /hello
buildah config --cmd /hello $newcontainer
buildah config --created-by "DaniloE"  $newcontainer
buildah config --author "DaniloE" $newcontainer
buildah config --label name=simple-helloworld $newcontainer
buildah commit --rm $newcontainer hellocontainer

Erstellung eines Images mittels Dockerfile

Buildah hat natürlich im Zuge der Docker-Kompatibilität eine Möglichkeit Container aus bestehenden Dockerfiles zu erstellen.

Hierzu wird der Aufruf buildah bud (build-using-dockerfile) genutzt.

Es erstellt ein Image aus einem Containerfile und einer Buildcontext-URL.

Wird kein Buildcontext übergeben ist $(pwd) automatisch gesetzt und hier wird auf das Dockerfile erwartet. Es ist aber auch möglich mit Remote-Sourcen zu arbeiten.

Weitere Möglichkeiten wären z.B. als Context ein Git-Repository zu übergeben. Hier wird der Inhalt des Repositories in ein temporäres Verzeichnis ausgecheckt (default „/var/tmp“) und im Ergebnis das Dockerfile erwartet.

Alternativ funktioniert dies auch mit einer Archiv URL. Hier wird das Archiv (xz,bzip2,gzip) ebenfalls in das temporäre Verzeichnis heruntergeladen, entpackt und als Context behandelt

Mit z.B. -f files/Dockfile wird der Pfad zum Dockerfile angegeben. Dies kann sowohl ein lokales Verzeichnis sein, oder aber ein Unterverzeichnis im Git oder Archiv.

Wichtig: Bei der Übergabe eines remote-context wird der Parameter immer auf den Inhalt der Remote Sources angewandt, nicht auf einen lokalen Pfad. Es ist also nicht möglich ein lokales Dockerfile und einen Remote-Context zu verbinden.

Weitere Informationen zu Parametern und Optionen sind unter man buildah-bud ausführlich dokumentiert.

Folgend noch einige Beispielaufrufe zur Veranschaulichung:

  • buildah bud --iidfile "id.txt" --squash -t "hello:dev" .
    • schreibt image-id nach hello.id
    • fügt alle Layer am Ende auf einen zusammen
    • tagged das Image als „hello:dev“
  • buildah bud github.com/scollier/purpletest
  • buildah bud -f dev/Containerfile https://10.10.10.1/docker/context.tar.gz

Erstellung eines Images „from scratch“

Buildah bietet auch die Option, Images komplett neu zu erstellen ohne ein vorangegangenes Basisimage zu nutzen.

Hierfür wird ein neuer Container mit buildah from scratch erstellt.

Dieser beinhaltet keine Dateien sondern lediglich ein leeres Dateisystem, das man anschließend befüllt.

So können z.B. mit der lokalen Paketverwaltung Pakete installiert oder aber lediglich Binaries in das Dateisystem übertragen werden.

Folgend ein Beispielskript für die Erstellung eines Containers from scratch mit der CLI.

newcontainer=$(buildah from scratch)
scratchmnt=$(buildah mount ${newcontainer})
dnf install --installroot ${scratchmnt} --releasever 30 bash coreutils --setopt install_weak_deps=false -y

Es wird ein neuer, leerer Container erstellt, das Dateisystem des Containers in ein temporäres Verzeichnis gemounted und mit Bordmitteln Pakete inkl. Abhängigkeiten installiert.

Für die Nutzung als unprivilegierter User gibt es hier jedoch einen wichtigen Punkt zu beachten.

So bricht das buildah mount ohne root Rechte ab:

[builder@buildah ~]$ newcontainer=$(buildah from centos)
[builder@buildah ~]$ buildah mount $newcontainer
cannot mount using driver overlay in rootless mode. You need to run it in a `buildah unshare` session

Um diese Problematik zu umgehen muss die Containererstellung in einem separaten Namespace durchgeführt werden.

Dieser wird mittels buildah unshare erstellt und auch direkt aktiviert. Da in diesem sowohl uid wie auch gid als root gesetzt sind lässt sich das FS entsprechend mounten.

[builder@buildah ~]$ buildah unshare
[root@buildah ~]# newcontainer=$(buildah from centos)
[root@buildah ~]# buildah mount $newcontainer
/home/builder/.local/share/containers/storage/overlay/...
[root@buildah ~]# ls -l /home/builder/.local/share/containers/storage/...
insgesamt 16
-rw-r--r--.  1 root root 12076  5. Dez 2018  anaconda-post.log
lrwxrwxrwx.  1 root root     7  5. Dez 2018  bin -> usr/bin
drwxr-xr-x.  2 root root     6  5. Dez 2018  dev
drwxr-xr-x. 47 root root  4096  5. Dez 2018  etc

Dieser Umstand klingt erst einmal problematisch, ist in der Praxis jedoch nicht, sofern man die Container automatisiert erstellt.

Ein Beispielskript findet sich unter man buildah-unshare.

Vergleich Buildah und andere Lösungen

| Applikation | Punkte                                         |
| ----------- | ---------------------------------------------- |
| Buildkit    | * benötigt Daemon + CLI                        |
|             | * schneller bei großen/Komplexen Builds        |
|             | * kompatibel mit verschiedenen Imagefile       |
|             | * z.B. Docker-/Mocker-/Gopperfile              |
|             | * rootless Mode noch experimental              |
| Kaniko      | * Nur Unterstützung für Dockerfiles            |
|             | * speziell für Builds in Containern/Kubernetes |
|             | * Nur Kaniko-eigenes Image empfohlen           |
|             | * Empfohlen für Gitlab CI/CD []                |

Unterstützung

Falls Sie Unterstützung beim Einsatz von Docker oder Buildah benötigen, steht Ihnen unser Open Source Support Center zur Verfügung – Falls gewünscht auch 24 Stunden am Tag, an 365 Tagen im Jahr.

Wir freuen uns auf Ihre Kontaktaufnahme.

Kategorien: HowTos
Tags: Buildah Docker Open Source Software

über den Autor

Danilo Endesfelder

Berater

zur Person

Danilo ist seit 2016 Berater bei der credativ GmbH. Sein fachlicher Fokus liegt bei Containertechnologien wie Kubernetes, Podman, Docker und deren Ökosystem. Außerdem hat er Erfahrung mit Projekten und Schulungen im Bereich RDBMS (MySQL/Mariadb und PostgreSQL<sup>®</sup>). Seit 2015 ist er ebenfalls im Organisationsteam der deutschen PostgreSQL<sup>®</sup> Konferenz PGConf.DE.

Beiträge ansehen


Beitrag teilen: