Kubernetes (k8s): Begriffe

7. Oktober 2020

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.