Kubernetes (k8s): Begriffe
Bei Kubernetes (auch k8s genannt) handelt es sich um ein System für die Orchestrierung von Containern (bspw. Docker).
Ein solche System besteht aus mindestens einem Master und mindestens einer Node (bzw. Worker). Der Master kontrolliert den Cluster. Auf den Nodes werden die Container ausgeführt. Welche Container eine Node ausführt, wird vom Master bestimmt. In den Deployments können dem Master allerdings ein paar Hinweise gegeben werden, was der Node sein/können soll.
Logischerweise muss dafür auf den einzelnen Nodes die entsprechende Container-Runtime installiert sein.
Der Youtube Kanal TechWorld With Nana bietet eine Vielzahl sehr gut gemachter Videos zu allen möglichen Kubernetes Themen.
Begriffe
Node
Jeder Server, der Teil des Kubernetes Clusters ist, ist eine Node. Jede Node hat einen von Kubernetes zugeordneten IP-Adressbereich. Pods, Services, … erhalten eine IP-Adresse aus diesem Adressbereich.
Pod
Ein Pod ist eine Gruppe von Containern, die gemeinsam auf einer Node ausgeführt werden. Diese werden immer gleichzeitig gestartet oder beendet. Pods teilen sich die Umgebung, die Datenträger und die IP-Adressen. Jeder Pod hat eine eigenen IP-Adresse, wodurch das Problem mit den freien Ports bei vielen Containern, welches es in Docker-Umgebungen gibt, weg fällt.
Um die bestehenden Pods abzufragen, kann folgender Befehl aufgerufen werden:
kubectl get pods
Pods werden normalerweise nicht direkt erstellt, sondern im Zuge eines Deployments.
Jeder Pod hat erhält eigene IP-Adresse. Die jeweiligen IP-Adressen können folgendermaßen abgefragt werden:
kubectl get pods -o wide
ReplicaSet
Ein ReplicaSet definiert, wie viele gleiche Pods im Cluster aktiv sein sollen. Wird die Anzahl geändert, so kümmert sich der Master darum, dass Pods gestartet oder gestoppt werden. Im Falle, dass ein Node stirbt, wird der Pod in einer anderen Node gestartet.
ReplicaSets werden normalerweise nicht direkt erstellt, sondern im Zuge eines Deployments.
Deployment
Deployments beschreiben die zu erstellenden Pods inkl. Replikate und werden vom Deployment-Controller angewendet und werden für Stateless Anwendungen verwendet.
StatefulSet
Das StatefulSet ist dafür da, dass Pods in der richtigen Reihenfolge und eindeutig gestartet werden.
Ein Beispiel für eine Stateful Anwendung sind Datenbanken. Die Verwendung von Replikaten ist bei diesen wesentlich komplexer (tw. unmöglich) als by Stateless Anwendungen.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: busybox-sf-deployment
labels:
app: busybox-sf
spec:
serviceName: busybox-sf
replicas: 1
selector:
matchLabels:
app: busybox-sf
template:
metadata:
labels:
app: busybox-sf
spec:
volumes:
- name: storage-specific
nfs:
server: 192.168.1.122
path: "/var/nfs/specific"
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
volumeMounts:
- name: storage-specific
mountPath: /data-specific
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
DaemonSet
Ein DaemonSet stellt sicher, dass auf allen Nodes ein bestimmter Pod läuft (z.B. Monitor). Werden bspw. neue Nodes hinzugefügt, so wird auf diesen automatisch der definierte Pod gestartet.
Job
Ein Job startet ein oder mehrere Pods, führt diese aus und und sobald die angegebene Anzahl von Pods erfolgreich ausgeführt wurden, ist der Job abgeschlossen.
Service
Jeder Pod in Kubernetes erhält eine eigene IP-Adresse. Wenn ein Pod neu gestartet wird, dann erhält dieser eine neue IP-Adresse. Daher macht es keinen Sinn die Pod-IP-Adresse bei der Referenzierung aus anderen Pods zu verwenden.
Services hingegen haben eine statische IP-Adresse. Jeder Pod, der bspw. per TCP/IP erreichbar sein soll, hat somit im Normalfall einen zugeordneten Service. Weiters unterstützen Services LoadBalancing, was den Zugriff auf Pods, auch wenn es mehrere Replikate davon gibt, vereinfacht. Je nach verwendetem Typ können Services nur intern im Cluster oder auch von extern erreichbar sein.
Einem Deployment werden in der Konfiguration unter „spec.template.metadata.labels“ ein oder mehrere Labels zugeordnet. Beim Service werden unter „spec.selector“ die Labels des Deployments angegeben, auf die die Weiterleitung gemacht werden soll. Wenn beim Service mehrere Labels angegeben werden, dann wird die Weiterleitung nur an die Pods gemacht, die alle diese definiert haben. Unter „spec.ports.port“ wird der Port definiert, auf den der Service hört. Zusätzlich kann „spec.ports.targetPort“ angegeben werden. Dies ist der Port des Pods, an den die Anfrage weitergeleitet wird. Wird er nicht angegeben, ist der ident zu „spec.ports.port“.
Mit dem folgenden Befehl kann das Mapping zwischen Services und Pods dargestellt werden.
kubectl get endpoints
Es gibt unterschiedliche Service Typen.
ClusterIP ist der am häufigsten verwendete Service Typ. Weiters ist es der Standard-Typ der verwendete werden, wenn in der Definition nicht explizit ein anderer angegeben wurde (alternativ „spec.type: ClusterIP“). Dieser Service ist nur von Komponenten innerhalb des Kubernetes-Clusters erreichbar. Gibt es aufgrund des „selectors“ mehrere Pods, wird ein Zufälliger auswählt.
Headless wird verwendet, wenn die Kommunikation mit einem spezifischen Pod gewünscht ist (bspw. bei Stateful-Anwendungen oder zur Kommunikation eines Pods mit seinen Replikaten). Ein Headless-Service wird definiert, in dem „spec.clusterIP: None“ gesetzt wird. Somit kann ein DNS-Lookup gemacht werden, der die IP-Adressen der zugeordneten Pods zurückliefert.
Mit Hilfe von NodePort („spec.type: NodePort“) kann auch mit Pods von extern kommuniziert werden. Zusätzlich muss unter „spec.ports“ ein „nodePort“ definiert werden. Dieser wird auf alles Nodes geöffnet und leitet die Anfragen dann entsprechend an die Pods weiter. Node-Ports müssen in einem Bereich zwischen 30000 und 32767 sein! Ansonsten verhält sich ein solcher Services gleich wie ein ClusterIP-Service (er hat auch ein „spec.ports.port“!). Nichtsdestotrotz sollten NodePort-Services vermieden werden, da sie ineffizient und unsicher (manuelles öffnen von div. Ports). Bessere Alternative dazu ist LoadBalancer.
LoadBalancer („spec.type: LoadBalancer“) sind eine Erweiterung von NodePort und werden zusammen mit externen Load-Balancers (z.B. des Cloud-Anbieters) verwendet.
Ingress
Ingress ist ein erweiterter Service für http/https, der es zusätzlich ermöglicht aufgrund von Request-Informationen die Anfragen an bestimmte Services weiterzuleiten. Beispielsweise kann ein Zugriff auf „https:/www.mydomain.at/staff“ an den Service A geleitet werden, „https://www.mydomain.at/orders“ an den Service B.
Ingress sind der Standard-Einsprungspunkt für Anfragen eines Browsers an den Kubernetes-Cluster.
Volume
Wird ein Pod neu gestartet, dann sind alle Daten im Dateisystem, die zuvor in diesem geändert wurden, verloren. Kubernetes startet Pods immer wieder frisch.
Um Daten unabhängig von Pods zu machen, können Volumes eingebunden werden. Unter https://kubernetes.io/docs/concepts/storage/volumes/ sind eine Vielzahl von möglichen Typen beschrieben, die verwendet werden können.
Nachfolgend ein Beispiel um ein NFS-Share in einen Pod einzubinden.
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busybox
spec:
replicas: 4
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
volumes:
- name: storage-specific
nfs:
server: 192.168.1.122
path: "/var/nfs/specific/test"
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
volumeMounts:
- name: storage-specific
mountPath: /data-specific
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
Persistent Volume
Der Gedanke von Persistent Volumes (PV) ist etwas abstrakter. Kubernetes Administratoren stellen ein oder mehrere PVs zur Verfügung. Dabei werden auch div. Kriterien definiert (max. Größe, Zugriffsmodi, …) und Ordnen der Definition Volume wie zuvor beschrieben zu.
Hier ein Beispiel für die Erstellung eines PV mir NFS.
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.122
path: "/var/nfs/general"
Für die Verwendung eines PV muss ein Persistent Volume Claim definiert werden.
Persistent Volume Claim
Bei der Definition von Deployments können Volumes auch über Persistent Volume Claims (PVC) eingebunden werden. Diese definieren Bedingungen, die sie benötigen und Kubernetes versucht ein passendes Persistent Volume zu ermittelt.
Um den Storage im Pod dann tatsächlich zur Verfügung zu haben, muss im Deployment im Bereich „spec“ ein Element „volumes“ definiert werden und im Container-Block darauf verwiesen werden.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busybox
spec:
replicas: 4
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
volumes:
- name: storage-general
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
volumeMounts:
- name: storage-general
mountPath: /data-general
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
Storage Class
Alternativ zur Erstellung von duzenden Persistent Volumes kann auch eine Storage Class definiert werden und in einem Persistent Volume Claim verwendet werden. Eine Storage Class erzeugt Persistent Volumes im Hintergrund automatisch.
Config Maps & Secrets
Config Maps und Secrets sind eine spezielles Volumes, die intern von Kubernetes gespeichert und verwaltet wird. Diese werden bspw. für das Speichern von Einstellungen oder Konfigurationsdateien verwendet.
Es gibt mehrere Wege eine Config Map in Kubernetes zu erstellen.
Hier ein Beispiel für das Erstellen mit Hilfe einer yaml-Datei:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data:
MY_NAME: "Stefan"
MY_COUNTRY: "Austria"
Der Name der Config Map ist „my-config“ und diese hat zwei Einträge: MY_NAME und MY_COUNTRY.
Hier ein Beispiel für das Hinzufügen über eine Datei (kann auch ein Verzeichnis sein):
kubectl create configmap my-config --from-file=config.txt
Der Name der Config ist hier ebenfalls „my-config“ und diese hat einen Eintrag: config.txt mit dem Wert aus der Datei. Als Namen des Eintrages wird immer der Dateiname verwendet (ohne Pfad).
Bei Deployments können diese als Umgebungsvariablen oder Volumes verwendet werden.
In diesem Beispiel werden alle Einträge in der Config Map als Umgebungsvariablen im Pod zur Verfügung gestellt:
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busybox
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
name: my-config
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
Und hier mit einem Volume:
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busybox
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
volumes:
- name: config-volume
configMap:
name: my-config
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config-volume
mountPath: /etc/config
command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
Wichtig ist hier zu erwähnen, dass jeder Eintrag in der Config Map eine eigene Datei ergibt, jeweils mit dem Namen des Eintrages.
Die Verwendung von Config Maps und Secrets ist nahezu ident. Secrets werden für das Speichern von Zertifikaten und Passwörter verwendet und Config Maps für nicht sensitive Daten. Wichtig zu erwähnen, dass auch Secrets standardmäßig nicht verschlüsselt sind und von jedem, der Zugriff auf die Kubernetes API hat abgerufen werden können. Mehr Details dazu unter https://kubernetes.io/docs/concepts/configuration/secret/.
Namespace
Namespaces ermöglichen das Gruppieren von unterschiedlichen Ressourcen in Kubernetes. Man kann sich diese als virtuelle Cluster innerhalb des Kubernetes-Clusters vorstellen.
Sie werden auch oft dafür verwendet die Produktivversion und die Testversion einer Anwendung innerhalb eines Clusters besser zu organisieren und zu trennen.
Pro Namespace können Limits wie CPU, RAM, … definiert werden wodurch ein Namespace nicht die kompletten Ressourcen des Clusters verbrauchen kann.
Die meisten Ressourcen innerhalb eines Namespaces können nicht von Ressourcen in einem anderen Namespace verwendet werden (bspw. Config Maps oder Secrets). Auf Services in einem anderen Namespace kann hingegen zugegriffen werden. Dabei muss allerdings der Namespace als Suffix an den Host angegeben werden (z.B. my-service.my-namespace).
Helm Chart
Helm Charts sind einfach ausgedrückt eine Sammlung von YAML-Files mit unterschiedlichen Ressourcen, die für das Deployment einer Anwendung notwendig sind.
Nehmen wir WordPress als Beispiel. Die benötigten Komponenten sind:
- Datenbank (MariaDB oder MySql)
- WordPress Installation (Webserver inkl. PHP)
- Ingress für den Zugriff auf den Webserver
- …
Ein Helm-Chart inkludiert alle diese Komponenten. Diese können mit einem Befehl installiert werden.
Zusätzlich können Templates in Helm Charts verwendet werden. Dies bedeutet, dass in den einzelnen YAML-Dateien Platzhalter definiert werden können. Diese werden dann mit den definierten Werten aus der values.yaml-Datei bzw. zusätzlicher Values-Dateien befüllt.