Portfolio |

Der Weg zum eigenen Container-Image

Der vierte Blogbeitrag unserer Reihe „Docker vs. OpenShift“ widmet sich den Container-Images. Wir zeigen euch, woher diese Images kommen und was getan werden muss, um individualisierte Laufzeit-Umgebungen zur Verfügung zu stellen.

Was ist ein Container-Image?

Die Definition von docker.com beantwortet diese Frage wie folgt: Ein Container-Image ist ein leichtgewichtiges, ausführbares Softwarepaket, welches alle zur Ausführung notwendigen Artefakte enthält. Dies beinhaltet Code, Laufzeitumgebungen, Systemwerkzeuge und -bibliotheken sowie Konfigurationen und Einstellungen.

Sobald ein Container-Image zur Ausführung gebracht wird, sprechen wir von Containern.

Der Vorteil eines Container-Images besteht  in der einfachen Ausführbarkeit und der Paketierung aller notwendigen Informationen in das Image. Weitere Installationsschritte oder ähnliches sind zum Start des Containers also nicht vorgesehen.

Weit verbreitete und oft genutzte Images sind über Registry-Dienste wie DockerHub, Quay.io oder die RedHat-Registry downloadbar. Jedoch kann im Regelfall nicht jedes dieser Images 1:1 in jeder Organisation genutzt werden – sei es wegen spezieller Einstellungen oder weil eigene Software geschrieben wird. Es ist also notwendig, eigene Images zu erstellen. Im Rahmen dieses Beitrags wollen wir zeigen, wie Container-Images sowohl mit Docker als auch in OpenShift-Umgebungen erzeugt werden können.

Dabei wird als einfaches Beispiel jeweils ein Webserver (httpd) mit eigenem Inhalt auf der Startseite erstellt.

Docker build

Docker bietet eine Build-Engine, um neue Container-Images zu erstellen. Dreh- und Angelpunkt der Engine ist das sogenannte Dockerfile. In Dockerfiles werden alle Anweisungen zur Erstellung eines neuen Images beschrieben. Dazu bedient sich Docker einer einfachen Syntax.

Jede Zeile eines Dockerfiles beschreibt einen auszuführenden Befehl nach folgendem Aufbau:

BEFEHL argumente

Der dabei wichtigste Befehl eines Dockerfiles ist die Benennung des Basis-Images. Dieses beschreibt, welche Software gegebenenfalls bereits vorinstalliert sein soll beziehungsweise ob sogar auf einem fertigen Applikations-Image aufgesetzt werden kann.

Im Beispiel unseres einfaches Webservers lautet der Befehl

FROM quay.io/centos7/httpd-24-centos7:2.4

Hier kann jedes Image aus der angegebenen Registry (in diesem Fall quay.io) angegeben werden. Es ist empfehlenswert, stets den gesamten Pfad sowie das Versions-Tag zu pflegen, um maximale Wiederholbarkeit auf unterschiedlichen Systemen sicherzustellen.

Nachdem das Basis-Image definiert wurde, können die eigentlichen Build-Schritte durchgeführt werden.

Der Schrittaufbau bleibt dabei stets gleich. Übliche Schritte sind:

USER <Benutzername>
RUN <Kommando>
COPY <Datei/Ordner im Host-System> <Datei/Ordner im Container-Dateisystem>
EXPOSE <Netzwerk-Port>
ENTRYPOINT <Kommando>
ENV < Umgebungsvariablen-Definition>

Der Befehl USER kann zum Beispiel genutzt werden, um nachfolgende Befehle unter einem bestimmen Benutzer (z.B. root) durchzuführen. Es ist allerdings empfehlenswert, vor der Definition eines Entrypoints bzw. am Ende des Dockerfiles zu einem Nicht-Root-Benutzer zu wechseln.

Der Befehl RUN führt Befehle in der Container-Shell aus und wird in der Regel dafür genutzt, die eigentlichen Installationsschritte und Einrichtungen vorzunehmen. Als allgemeine Regel lässt sich sagen, dass alle Schritte, welche zur Installation einer Software im Host-System notwendig wären, durch RUN-Statements in ein Container-Image geladen werden können.

