lars
webmobiledatenbankendevopsarchitektur
hello (at) larskoelpin.de

Kubernetes Services

October 03, 2022
kubernetes

Wird Kubernetes eingesetzt existieren zumeist bereits verschiedene Computer die zu einem Cluster verbunden wurden. Das Ziel von Kubernetes ist es, diese letztlich gemeinsam als Ganzes zu verwalten. In einem Setup, wo kubernetes zum Einsatz kommt, können das beispielsweise 4 Computer sein, die gemeinsam ein Cluster ergeben. Architektonisch kann sich ein Setup wie im Bild 1 ergeben.

Bild 1: Setup
Bild 1: Setup

Alle Computer, genannt Knoten, befinden sich in einem privaten Netzwerk — Sie sind nicht außerhalb erreichbar. Auf diesen Rechnern läuft ein Kubernetes Agent, der die Verwaltungskommandos von Kubernetes in die Praxis ausführt.

Kubernetes Pods

Es ist natürlich nicht sinnvoll einen Orchestrator und ein Cluster zu betreiben, wenn es keine Anwendungen gibt. Deshalb gibt es bei Kubernetes das Konzept von Pods (bzw. Abstraktionen wie Deployments, die Pods erzeugen) um Anwendungen auf dem Cluster zu auszuführen.

Bild 2: Kubernetes Deployment
Bild 2: Kubernetes Deployment

Es ist also möglich, wie in Bild 2, ein Deployment auf das Cluster auszurollen, um Anwendungen zu installieren.

Ein Deployment hat dabei gewisse Kerndaten, die für das Beispiel relevant sind. Zum Einen natürlich die Applikation, die überhaupt ausgeführt werden soll (“image”) und zum Anderen die Anzahl der Instanzen, die auf dem Cluster deployed sein sollen.

Das Deployment sorgt dafür, dass genau so viele Pods mit der Applikation auf den Knoten verteilt werden wie angegeben.

Zusätzlich zu den Kerndaten, ist es möglich zusätzliche “Labels” an die Pods zu heften. Nach dem Ausrollen folgt also der Zustand auf der rechten Seite des Bildes. Es gibt 4 Knoten, ein Deployment. Dieses instruiert Kubernetes dafür zu sorgen, dass auf 3 verschiedenen Knoten eine Kopie der Applikation ausgeführt wird.

Noch ist jedoch keine einfache Kommunikation zwischen und mit den Applikationen möglich. Sie existiert ja nur als verteilte Pods. Zwar ist es möglich, dass einzelne Pods der Applikation über IP-Adressen kommunizieren, Pods sind aber vergängliche Ressourcen. Das bedeutet dass sie ständig erneuert und verworfen werden können, also sich die IP-Adresse ständig ändert. Es muss also eine Lösung existieren, die eine Kommunikation mit verschiedenen Pods über eine einheitliche Schnittstelle ermöglicht, also ohne konkrete IP-Adressen verwenden zu müssen.

Services

Kubernetes Services setzen genau hier an: Sie geben Pods mit verschiedenen IP-Adressen eine einheitliche Schnittstelle. Ein Service nutzt dabei als API keine konkreten IP-Adressen, sondern nutzt ein Kubernetes-API. Das bedeutet: Damit ein Service gewisse Pods ansteuern kann, wird ein Service immer mit einem Label-Selector versehen. Dieser Label-Selector wählt aus, an welche Pods er Datenverkehr weiterleitet, wenn der Service Anfragen erhält. Die konkreten IP-Adresen der Pods werden dabei undurchsichtig vom Nutzer aufgelöst.

Im folgenden Beispiel erstellen wir einen Service “Green-Label-Service”, der alle zuvor erstellten Pods verwaltet, die das label color=green haben.

Wird dieser Service, der alle “grünen” Pods verwaltet, als ClusterIP umgesetzt, sähe es wie folgt aus.

Bild 3: ClusterIP
Bild 3: ClusterIP

Allegrünen Pods nun eine gemeinsame Schnittstelle: den Service. Kubernetes stellt für diesen einen DNS Eintrag mit dem Namen: green-service.namespace.svc.cluster.local bereit. Jeglicher Anfrage, innerhalb des Pod-Netzwerkes, d.h. von Pod zu Pod, kann nun statt gegen eine konkrete Pod-IP-Adresse gegen den DNS Namen green-service.namespace.svc.cluster.local angefragt werden. Kubernetes löst diesen dann intern konkret gegen einen Pod auf. Welcher der drei Instanzen die Anfrage bearbeitet, ist dabei vom internen LoadBalancing abhängig.

Ein Service vom Typ ClusterIP erstellt also einen internen DNS Eintrag, der wenn er aufgerufen wird, auf eine zufällige IP-Adresse eines Pod aufgelöst wird. Das ist besonders dann hilfreich, wenn Applikationen innerhalb des Netzwerkes erreichbar sein sollen.

Anders sieht es aus, wenn Datenverkehr von außerhalb des Clusters kommt. Beispielsweise könnten die grünen Pods eine Webseite ausliefern. Damit ein externer Browser von außerhalb des Clusters diese konkret ansprechen kann, ist ein Service vom Typ NodePort eine Möglichkeit.

Bild 4: NodePort
Bild 4: NodePort

Bei diesem Setup habe ich zwei Änderungen vorgenommen. Um einen sind die konkreten Rechner nun mit dem Internet verbunden und haben eine öffentliche IP-Adresse. Zum anderen existiert in dem Szenario ein Service vom Typ NodePort. Der NodePort gibt ein Port an (standardmäßig Port 30000 - 32000), der auf dem konkreten Rechner aufrufbar ist.

