Kubernetes – Funktioniert das wirklich?

Kubernetes ist in aller Munde – und das nicht erst, seit Docker die native Unterstützung von Kubernetes als Orchestrierungstool neben Docker-Swarm angekündigt hat. Trotzdem scheint Kubernetes für viele noch ein Buch mit sieben Siegeln zu sein. Oft höre ich Fragen wie "Habt ihr das im Einsatz?" oder "Funktioniert das wirklich?".
Anwendungen in Container zu packen und dort laufen zu lassen, hat sich mittlerweile in der Entwicklergemeinde etabliert. So lässt sich schnell mal eine Datenbank hochfahren oder ein Webserver starten, mit dem die lokale Entwicklung getestet werden kann. Sobald aber mehrere Anwendungen in Docker gestartet werden oder wenn man gar eine Anwendung mehrfach redundant starten will und diese Anwendungsinstanzen miteinander kommunizieren müssen, stellt sich die Frage, wie man die unterschiedlichen Anwendungen für den Test konfiguriert und orchestriert.
In diesem Artikel zeige ich, wie eine Anwendung aus drei Komponenten, die auf unterschiedlichen Technologien basieren, in Kubernetes konfiguriert, ausgerollt und skaliert wird.
Voraussetzungen
Um die Beispiele mit Kubernetes [1] nachzuvollziehen, sind eine Kubernetes-Installation und eine lokal installierte kubectl-Anwendung notwendig.
Die verwendeten Kubernetes-Konfigurationen sowie der komplette Programmcode für die Beispielanwendungen sind auf GitHub [2] verfügbar. Die Docker-Images mit den Anwendungen sind im offiziellen Docker-Repository abgelegt und werden von den Beispielkonfigurationen verwendet. Dadurch können die Beispiele auch in Kubernetes getestet werden, ohne dass die Anwendungen lokal gebaut und in Docker-Images gepackt werden müssen.
Auf das Erstellen der Anwendungen sowie der Docker-Images gehe ich in diesem Artikel nicht weiter ein. Das Quellcode-Repository enthält sowohl auf der obersten Ebene als auch in den Unterverzeichnissen für die einzelnen Anwendungen jeweils detaillierte Anweisungen in den readme.md-Dateien.
Grundlagen
Docker
Docker soll in diesem Artikel nur so weit beschrieben werden, wie es für das Verständnis von Kubernetes notwendig ist. Docker bietet die Möglichkeit, Prozesse wie z. B. Webserver, REST-Services oder Datenbanken in einer beschränkten Umgebung ablaufen zu lassen. Beschränkt bedeutet hier, dass ein Prozess im Container die anderen Prozesse außerhalb nicht sieht, seine eigene IP-Adresse hat und sein eigenes Filesystem. Definiert wird diese Containerumgebung in einem Docker-Image, welches in einer Docker-Registry wie z. B. hub.docker.com[3] zur Verfügung gestellt wird.
Während ein Docker-Image die Umgebung für einen Prozess beschreibt, ist ein Docker-Container quasi eine lebende Instanz dieses Images. Beim Start eines Container verwendet der Docker-Service die Beschreibung im Docker-Image, um den gewünschten Prozess zu starten und ihn in den Grenzen auszuführen (Dateisystem, Ports etc.), die durch das Docker-Image vorgesehen sind.
Kubernetes
Kubernetes ist ein Tool zur Orchestrierung von Anwendungen, welche in Containern (meistens Docker) ausgeführt werden, auch über verschiedene Rechnerknoten hinweg. In diesem Abschnitt möchte ich die wichtigsten Konzepte von Kubernetes vorstellen.
Label
Label werden in Kubernetes bei fast allen Komponenten verwendet. Ein Label ist ein key-value-Paar, wobei sowohl der key als auch der value frei gewählt werden können. Beispiele für Label sind app=myservice oder environment=test.
Nodes
Nodes oder Knoten sind die Rechner, auf denen Kubernetes installiert wird, und die zusammen den Kubernetes-Cluster bilden. Dabei spielt es keine Rolle, ob es sich hierbei um physikalische Rechner oder um virtuelle Instanzen handelt, die z. B. von einem Cloud-Provider zu Verfügung gestellt werden. Nodes können im laufenden Betrieb zu einem Kubernetes-Cluster hinzugefügt werden, ebenso ist es möglich, Nodes aus einem Kubernetes-Cluster im laufenden Betrieb zu entfernen.
Pod und Deployment
Die grundlegende Einheit im Kubernetes-Universum ist ein "Pod". Ein Pod enthält im Normalfall einen Docker-Container. Es gibt zwar auch Pods, welche mehr als einen Container enthalten, aber dies sind Spezialfälle, die hier nicht weiter berücksichtigt werden sollen.
Ein Pod hat eine eigene IP-Adresse, unter der er im Cluster erreichbar ist, er hat einen Namen und er wird mit Hilfe von Labeln gekennzeichnet. Man kann sich einen Pod vorstellen wie einen eigenen kleinen Rechner, auf dem nur ein einzelner im Docker-Container spezifizierter Prozess läuft.
Der Anwender erzeugt im Normalfall die Pods nicht selber, sondern er erzeugt ein "Deployment", welches die Beschreibung des Pods enthält sowie zusätzliche Angaben wie z. B. die Anzahl der Pods, die gleichzeitig laufen sollen (Skalierung der Pods). Das Deployment startet die gewünschte Anzahl Pods und überwacht diese. Sollte eine Anwendung in einem Container z. B. abstürzen und damit den Container und den Pod beenden, sorgt das Deployment wieder dafür, dass ein neuer Pod gestartet wird und somit wieder die gewünschte Anzahl an Pod-Instanzen zur Verfügung steht.
Für die Pods bzw. Container können – und sollen – Beschränkungen für CPU und Speicherbedarf definiert werden, damit zum einen Kubernetes die Pods auf den Nodes laufen lassen kann, auf denen noch genügend Ressourcen zur Verfügung stehen, und zum anderen können Pods, die mehr als das erlaubte Limit an Ressourcen verbrauchen, gestoppt und neu gestartet werden.
Service
Während ein Deployment dafür sorgt, dass die Pods in der gewünschten Anzahl im Cluster laufen, gibt es noch keine Möglichkeit, auf diese Pods zuzugreifen. Zwar hat jeder Pod eine IP-Adresse, aber es gibt für den Anwender noch keine Komponente, welche ihm den Zugriff auf die Pods ermöglicht. Diese Funktionalität übernehmen in Kubernetes die Services.
Ein Service hat einen Namen und eine Konfiguration, die ihm mitteilt, welche Pods ihm zugeordnet sind. Diese Zuordnung geschieht über die Label, die an die Pods konfiguriert wurden. Es gibt drei wichtige Arten von Services:
- cluster-interne Loadbalancer: Dieser Service besitzt eine eigene IP-Adresse. Er kann über seinen Namen im DNS gefunden werden und routet den Netzwerkverkehr an die Pods weiter, für die er zuständig ist.
- headless-Service: Dieser Service hat keine eigene IP-Adresse, eine DNS-Anfrage nach seinem Namen liefert alle IP-Adressen der zum Service gehörenden Pods zurück.
- globale Loadbalancer: Diese Services haben zum einen die gleiche Funktionalität wie cluster-interne Loadbalancer-Services, sind aber auch von außerhalb des Clusters über eine vom Cloudprovider zur Verfügung gestellte Adresse erreichbar.
ConfigMaps und Secrets
Eine ConfigMap ist eine Sammlung von Key-Value-Werten, die z. B. als Environment-Variable oder als Konfigurationsdatei in den Pods verwendet werden können. Secrets bieten die gleiche Funktionalität, werden aber kodiert in Kubernetes gespeichert. In den Beispielanwendungen erläutere ich die genaue Verwendung beider Typen.
Die Beispielanwendung
Als Beispiel dient eine Anwendung, die es ermöglicht, Dokumente zu speichern und später zu durchsuchen. Ein Dokument hat in diesem Kontext lediglich zwei Eigenschaften: einen Titel und den Inhalt. Die Anwendung besteht aus drei Komponenten, die mit unterschiedlichen Technologien implementiert sind:
Das Frontend ("documentui") ist eine mit Angular 5 geschriebene Webanwendung, welche über einen in JavaScript geschriebenen Server mit node.js ausgeliefert wird. In diesem Frontend gibt es drei über ein Menü erreichbare Bereiche: eine filterbare Liste aller Dokumente, eine Detailansicht und ein Bereich zum Hinzufügen neuer Dokumente.
Die Webanwendung enthält keinerlei fachliche Logik, sie dient nur der reinen Repräsentation der Daten und der Nutzerinteraktion. Der in JavaScript geschriebene Server, welcher die Anwendung zur Verfügung stellt, dient auch als Proxy für den Backend-REST-Service (unter dem URL-Pfad /api/). Über diesen Pfad greift zum einen die Webanwendung auf den Service zu, zum anderen kann hierüber mit einem Tool wie z. B. curl auch auf den REST-Service zugegriffen werden.
Die Kernanwendung ("documentservice"), welche die Dokumentdaten entgegennimmt, speichert und durchsucht, ist eine in Java geschriebene Anwendung, welche auf Spring-Boot basiert. Diese Anwendung stellt eine REST-API zur Verfügung.
Als dritte Komponente wird Elasticsearch für die Speicherung der Dokumente verwendet. Abb.1 zeigt die Listenansicht der Dokumente im Browser.
Deployment der Komponenten im Cluster
Start des Clusters
Für das Beispiel in diesem Artikel verwenden wir eine minikube-Installation [4]. Das genaue Vorgehen zur Installation von minikube ist in der verlinkten Dokumentation ausführlich beschrieben. Die aktuell verwendeten Versionen sind:
- minikube v0.23.0
- kubectl v1.8.3
- kubernetes v1.8.0
Damit im Beispiel der Cluster auch genug Hauptspeicher hat, um die Anwendungen skalieren zu können, starten wir ihn mit 4GB Speicher, ferner aktivieren wir das von minikube mitgelieferte heapster-Addon, um den Ressourcenverbrauch der einzelnen Komponenten im Dashboard anzeigen zu können:
$ minikube start --vm-driver virtualbox --memory 4096 Starting local Kubernetes v1.8.0 cluster... Starting VM... Getting VM IP address... Moving files into cluster... Setting up certs... Connecting to cluster... Setting up kubeconfig... Starting cluster components... Kubectl is now configured to use the cluster. $ minikube addons enable heapster heapster was successfully enabled
Der erfolgreiche Start können wir mit minikube status überprüfen. Für den Rest dieses Artikels verwenden wir keine minikube-Kommandos mehr, sondern nur kubectl-Kommandos, damit die Beispiele auch auf anderen Kubernetes-Installationen nachvollziehbar sind:
$ kubectl cluster-info Kubernetes master is running at 192.168.99.100 To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'
Das Kubernetes-Dashboard stellen wir durch den Befehl kubectl proxy & auf dem lokalen Rechner unter der Adresse http://127.0.0.1:8001/ui zur Verfügung. Hier können wir uns einen Überblick über die in Kubernetes laufenden Komponenten und z. B. auch über die Auslastung der Kubernetes-Nodes anzeigen lassen (s. Abb.2).
Elasticsearch
Die erste Komponente, die wir im Kubernetes-Cluster installieren, ist der Service, der den Zugriff auf die Elasticsearch-Pods ermöglicht. Dieser Service wird vor dem Deployment installiert, da in der Konfiguration der Pods auf diesen Service verwiesen wird, schließlich müssen die Elasticsearch-Instanzen sich ja gegenseitig finden, um einen Elasticsearch-Cluster zu bilden, und hierfür wird der Service verwendet.
Hier nun die Definition des Service in der Datei elasticsearch-service.yaml:
# (1) kind: Service apiVersion: v1 metadata: name: elasticsearch spec: # (2) selector: app: elasticsearch # (3) type: ClusterIP clusterIP: None # (4) ports: - name: cluster port: 9300 targetPort: 9300
- Die Konfiguration beschreibt einen Service, der den Namen "elasticsearch" erhält.
- Der Selector gibt an, dass dieser Service alle Pods zur Verfügung stellt, welche ein Label mit dem Key app und dem Value elasticsearch haben.
- Der Service ist nur innerhalb des Kubernetes-Clusters verfügbar, er hat aber keine eigene IP-Adresse, sondern stellt als headless-service über den cluster-internen Nameserver die IP-Adressen der Pods zu Verfügung.
- Die Elasticsearch-Knoten verwenden Port 9300 um miteinander zu kommunizieren, dieser Port wird auch im Service spezifiziert.
Durch das folgende Kommando installieren wir den Service in Kubernetes. Der anschließende Aufruf zum Anzeigen der vorhandenen Services zeigt den neu angelegten Service sowie den im Cluster vorhandenen Kubernetes API-Server (der in diesem Beispiel nicht verwendet wird).
$ kubectl apply -f elasticsearch-service.yaml service "elasticsearch" created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None <none> 9300/TCP 24s kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 1h
Als nächstes installieren wir das Deployment der Elasticsearch-Pods. Die dafür notwendige Konfiguration sieht wie folgt aus (elasticsearch-deployment.yaml):
# (1) kind: Deployment apiVersion: apps/v1beta2 metadata: name: elasticsearch spec: # (2) replicas: 2 # (3) selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch # (4) spec: # (5) initContainers: - name: sysctl-vm image: busybox command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: privileged: true # (6) containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:5.6.3 # (7) env: - name: cluster.name value: "es-cluster" - name: ES_JAVA_OPTS value: "-Xms300m -Xmx300m" - name: discovery.zen.ping.unicast.hosts value: "elasticsearch" - name: xpack.security.enabled value: "false" # (8) ports: - name: cluster containerPort: 9300 protocol: TCP # (9) resources: limits: memory: 350Mi cpu: 0.4 requests: memory: 250Mi cpu: 0.2 # (10) volumeMounts: - name: storage mountPath: /usr/share/elasticsearch/data # (11) volumes: - name: storage emptyDir: {}
- Es wird ein Deployment mit dem Namen elasticsearch angelegt.
- Das Deployment soll zwei Instanzen der spezifizierten Pods enthalten.
- Zu diesem Deployment gehören alle Pods, welche das Label app mit dem Wert elasticsearch haben; unter template wird dieses Label für die Pods spezifiziert. In unserem Beispiel sind die Einträge identisch, es kann aber auch komplexere Fälle geben, in denen der Selector zusätzliche Pods enthalten kann, dies wird hier nicht weiter berücksichtigt.
- Es folgt die Spezifikation des Pods.
- Dieser Pod enthält einen Docker-Container, der vor dem eigentlichen Container ausgeführt wird. Dies ist notwendig, da für Elasticsearch eine Systemvariable (vm.max_map_count) gesetzt werden muss. Genauere Info findet sich in der Elastic-Dokumentation [5]
- Der Pod enthält einen Docker-Container mit dem Namen elasticsearch, der Parameter image gibt an, von wo dieses Image zu laden ist, in diesem Fall aus der Docker-Registry von elastic.
- Beim Start des Containers werden die angegebenen Environment-Variablen gesetzt, hier wird in discovery.zen.ping.unicast.hosts als Wert der Name des Services übergeben, der im vorherigen Schritt im Kubernetes-Cluster angelegt wurde.
- Der Container stellt Port 9300 zur Verfügung, dies ist der Standardport von Elasticsearch für die Elasticsearch-Cluster-Kommunikation.
- Der Container wird auf einen Speicherverbrauch von 350 MB und einen CPU-Verbrauch von 0,4 CPUs beschränkt. Sollte er mehr Speicher verwenden, wird er von Kubernetes beendet und neu gestartet. Bei Verbrauch von mehr CPU wird er gedrosselt. Die Werte bei den requests verwendet Kubernetes, um zu prüfen, auf welchem Knoten noch Ressourcen frei sind, um den Pod dort laufen zu lassen.
- Unter dem angegebenen Pfad wird ein Volume zum Speichern der Daten in den Container eingebunden.
- Das in (10) verwendete Volume wird hier definiert.
Das Deployment rollen wir mit dem folgenden Befehl im Cluster aus:
$ kubectl apply -f elasticsearch-deployment.yaml deployment "elasticsearch" created
Nach kurzer Zeit sind die beiden Pods des Deployment verfügbar, was mit dem entsprechenden kubectl-Befehl überprüft werden kann:
$ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE elasticsearch 2 2 2 2 15m $ kubectl get pod -w NAME READY STATUS RESTARTS AGE elasticsearch-67d494cc79-brsgt 1/1 Running 0 2m elasticsearch-67d494cc79-kbm6r 1/1 Running 0 2m
An der Ausgabe sehen wir, dass die Pods einen Namen erhalten haben, der aus dem Namen des Deployments, der Kennung des ReplicaSets und einer Pod-Kennung zusammengesetzt ist. Auch im Dashboard können wir den Status des Deployment überprüfen (s. Abb.3).
Es kann übrigens durchaus ein paar Minuten dauern, bis die Pods im Status READY sind, da ja zuerst das Docker-Image in den Kubernetes-Cluster geladen werden muss. Bei Neustarts oder Erhöhen der Anzahl von Pods ist das natürlich nicht mehr notwendig.
REST-Service
Für den "documentservice" installieren wir als erstes ein Secret (documentservice-secret.yaml), welches die Anwendungskonfiguration enthält:
# (1) kind: Secret apiVersion: v1 metadata: name: config-documentservice # (2) stringData: application.yaml: | spring: data: elasticsearch: cluster-nodes: "elasticsearch:9300"
- Das Secret wird in Kubernetes mit dem Namen config-documentservice angelegt.
- Es enthält einen Eintrag mit dem key application.yaml. Der value dieses Eintrages ist der Inhalt der Spring-Boot-Konfiguration für die Anwendung.
Die Installation führen wir wie schon gewohnt mit kubectl apply -f durch:
$ kubectl apply -f documentservice-secret.yaml secret "config-documentservice" created
Bei der Beschreibung der Deployment-Konfiguration (documentservice-deployment.yaml) erläutere ich nur die Elemente genauer, welche bisher noch nicht beschrieben wurden:
kind: Deployment apiVersion: apps/v1beta2 metadata: name: documentservice spec: replicas: 1 selector: matchLabels: app: documentservice template: metadata: labels: app: documentservice spec: containers: - name: documentservice image: codecentric/informatik-aktuell-usingk8s-documentservice:1.0 env: - name: JAVA_OPTS value: "-Xmx128m" - name: SPRING_CONFIG_LOCATION value: "file:/config/" ports: - name: api containerPort: 8080 protocol: TCP resources: limits: memory: 300Mi cpu: 0.2 requests: memory: 100Mi cpu: 0.1 volumeMounts: - name: config-volume mountPath: /config # (1) volumes: - name: config-volume secret: secretName: config-documentservice
- Das Volume mit dem Namen config-volume, welches für diesen Pod spezifiziert wird, wird aus dem Secret config-documentservice erzeugt, das wir gerade installiert haben und wird im Container unter dem Pfad /config zur Verfügung gestellt. Im konkreten Fall führt das dazu, dass es im Container eine Datei /config/application.yaml gibt, welche den im Secret spezifizierten Inhalt hat. Und über den Environment-Eintrag SPRING_CONFIG_LOCATION instruieren wir die Spring-Boot Anwendung, diese Konfiguration zu verwenden.
Die Installation erledigen wir mit:
$ kubectl apply -f documentservice-deployment.yaml deployment "documentservice" created
Der Service für den documentservice konfigurieren wir mit der Datei documentservice-service.yaml:
kind: Service apiVersion: v1 metadata: name: documentservice spec: selector: app: documentservice # (1) type: ClusterIP ports: - name: api port: 8080 targetPort: 8080
- Im Gegensatz zum Service für elasticsearch wird hier durch Kubernetes eine IP-Adresse für den Service vergeben, da wir die ClusterIP "nicht" setzen.
$ kubectl apply -f documentservice-service.yaml service "documentservice" created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE documentservice ClusterIP 10.0.0.104 <none> 8080/TCP 19s elasticsearch ClusterIP None <none> <none> 2h kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 13h
Wählen wir im Dashboard den Service aus, sehen wir, dass der Service die IP-Adresse 10.0.0.104 hat und auf den Pod mit der IP 172.17.0.8 routet (die IP-Adressen sind je nach Installation unterschiedlich).
Webanwendung
Für die Webanwendung documentui gibt es ebenfalls drei Konfigurationsdateien. Die erste ist documentui-configmap.yaml, diese enthält die konfigurierbaren Werte für die Anwendung – dies ist nur ein Wert, der angibt, unter welchem Namen und Port der REST-Service erreichbar ist.
kind: ConfigMap apiVersion: v1 metadata: name: config-documentui data: apihost: documentservice:8080
Die zweite Datei für das Deployment der Webanwendung ist documentui-deployment.yaml:
kind: Deployment apiVersion: apps/v1beta2 metadata: name: documentui spec: replicas: 1 selector: matchLabels: app: documentui template: metadata: labels: app: documentui spec: containers: - name: documentui image: codecentric/informatik-aktuell-usingk8s-documentui:1.0 # (1) env: - name: API_HOST valueFrom: configMapKeyRef: name: config-documentui key: apihost ports: - name: server containerPort: 3000 protocol: TCP resources: limits: memory: 100Mi cpu: 0.2 requests: memory: 50Mi cpu: 0.05
- Neu in dieser Konfiguration ist, dass für die Environmentvariable API_HOST für den Container die zuvor installierte ConfigMap verwendet wird.
Den Service für die Webanwendung konfigurieren wir mit Hilfe der Datei documentui-service.yaml:
kind: Service apiVersion: v1 metadata: name: documentui spec: selector: app: documentui # (1) type: LoadBalancer # (2) ports: - name: api port: 80 targetPort: 3000
- Da die Webanwendung von außerhalb des Kubernetes-Clusters erreichbar sein soll, wird der Service als LoadBalancer definiert. Er hat damit eine cluster-interne IP, aber auch eine externe, welche vom Cloud-Provider zur Verfügung gestellt wird.
- Der Service stellt nach außen den Port 80 zur Verfügung, routet aber den Verkehr auf Port 3000 der Pods.
Alle drei Dateien installieren wir wie gehabt:
$ kubectl apply -f documentui-configmap.yaml configmap "config-documentui" created $ kubectl apply -f documentui-deployment.yaml deployment "documentui" created $ kubectl apply -f documentui-service.yaml service "documentui" created $ kubectl get po NAME READY STATUS RESTARTS AGE documentservice-665757d4c8-rjwwc 1/1 Running 0 15m documentui-6bc456ff4d-mtvf5 1/1 Running 0 1m elasticsearch-67d494cc79-brsgt 1/1 Running 0 27m elasticsearch-67d494cc79-kbm6r 1/1 Running 0 27m $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE documentservice ClusterIP 10.0.0.104 <none> 8080/TCP 8m documentui LoadBalancer 10.0.0.31 <pending> 80:32436/TCP 1m elasticsearch ClusterIP None <none> 9300/TCP 33m kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 47m
Bei der Anzeige der Services sehen wir, dass durch den Cloud-Provider noch keine externe IP zugewiesen wurde (PENDING). Bei minikube ist das auch nicht möglich, hier muss auf das Kommando minikube service zurückgegriffen werden:
$ minikube service documentui --url 192.168.99.100
Unter der angezeigten URL ist dann die Webanwendung verfügbar. Bei einem "echten" Cluster wäre die Anwendung unter http://EXTERNAL-IP:80 erreichbar.
Unser Kubernetes-Cluster sieht zur Zeit so aus (Abb.5 enthält nur die Services und Pods, die Deployments, welche die Pods verwalten sind nicht dargestellt, da sie für den Netzwerkverkehr keine Rolle spielen).
Ein Aufruf von außen mit dem Browser landet beim documentui-Service, dieser routet weiter an den documentui-Pod. Der Pod greift auf den documentservice-Service zu, der an den documentservice-Pod routet. Die gestrichelten Linien zu elasticsearch zeigen, dass der documentservice-Pod vom elasticsearch-Service die IP-Adressen der elasticsearch-Pods erhält und sich zu einem der elasticsearch-Pods verbindet.
Laden der Beispieldaten
Durch Aufruf der URL http://192.168.99.100:32436/api/documents/load – die IP-Adresse und der Port sind entsprechend anzupassen – laden wir die in der Anwendung vorhandenen Beispieldokumente in Elasticsearch. Dies können wir entweder im Browser oder mit curl erledigen:
$ curl 192.168.99.100/api/documents/load 5 Datensätze geladen.
Wenn wir die Anwendung im Browser aufrufen, sehen wir, dass in der Kopfzeile die Namen der Pods ausgegeben werden, welche die Anfrage verarbeitet haben (processed on documentservice-abc via documentui-xyz). An dieser Stelle können wir später sehen, wie Anfragen geroutet werden, wenn die Anzahl der Pods erhöht wird (s. Abb.6).
Skalieren der Deployments
Nach der Basisinstallation laufen insgesamt 4 Pods im Cluster:
$ kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE documentservice-665757d4c8-rjwwc 1/1 Running 0 42m 172.17.0.8 minikube documentui-6bc456ff4d-mtvf5 1/1 Running 0 27m 172.17.0.9 minikube elasticsearch-67d494cc79-brsgt 1/1 Running 0 53m 172.17.0.6 minikube elasticsearch-67d494cc79-kbm6r 1/1 Running 0 53m 172.17.0.7 minikube
Um die Verfügbarkeit und Performance unserer Anwendung zu erhöhen, skalieren wir die Webanwendung auf 3 Instanzen und den REST-Service auf 2 Instanzen:
$ kubectl scale deployment documentui --replicas=3 deployment "documentui" scaled $ kubectl scale deployment documentservice --replicas=2 deployment "documentservice" scaled $ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE documentservice-665757d4c8-rjwwc 1/1 Running 0 43m 172.17.0.8 minikube documentservice-665757d4c8-zcpsd 1/1 Running 0 4s 172.17.0.12 minikube documentui-6bc456ff4d-2hfc2 1/1 Running 0 10s 172.17.0.10 minikube documentui-6bc456ff4d-mtvf5 1/1 Running 0 28m 172.17.0.9 minikube documentui-6bc456ff4d-scszf 1/1 Running 0 10s 172.17.0.11 minikube elasticsearch-67d494cc79-brsgt 1/1 Running 0 55m 172.17.0.6 minikube elasticsearch-67d494cc79-kbm6r 1/1 Running 0 55m 172.17.0.7 minikube $ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE documentservice 2 2 2 2 43m documentui 3 3 3 3 29m elasticsearch 2 2 2 2 55m
Dieses Skalieren der Anwendungen geschieht im laufenden Betrieb, die neuen Pods werden von den Services erkannt und dem Loadbalancing hinzugefügt. Unser Kubernetes-Cluster sieht jetzt wie in Abb.7 dargestellt.
Wenn wir jetzt mit der Anwendung arbeiten, sehen wir durch Auswahl von Einträgen aus der Liste oder auch durch Hinzufügen eines neuen Eintrages, dass die Anfragen über die Frontend-Pods und Backend-Pods verteilt werden. Die als LoadBalancer arbeitenden Services verteilen die Aufrufe übrigens in der Default-Einstellung zufällig an die Pods, so dass durchaus auch mehrere Aufrufe hintereinander vom gleichen Pod bearbeitet werden.
Die Skalierung der Pods in einem Deployment können wir auch so definieren, dass die Anzahl der Pods dem aktuellen CPU-Verbrauch angepasst wird, das heißt, wenn die Container unter Last stehen, können automatisch neue hinzugefügt werden, und wenn die Last wieder sinkt, werden Pods wieder gelöscht. Das im Rahmen dieses Artikels zu demonstrieren geht jedoch zu weit, hier verweise ich auf die ausführliche Dokumentation von Kubernetes.
Wir können das Dashboard nutzen, um uns den Zustand der einzelnen Komponenten anzeigen zu lassen oder auch um einen allgemeinen Überblick zu bekommen (s. Abb.8).
Fazit
Wir haben in diesem Artikel gesehen, wie verschiedene Anwendungen, die mit unterschiedlichen Technologien entwickelt wurden, in einem Kubernetes-Cluster deployed werden und wie sie miteinander kommunizieren. Wir haben die Anwendungen im Kubernetes-Cluster durch einen einfachen Befehl horizontal skaliert und gesehen, wie Kubernetes das Loadbalancing und Routing automatisch anpasst. Die Beispiele haben gezeigt, wie auch auf einem einzelnen Rechner ein lokaler Kubernetes-Cluster aufgesetzt werden kann, dies kann zum Beispiel für lokale Tests bei der Entwicklung verwendet werden.
Ich hoffe, mit diesem Artikel das Interesse an Kubernetes geweckt zu haben und möchte ausdrücklich auf die sehr umfangreiche Kubernetes-Dokumentation verweisen.