Skip to main content
Bootstraping in Kubernetes Deployments

Kubernetes – Bootstrapping mit InitContainern

Diese Serie befasst sich mit dem Container Orchestrierungsframework Kubernetes (k8s) und dem Bootstrapping von Containern innerhalb eines Deployments. Viele Nutzer kommen bei der Verwendung von k8s schnell an den Punkt bei dem eine einfach Konfiguration mittels ConfigMaps nicht mehr praktikabel ist, da der initiale Zustand eines Deployments nicht mehr nur über Schlüssel-Wert-Paare definiert werden kann. Auch große Datenmengen zur Initialisierung wollen nicht in Images verpackt werden und blähen eine Docker-Registry nur unnötig auf. Gerade im Bereich des CI/CD Workflows können solche Datenmengen schnell zu Problemen führen und laufende Prozesse bremsen oder gar behindern. Hierfür bietet k8s die sogenannten InitContainers an. Im Folgenden wird darauf eingegangen, wie diese dazu genutzt werden können, um komplexe, initiale Zustände eines Deployments herzustellen.

Was sind InitContainer?

InitContainer sind Container, die in einem Pod als erstes ausgeführt werden und die Pod-Umgebung vorbereiten. Erst wenn alle InitContainer erfolgreich und in der definierten Reihenfolge durchgelaufen sind, starten die restlichen Container des Pods. Zur Erinnerung: Ein Pod in k8s ist eine logische Hülle für eine Menge von Containern. Stark vereinfacht gesagt, verhält sich diese Hülle wie ein virtueller Server. Alle Container laufen in dieser Hülle als einzelne Prozesse, die aber dasselbe Netzwerk, Dateisystem und diesselben Umgebungsvariablen nutzen. Ein InitContainer ist ein vorgelagerter Prozess, der diese Hülle manipulieren und vorbereiten kann. Dieser Zustand bleibt auch nach seiner Lebenszeit erhalten bis alle anderen regulären Container beendet worden sind und/oder der Pod neugestartet wird.

Wie benutzt man InitContainer?

Anhand eines kurzen Beispiels wird hier die Verwendung eines InitContainers demonstriert, um eine Datei aus dem Internet herunterzuladen und an einen vordefinierten Ort zu legen.

Der folgende Code dient als unsere Ausgangsbasis:

Beispiel 1
# Apache HTTP Server
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
    app: init-container-example-1
name: init-container-example-1
spec:
replicas: 1
selector:
    matchLabels:
    app: init-container-example-1
template:
    metadata:
    labels:
        app: init-container-example-1
    spec:      
    containers:     
    - image: httpd:2.4
        imagePullPolicy: Always
        name: httpd       
        resources:
        requests:
            memory: 50Mi
            cpu: 0.1
        limits:
            memory: 50Mi
            cpu: 0.1
        volumeMounts:
        - mountPath: /srv
            name: shared-folder
    volumes:
        - name: shared-folder
        emptyDir: {}
---
# Service to acces via NodePort
apiVersion: v1
kind: Service
metadata:
name: init-container-example-1-nodeport
spec:
ports:
    - name: httptcp80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
    app: init-container-example-1
type: NodePort

In dieser Deployment-Konfiguration wird ein Apache Http Server deployt. Der Server liefert aus seinem htdocs-Verzeichnis in seinem Dateisystem eine index.html Datei aus. In der Basis Version finden sich dort lediglich die Worte „It works!“, wenn man auf ihn über den freigegebenen NodePort zugreift. Im Folgenden wird diese Datei durch einen InitContainer manipuliert, indem sie mit einer anderen Datei überschrieben wird.

Beispiel 2
# Apache HTTP Server
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
    app: init-container-example-1
name: init-container-example-1
spec:
replicas: 1
selector:
    matchLabels:
    app: init-container-example-1
template:
    metadata:
    labels:
        app: init-container-example-1
    spec:   
    initContainers:
    - image: busybox:1.31
        name: download-stopwords-txt
        command: 
        - sh
        - -c
        - |
            #!/usr/bin/env bash -e
            mkdir -p /usr/local/apache2/htdocs
            wget -O /usr/local/apache2/htdocs/index.html \
            "https://gist.githubusercontent.com/sebleier/554280/\ raw/7e0e4a1ce04c2bb7bd41089c9821dbcf6d0c786c/\ NLTK's%2520list%2520of%2520english%2520stopwords"
        volumeMounts:
        - mountPath: /usr/local/apache2/htdocs
            name: shared-folder    
    containers:     
    - image: httpd:2.4
        imagePullPolicy: Always
        name: httpd       
        resources:
        requests:
            memory: 50Mi
            cpu: 0.1
        limits:
            memory: 50Mi
            cpu: 0.1
        volumeMounts:
        - mountPath: /usr/local/apache2/htdocs
            name: shared-folder
    volumes:
        - name: shared-folder
        emptyDir: {}
---
# Service to acces via NodePort
apiVersion: v1
kind: Service
metadata:
name: init-container-example-1-nodeport
spec:
ports:
    - name: httptcp80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
    app: init-container-example-1
type: NodePort