In diesem Szenario würde es also bedeuten, dass der Port 30001 auf irgendeinem der Rechner-Adressen (199.255.12.121, 199.255.12.122, 199.255.12.123, 199.255.12.124), auf einen grünen Pod innerhalb des Pod-Netzwerkes weiterleitet. Ein NodePort ermöglicht also die Kommunikation von dem Netzwerk des physischen Host-Rechners in das Pod-Netzwerk. Würden jetzt im DNS-Service (z.B: Cloudflare) drei Einträge angelegt werden

A-RECORD myapp.de 199.255.12.121
A-RECORD myapp.de 199.255.12.122
A-RECORD myapp.de 199.255.12.123
A-RECORD myapp.de 199.255.12.124

Wäre die Applikation für den Normalverbraucher auf http://www.myapp.de:30000 erreichbar (man spricht hier auch vom DNS-LoadBalancing).

Ein NodePort-Service hat die Einschränkung, dass nur die Ports 30000+ freigegeben werden können. Das ist für HTTP-Server leider hinderlich. Doch dazu später mehr.

Dieser Ansatz hat aber einen weiteren konkreten Nachteil. Wird unsere Applikation öffentlich betrieben, muss im öffentlichen DNS-server (z.B. CloudFlare) eine IP-Adresse (oder mehrere) zum Auflösen eingetragen werden. Das bedeutet, dass die 4 Computer und dessen IP-Adresse für immer stabil bleiben müssen, wenn eine hohe Erreichbarkeit erreicht werden soll. Das läuft natürlich genau gegenteilig zum Mantra “Treat Computer as Cattle, not as Pets”. Das betreiben mehrerer konkreten IP-Adressen sollte also eher vermieden werden, wenn eine hohe Fehlertoleranz geschaffen werden soll. Auch Rechner können (oder eher: sollten), genauso wie Pods, jederzeit verworfen (herunter- und hochgefahren) werden können.

Und hier kommt der dedizierte LoadBalancer Service ins Spiel. Mit dem Service Type LoadBalancer, dargestellt im folgenden Bild in mit der roten Box, wird ein extra Rechner, ein externer LoadBalancer vom Cloud-Anbieter angebunden. Dieser existiert meistens in einem öffentlichem Netzwerk und erhält eine öffentliche IP. Dieser leitet den Datenverkehr an die internen Rechner, die wiederum über NodePort ansprechbar sind. Diese Abstraktion hat den Vorteil, dass im DNS dann beispielsweise die IP-Adresse des LoadBalancers eingetragen werden kann.

A-RECORD myapp.de 165.123.1231.1

Der LoadBalancer muss i.d.R. in diversen Cloud Umgebungen nicht selbst betrieben werden und ist deshalb ein Stück weniger Infrastruktur. Konkrete IP-Adressen der Knoten bleiben verborgen. Das heißt allerdings auch, wenn das Setup selbst gehostet wird, dass ein Anbinden eines LoadBalancers selbst erledigt werden muss, da es keinen LoadBalance-Provider gibt. Im Falle von k3s kann das beispielsweise Klipper übernehmen.

Bild 5: LoadBalancer
Bild 5: LoadBalancer

Es gibt also 3 Services: LoadBalancer, NodePort und ClusterIP, die jeweils den darunter liegenden Typ nutzen. Ein Service vom Typ ClusterIP ist ein internes Networking für Pods untereinander. Ein LoadBalancer ist eine extra Infrastruktur Komponente, die die konkreten Nodes aus dem System versteckt (und natürich je nach Auslastung verteilt). Ist kein dedizierter LoadBalancer gewünscht oder verfügbar, ist ein Service vom Typ NodePort notwendig. Doch dieser hat den Nachteil nur in gewissen Port-Verfügbarkeiten arbeiten zu können.

Die Frage die sich deshalb viele Stellen: Wie ist es trotzdem möglich Services auf nativen ports (z.B. Port 80 für HTTP) zu nutzen?

Und das bringt mich zum letzten “Psuedo”-Service: dem DaemonSet + hostPort. Ein DaemonSet ist streng genommen eher vergleichbar mit einem Deployment. Ein DaemonSet sorgt dafür, dass in jedem Computer im Cluster, immer genau eine Instanz einer gewissen Applikation ausgeführt wird. Des Weiteren ist es möglich diese Pods mit dem host Netzwerk zu verbinden (Ähnlich wie ein NodePort) und so native Ports (wie den Port 80) zu nutzen.

Viele Dienste, wie beispielsweise Ingress-Controller oder das zuvor erwähnte Klipper, nutzen diesen Umstand, wenn ein dedizierter LoadBalancer nicht verfügbar ist.

Bild 6: HostPort-Service
Bild 6: HostPort-Service

Im Bild wird sichergestellt, dass jede Node genau einen blauen Pod (bspw. einen nginx-IngressController auf port 80) ausführt. Dieser gilt als Eintrittstor in das Cluster und hat natürlich dieselben Nachteile wie ein Service vom Typ NodePort. Mit dem extra Nachteil, dass nur eine Instanz pro Rechner betrieben werden kann. Bei einem NodePort Services sind es beliebig viele.

Zusammenfassung

Kubernetes hat verschiedene Service-Typen, um Datenverkehr zwischen Pods zu ermöglichen. Dabei existieren ClusterIP für interne Kommunikation, NodePort-Services für externen Datenverkehr und LoadBalancer, meistens vom Cloud-Anbieter, für sehr hohe Arbeitslasten. Viele Applikationen werden durch DaemonSets auf jedem Knoten ausgeführt und können durch das Host-Network auch native Ports belegen.