COPY kopiert Dateien oder ganze Ordnerstrukturen vom Host-System (also dem System auf dem Docker läuft) in das Container-Image. Der Befehl wird dazu verwendet, Konfigurations-Dateien vom Entwicklungssystem im eigentlichen Image zu hinterlegen. Dies können sowohl Text- (Konfigurationen) als auch Binärdateien (z.B. die paketierte Anwendung) sein.

EXPOSE stellt sicher, dass benötigte Netzwerk-Ports innerhalb des Containers erreichbar sind.

Neben dem FROM-Befehl ist der ENTRYPOINT-Befehl wohl der zweitwichtigste. Er definiert, welcher Befehl durchgeführt werden soll, sobald das Image zur Ausführung gebracht wird. Hier wird festgelegt, wie zum Beispiel ein Server gestartet wird.

Als Beispiel-Referenz kann das Dockerfile für den httpd-Server unter https://github.com/docker-library/httpd/blob/master/2.4/Dockerfile gefunden werden.

Für diesen Artikel soll es jedoch reichen, eine Willkommens-Nachricht auf dem Server anzeigen zu lassen. Dazu wird eine Datei namens Dockerfile benötigt. Der minimale Inhalt der Datei ist die Definition des Basis-Images:

Nachdem die Datei gespeichert wurde, kann ein neues Image durch den Befehl

$ docker build -t my-own-httpd

im Ordner des Dockerfiles erzeugt werden.

Die neue Version des Images wird als my-own-httpd:latest in der lokalen Registry abgelegt und durch

$ docker run—rm  -p 8080:8080 my-own-httpd

zur Ausführung gebracht.

Um eigene Inhalte auf dem Webserver zur Verfügung zu stellen, müssen die entsprechenden Dateien aus dem Host-System in das Container-Image kopiert werden. Dazu wird, wie oben erläutert, der COPY-Befehl genutzt:

httpd erwartet die statischen HTML-Seiten im Ordner /var/www/html. Auf dem Hostsystem liegen die Dateien neben dem Dockerfile hier examplarisch in einem Ordner website. Um eine eigene Startseite anzuzeigen, wird eine Datei index.html im Ordner website angelegt und mit gültigem HTML-Code gefüllt:

Nachdem beide Dateien gespeichert wurden, werden sowohl der docker build-Befehl als auch der docker run-Befehl erneut durchgeführt. Nach dem Aufruf der resultierenden Website ist zu sehen, dass der neue Inhalt verfügbar ist.

OpenShift Build-Verfahren

Wie bereits im zweiten Teil dieser Reihe angesprochen, kann auch OpenShift Build-Schritte durchführen. Die Plattform bietet dafür diverse Verfahren. Im Rahmen dieses Beitrags wollen wir uns jedoch auf die zwei hauptsächlich verwendeten Verfahren konzentrieren:

  • Docker Builds
  • Source-to-Image Builds (S2I)

Das Docker-Buildverfahren erstellt dabei, auf ähnliche Art wie oben beschrieben, ein neues Image auf Basis eines Dockerfiles in einen so genannten Image-Stream.

Um ein neues Docker Build anzulegen, wird folgender Aufruf benötigt

$ oc new-build  https://github.com/proficomag/container-build-examples.git \
--context-dir='docker-build' \
--strategy='docker' \
--name='my-own-h

Das Argument –strategy legt dabei fest, dass der Build per Docker-Verfahren durchgeführt werden soll. Für die genutzte Code-Basis ist darauf zu achten, dass OpenShift ein zentrales System, unabhängig von der eigenen Entwicklungs-Umgebung, ist. Daher muss der Source-Code, in unserem Fall das Dockerfile und die index.html, in einem für OpenShift erreichbaren Sourcecode-Repository liegen.

Nachdem der Befehl durchgeführt wurde, sieht man im Log, dass zwei Imagestreams und eine Build-Configuration angelegt wurden.