Unter dem Abschnitt initContainers wird jetzt ein Container mit einem buysbox Image gestartet. Dieser kleine Werkzeugkasten enthält viele nützliche Tools und unter anderem auch wget, womit in unserem Beispiel eine Stoppwort Liste aus dem Internet heruntergeladen und unter /usr/local/apache2/htdocs/index.html gespeichert wird. Dafür muss in dem Pod ein Volume angelegt werden auf das beide Container zugreifen können. Wichtig hierbei ist, dass alle Verzeichnisse, die in dem Apache Http Container später verfügbar sind, nicht zwingend vorab zur Laufzeit der InitContainer vorhanden sein müssen. Daher muss an dieser Stelle immer darauf geachtet werden, dass die Dateisystemstrukturen entsprechend im InitContainer erzeugt werden. Wenn danach der reguläre Container des Pods hochfährt, übernimmt er durch die Definition des Volumes diesen Pfad in sein Dateisystem und überschreibt seine lokalen Daten mit den Dateien aus dem Volume.

Nach der Aktualisierung des Deployments wird man jetzt unter dem NodePort des Deployments nicht mehr die „It works!“-Webseite finden, sondern eine Textdatei mit Stoppworten.

Und wobei hilft uns das jetzt genau?

Das obige Beispiel ist natürlich trivial und veranschaulicht nur den groben Ablauf und die Funktionsweise von InitContainern. Viel wichtiger wird diese Methode bei der Initialisierung von Deplyoments, die große binäre Daten zur Laufzeit benötigen, wie beispielsweise trainierte Modelle oder vorberechnete Datenbank-Dumps. Diese Daten sollen zum einen nicht mit der Software gekoppelt werden und zum anderen, wie bereits zuvor erwähnt, kann es schädlich für den CI/CD Workflow sein, solche Daten in Images oder dergleichen unterzubringen. Hier können schnell Terabytes an redundanten Daten anfallen, die dann unwiederbringlich mit dem Release einer Softwareversion verschmolzen werden und alle nachfolgenden Deploymentprozesse ausbremsen.

Viel eleganter ist das Beziehen dieser Daten zur Zeit des Deployments von den Servern, auf denen dann diese Softwareprodukte laufen. Damit können für unterschiedliche Deployments verschiedene Versionen der Daten bereitgestellt und auch ohne viel Aufwand getauscht und aktualisiert werden. Außerdem leidet dann nicht die Performance der CI/CD-Umgebung darunter. Stattdessen können wir die entsprechenden Systeme verwenden, die für das Speichern, Versionieren und Publizieren von solchen Daten geeignet sind.

Nachfolgend ein etwas realistischeres Szenario, bei dem die Datei nicht einfach via wget heruntergeladen, sondern mittels git das zugehörige Repository geklont und dann die Datei kopiert wird.

Beispiel 3
# Apache HTTP Server
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
    app: init-container-example-1
name: init-container-example-1
spec:
replicas: 1
selector:
    matchLabels:
    app: init-container-example-1
template:
    metadata:
    labels:
        app: init-container-example-1
    spec:   
    initContainers:
    - image: alpine/git:1.0.7
        name: clone-stopwords-and-copy
        command: 
        - sh
        - -c
        - |
            #!/usr/bin/env bash -e
            mkdir -p /usr/local/apache2/htdocs
            git clone https://github.com/stopwords-iso/stopwords-en.git
            cd stopwords-en
            git checkout v0.3.0
            cp stopwords-en.txt /usr/local/apache2/htdocs/index.html
        volumeMounts:
        - mountPath: /usr/local/apache2/htdocs
            name: shared-folder    
    containers:     
    - image: httpd:2.4
        imagePullPolicy: Always
        name: httpd       
        resources:
        requests:
            memory: 50Mi
            cpu: 0.1
        limits:
            memory: 50Mi
            cpu: 0.1
        volumeMounts:
        - mountPath: /usr/local/apache2/htdocs
            name: shared-folder
    volumes:
        - name: shared-folder
        emptyDir: {}
---
# Service to acces via NodePort
apiVersion: v1
kind: Service
metadata:
name: init-container-example-1-nodeport
spec:
ports:
    - name: httptcp80
    port: 80
    protocol: TCP
    targetPort: 80
selector:
    app: init-container-example-1
type: NodePort

Wie oben zu sehen, wird jetzt ein entsprechendes Image verwendet, um eine Git-Kommandozeile zur Verfügung zu haben. Auf diesem Weg wird dann das Repository geklont, ein Release-Branch ausgecheckt und dann die entsprechende Datei kopiert.

Das lässt sich natürlich noch verfeinern. Zum Beispiel mittels ConfigMaps mit Variablen zur Generalisierung des Deployments für weitere Anwendungsszenarien. Oder aber auch durch die Erstellung eigener Images zur Standadisierung von häufigen Arbeitsabläufen während des Deployments in der eigenen Betriebsumgebung.

Zusammenfassung

InitContainers sind hilfreiche Tools, um kleinere Aufgaben zu erledigen, bevor der Hauptprozess eines Pods startet. Theoretisch kann man in InitContainern alles mögliche anstellen, da man die komplette Freiheit hat, die Container mit sich bringen. Git und wget sind hier nur zwei beispielhafte Demonstrationen.

Wir nutzen InitContainers sehr häufig. Dank dieser mächtigen Werkzeuge lassen sich viele Schritte in Kubernetes einfacher abbilden als beispielsweise in Docker Swarm.

Wie gehts weiter?

In dem nächsten Artikel dieser Reihe wird das Thema vertieft und in Richtung Cloud-Infrastrukturen bewegt. Im Fokus werden dann Speicherdienste stehen, wie beispielsweise Amazon S3. Gemeinsam werden wir erarbeiten, wie eine Initialisierung über S3-Buckets aussehen kann.

content by Timm Kißels

Interesse geweckt? Dann lass uns sprechen.
Folge uns auf