Suche
Docker Container Loading - Teil 1

10 kreative Wege, Docker Images zu bauen: Dockerfile & Docker Commit

Roland Huß

© Shutterstock.com / Good_24

Ist der klassische Weg, Docker Images über docker build zu bauen, wirklich in jedem Fall die beste Art und Weise? In seiner Artikelserie geht Roland Huß, Software Engineer bei Red Hat, dieser Frage auf den Grund und stellt zehn alternative und kreative Wege vor, Docker Images zu erstellen.

Erinnert ihr euch noch daran, wie ihr euer erstes Docker Image gebaut habt? Ein Basis-Image auswählen, ein simples Dockerfile schreiben und mithilfe von docker build wird das Ganze dann gebaut. Das ist eigentlich schon zu einfach, um wahr zu sein. docker build ist auch heute noch der kanonische Weg, Docker Images zu bauen. Aber ist es auch immer die beste Methode für alle Fälle? Welche Alternativen gibt es zum Bauen von Docker Images?

Diese kleine Artikelreihe präsentiert zehn alternative und kreative Wege, Docker Images zu bauen. Dabei werden wir kennenlernen, wie man mit spezialisierten Tools diese Images lokal bauen kann, aber auch verschiedene Möglichkeiten erforschen, diese Aufgabe in die Cloud zu verlagern.

Naturgemäß wird diese Auswahl nicht vollständig sein, aber dennoch einen guten Eindruck vermitteln, was aktuell im Bereich „Docker Image bauen“ so zur Verfügung steht. Auch ist die Selektion natürlich nur eine Bestandsaufnahme. Weitere Tools und Methode werden in Zukunft dazukommen, andere werden auch wieder verschwinden. Ziel dieser Artikelreihe ist es, einen Blick über den Tellerrand zu werfen und zu verdeutlichen, dass es mehr gibt als reine Dockerfiles.

Bevor wir aber unsere Reise starten, lasst uns in dieser Einführung nochmals auf die Möglichkeiten schauen, die uns Docker von Haus aus bietet.

Tatsächlich bietet Docker nämlich zwei Möglichkeiten zur Erstellung von Docker Images: Die bereits erwähnte Dockerfile-Methode und den Weg über docker commit, bei dem aus einem Docker-Container ein Docker Image erzeugt wird.

Dockerfile

Die klassische Methode ein Docker image zu erstellen, ist mithilfe eines Dockerfiles. Dabei is ein Dockerfile ein einfaches Skript, das anhand einer handvoll Schlüsselwörter ein Docker Image Schicht für Schicht aufbaut.

Ein Dockerfile sieht beispielsweise so aus:

FROM centos:centos7
RUN yum install epel-release -y && \
    yum update -y && \
    yum install redis -y && \
    yum clean all
EXPOSE 6379
ENTRYPOINT ["/usr/bin/redis-server"]

Hier wird von einem Centos-7-Basis-Image gestartet. Im Anschluss wird ein Redis-Server über yum installiert und der Port 6379 als exportierbar deklariert. Schließlich wird als Entrypoint der entsprechende Befehl eingerichtet. In diesem Beispiel werden on-top zu dem Basis-Image drei weitere Layer erzeugt. Man beachte, dass in der RUN-Zeile (die aus drei Textzeilen besteht, die mit \ zusammengefügt sind) mehrere Kommandos mit & & verknüpft werden. Dieser altbekannte Trick wird benutzt, um die Anzahl der erzeugten Schichten und damit den Platzverbrauch niedrig zu halten. Leider gibt es keine direkte Möglichkeit, Einfluss auf die Gestaltung der Imageschichten zu nehmen. Schön wäre es hier, eine Möglichkeit der Klammerung zu haben, um z.B. alle Kommandos innerhalb eines BEGIN .... COMMIT Blockes zu einer einzigen Schicht zusammenführen zu können.

Sehen Sie auch: Im Gespräch mit Docker – Was die Zukunft der Container-Technologie bereit hält

Seit Docker 1.9 gibt es die Möglichkeit einer einfachen Parametrisierung innerhalb des Dockerfiles. Dazu werden im Dockerfile mit ARG Parameter deklariert, die dann im weiteren Verlauf des Dockerfiles bei bestimmten Anweisungen benutzt werden können.

Im folgenden Beispiel

ARG uid
USER ${uid:-jboss}
# ...

wird ein Parameter uid deklariert, der dann in der USER-Direktive benutzt wird. Dabei wird ein Default-Wert jboss verwendet, falls der Parameter von aussen nicht gesetzt wird.

Um den Parameter beim Bauen des Images zu setzen, verwendet man das Argument --build-arg:

docker build --build-arg uid=daemon ...

Zu beachten ist, dass Dockerfile-Parameter nicht beim Basis-Image (FROM) benutzt werden können, was die Verwendung von Docker-Parametern für bestimmte Einsatzszenarien limitiert. Später werden wir sehen, wie Templatesysteme diese Einschränkung aufheben und auch darüberhinaus mehr Flexibilität bieten.

Eine weitere Neuerung sind Multi-Stage Builds, die es seit Docker 17.06 gibt. Dabei können mehrere FROM-Direktiven in einem Dockerfile verwendet werden. Interessant sind Multi-Stage Builds vor allem, wenn man das Bauen der Applikation selbst mit der Image-Erstellung kombinieren möchte, sodass ein docker build auch gleich die Applikation selbst kompiliert.

Das Ganze lässt sich am besten in einem Beispiel verdeutlichen. In dem folgenden Dockerfile wird eine Go-Anwendung kompiliert und daraus ein Image gebaut:

FROM golang AS builder
WORKDIR /tmp
COPY app.go .
RUN go build app.go

FROM scratch
COPY --from=builder /tmp/app .
CMD ["./app"]

In der ersten FROM-Anweisung wird ein Image ausgewählt, dass einen Go Compiler enthält. Der Zusatz AS builder gibt diesem Image einen Namen, den wir weiter unten referenzieren. Nun wird die Anwendung kompiliert (RUN go build app.go), was in ein statisch gelinktes Programm /tmp/app mündet.

Das eigentliche Image wird nach dem letztem FROM erzeugt. Hier ist es das FROM scratch, was ein „nacktes“ Docker Image auswählt. Da unsere Anwendung komplett statisch kompiliert ist, benötigt es auch kein Betriebsystem als Basis-Image. Der Clou hier ist nun die COPY-Anweisung. Dabei wird über das --from=builder-Argument auf das vorherige Image referenziert und auf dessen Dateisystem zugegriffen. Somit enthält das finale Image nur das fertig kompilierte Programm, nicht jedoch die gesamte Go Toolchain aus dem golang-Image.

Diese Technik erlaubt es, sehr schlanke Images zu erzeugen und bei kompilierenden Programmiersprachen (Go, Java, C, C++, …) wird nicht die Toolchain inklusive Compiler und Buildsystem in das endgültige Image gepackt.

In einer der späteren Folgen sehen wir, wie dieses Docker Feature von Rocker, einem alternativen Build-System für Docker Images, inspiriert wurde.

Lesen Sie auch: Von Linux zu Docker: Die Grundlagen der Container-Technologie

docker commit

Neben der Image-Erzeugung mit Dockerfile gibt es noch die Möglichkeit, direkt aus einem Container ein Image zu erstellen:

docker commit 3e3d3cf39 redis-server:1.0

Dabei ist 3e3d3cf39 hier die ID eines gestoppten Containers. Dieser Container könnte im interaktiven Modus (z.B. docker run -it centos) gestartet und im Anschluss per Hand Redis installiert worden sein. Nach einem exit aus der Shell wird der Container beendet, der dann wiederum als Blaupause für andere Container dienen kann, indem er wie oben gezeigt zu einem Image comitted wird.

Diese Methode, wenn sie interaktiv genutzt wird, hat natürlich den großen Nachteil, dass das erzeugte Image nicht ohne Weiteres reproduzierbar ist. Auf jeden Fall ist im Gegensatz zur Dockerfile-Methode im Nachhinein nicht nachvollziehbar, wie das Image entstanden ist.

Daher ist docker commit auch zu Recht verpönt.

Wir werden jedoch in einer späteren Folge sehen, wie docker commit im Zusammenspiel mit einen Konfigurationsmanagementsystem durchaus Sinn machen kann.

Nachteile

Wie wir gesehen haben, ist es nicht schwierig, Docker Images zu bauen. Ein paar Einschränkungen gibt es aber dennoch:

  • Die Dockerfile Parametrisierung ist limitiert. Das Basis-Image kann nicht parameterisiert werden. Die Parametrisierung ist zudem auf einfache Variableninterpolation beschränkt./li>
  • Als Erweiterungsmechanismus steht nur eine lineare Vererbung von Docker Images zur Verfügung. Die Möglichkeit der Komposition (oder „includes“) zur Wiederverwendung von gemeinsamen Dockerfile-Blöcken fehlt./li>
  • Zwar kann man die Gestaltung der Docker-Schichten beeinflussen, jedoch gibt es keine feingranulare Möglichkeit, den Inhalt der Image-Schichten zu bestimmen. Ein bekanntes Problem ist zum Beispiel, dass das Kopieren von Dateien und eine gleichzeitige Änderung des Dateieigentümers nur mithilfe zweier (statt einer) Schichten der gleichen Größe möglich ist./li>
  • Die Dockerfile-Syntax war lange Zeit „eingefroren“, sodass Verbesserungen nur schwer zu realisieren waren. Diese Einschränkung hat sich glücklicherweise etwas gelockert, jedoch ist es immer noch ein langwieriges Unterfangen, Beschränkungen wie die oben beschriebene Datei-Ownership aufzulösen (man beachte, dass das Problem seit über drei Jahren bekannt ist)./li>
  • Schließlich kann Security ein Problem sein. Beim Bauen eines Containers laufen die Kommandos im Dockerfile mit Root-Rechten ab. Das passiert zwar innerhalb eines Containerkontextes, doch werden immer wieder Sicherheitslücken wie Dirty Cow entdeckt, bei dem aus einem Container entkommen werden kann.

Ausblick

In den folgenden Artikeln werden wir uns verschieden Ansätze anschauen, die sich diesen Limitierungen annehmen und weitere Features bieten. Die nächste Folge demonstriert, wie mit einer Templatelösung eine erweiterte Parametrisierung und Komposition von Dockerfiles emöglicht wird.

Verwandte Themen:

Geschrieben von
Roland Huß
Roland Huß
Roland Huß ist ein Software Engineer, der für Red Hat an fabric8, einer Microservices-Plattform für Kubernetes und OpenShift arbeitet. Er entwickelt seit fast 20 Jahren, hat aber niemals seine Wurzeln als Systemadministrator vergessen. Roland arbeitete aktiv in Open-Source-Projekten mit und betreut sowohl Jolokia, die JMX-HTTP Bridge als auch das populäre Docker-Maven-Plug-in fabric8io. Und er liebt Chilis.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.