Die Build-Configuration legt automatisch einen neuen Job an, dessen Logs über

$ oc logs my-own-httpd-1-build

eingesehen werden können.

Das fertige Image („my-own-httpd“) kann jetzt, wie im zweiten Teil dieser Reihe erläutert, deployed und veröffentlicht werden:

$ oc new-app --image-stream='my-own-httpd' --name='httpd-oc'

Nachdem der Service über eine Route veröffentlicht wurde, kann die Website durch die freigegebene URL aufgerufen werden:

OpenShift Imagestreams

Eine OpenShift-Plattform beinhaltet je Projekt in der Regel eine eigene Container-Registry. Die Container-Images innerhalb dieser Registry werden durch so genannte Imagestreams adressiert. Imagestreams liefern eine Möglichkeit, ein einfaches Output-Format für Build-Jobs sowie Input-Formate für die eigentlichen Deployments zu nutzen. Es ist nicht notwendig, eine externe Registry anzubinden, sondern OpenShift nutzt dafür interne Ablage- und Datenstrukturen.

Deployments können dabei so eingerichtet werden, dass diese bei einer Änderung der Imagestreams, also des zugrundeliegenden Container-Images, automatisch Updates beziehen. Diese Möglichkeiten werden in einem weiteren Artikel dieser Reihe genauer behandelt.

Über die Kommandozeile können die Imagestreams eines Projektes einfach durch

$ oc get imagestreams

aufgelistet werden.

Source-to-Image Builds

Neben der Docker Build-Strategy liefert OpenShift ein weiteres Verfahren: Source-to-Image (S2I). Dieses Verfahren erlaubt es, Container-Images ohne ein zusätzliches Dockerfile zu erstellen. Bedingung dafür ist, dass der eigentliche Source-to-Binary Build vergleichsweise einfach gestaltet ist.

Wie bereits aufgezeigt, können S2I-Builds durch den oc new-app Befehl durchgeführt werden. Im Sinne der Nachvollziehbarkeit werden die im Hintergrund erzeugten Schritte geteilt.

Auch bei S2I muss zuerst eine Build-Configuration angelegt werden

$ oc new-build quay.io/centos7/httpd-24-centos7:2.4~ github.com/proficomag/container-build-examples.git
--context-dir='s2i'
--strategy='source'
--name='s2i-httpd'

Der Output dieses Befehls ähnelt dem Output der Docker-Strategie:

Ein Blick in das Log des Build-Jobs verrät, dass diverse Schritte des zugrundeliegenden Builder-Images (in diesem Fall httpd) durchgeführt wurden

$ oc logs s2i-httpd-1-build

Nachdem das hier erzeugte Container-Image installiert und die Route eingerichtet wurde, kann die fertige Website aufgerufen werden.

$ oc new-app --image-stream='s2i-httpd' --name='httpd-s2i'
$ oc expose svc/httpd-s2i

Um das Projekt wieder aufzuräumen können alle Artefakte per

 oc delete all --all

gelöscht werden.
 

Dieser Beitrag hat gezeigt, wie es sowohl in einer nativen Docker-Umgebung als auch in OpenShift möglich ist, individuelle Software zu paketieren und lauffähig zu machen. Duch den Einsatz von Dockerfiles ist es möglich, die Basis-Images der genutzten Software auf die eigenen Bedarfe anzupassen und zu konfigurieren.

Source-to-Image Builds helfen dabei, eigene Software einfach und unabhängig von Entwickler-Umgebungen zu erstellen und auszurollen.

In den kommenden Wochen werden wir unter anderem zeigen, wie Container untereinander kommunizieren können und wie es möglich ist, auch innerhalb von Container-Infrastrukturen eine dauerhafte Datenhaltung zu garantieren.

Weiterführende Links

Dockerfile Referenz: https://docs.docker.com/engine/reference/builder/

Openshift Build-Strategien: https://docs.openshift.com/container-platform/4.7/cicd/builds/build-strategies.html