Dennis Hemeier

101 Series: Kubernetes Resource Management

101 Series
Kubernetes ermöglicht es uns, unsere Cluster Ressourcen wie CPU, Memory und Storage optimal zu nutzen und zu verwalten. Erfahre hier, welche Möglichkeiten es innerhalb von Kubernetes gibt und wie du diese in deiner täglichen Arbeit mit Kubernetes nutzen kannst.
Kubernetes Resource Management

Einführung Ressourcen

Jede laufende Applikation benötigt im Betrieb grundsätzlich eine bestimmte Anzahl von Ressourcen. So bucht ihr vermutlich für einen Online-Shop einen größeren Server bei eurem Webhosting Anbieter wie für eine kleine Microservice Webanwendung. Das Problem hierbei ist, dass du meist nur zwischen wenigen Server-Paketen und Größen wählen kannst. 

Mit Kubernetes können wir unsere benötigten Ressourcen komplett unabhängig von den eigentlichen Servern verwalten und steuern. Diese Angaben werden zur Verwaltung der Kapazitäten innerhalb des Clusters verwendet. Doch zunächst müssen wir uns einmal anschauen, welche Ressourcen es überhaupt gibt

Memory

Der benötigte Arbeitsspeicher wird in Bytes angegeben. Hierbei können wir entweder eine plain Integer Zahl (Bytes Angabe), eine Zahl in Verbindung mit einem einstelligen Suffix (K, M, G, T) für die Umrechnung mit 1000 oder einen zweistelligen Suffix (Ki, Mi, Gi, Ti) für die Umrechnung mit 1024 angeben. Die folgende Angabe entspricht immer nahezu dem gleichen Wert:

128974848, 129e6, 129000K, 123000Ki, 129M, 123Mi

CPU

Die Angabe der CPU gestaltet sich etwas komplizierter. Zunächst einmal müssen wir Wissen, was genau "eine CPU" innerhalb von Kubernetes bedeutet:

Eine CPU entspricht eine AWS vCPU, ein Azure vCore oder ein Hyperthread on Bare-Metal Intel Prozessoren

Da wir meist mit sehr kleinen Werten arbeiten und unsere Aplikation z.B. nur einen viertel CPU-Core benötigt, können wir statt 0.25 CPU die Angabe 250m verwenden. Das Konzept dahinter nennt sich Millicores.

1000m = 1000 Millicores = 1 CPU

Ephermal Storage

Über einen sogenannten emptyDir-Storage kann dem Container ein (nicht persistenter, temporärer) Speicherplatz zur Verfügung gestellt werden. Dieser Speicherplatz wird meist für temporäre Arbeitsergebnisse, Caching und/oder für Logs der Applikation verwendet. Die Angabe erfolgt hier wie bei Memory über Bytes (plain Integer Zahl, einstelliger Suffix, zweistelliger Suffix).

Angabe der Resources in Kubernetes - Requests und Limits

Die Angabe der Ressourcen erfolgt innerhalb von Kubernetes in den jeweiligen Applikationen. Genauer gesagt KÖNNEN wir für jeden Container innerhalb eines Pods entsprechende Requests und Limits hinterlegen. Doch was genau ist der Unterschied zwischen Requests und Limits:

Requests

Die Angabe der Requests wird für das Scheduling (Entscheidung, auf welcher Node die Applikation starten soll) verwendet. Diese Angabe wird also zur Berechnung der freien und verwendeten Ressourcen der Nodes und daraus folgend der freien und verwendeten Ressourcen des gesamten Clusters verwendet. Merke dir hierbei folgendes:

Node Kapazität (CPU, Memory, Ephermal Storage) - Requests der laufenden Pods = Freie Ressourcen der Node

Best-Practice: Setze diese Werte auf die tatsächliche Nutzung

Limits

Wie der Name es schon vermuten lässt, handelt es hierbei um harte Limits, die wir einem Pod mitgeben können. Hierbei ist zu beachten das der Memory Wert wesentlich wichtiger ist. Sollte ein Pod sein Memory-Limit überschreiten, so wird dieser mit einer Out-of-Memory Meldung gekillt und neugestartet.

Ihr solltet die Limits immer setzen, da ansonsten eine einzelne (fehlerhafte) Applikation, alle Ressourcen der jeweiligen Node beanspruchen kann und es somit zu einem Ausfall der ganzen Node kommt. 

Best-Practice: Immer setzen, Setze die Limits auf die tatsächliche Nutzung + Puffer für Spitzen

Beispiel: Requests und Limits

Im folgenden findest du eine Beispiel Applikation mit definierten Requests und Limits:

apiVersion: v1
kind: Pod
metadata:
  name: wordpress-demo
spec:
  containers:
  - name: app
    image: bitnami/wordpress
    env:
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
        ephemeral-storage: "2Gi"
      limits:
        memory: "128Mi"
        cpu: "500m"
        ephemeral-storage: "3Gi"

Quality of Service

Je nachdem wie du die Ressourcen innerhalb deiner Applikation definierst läuft diese in einem bestimmten Quality of Service. Hierbei hast du drei Möglichkeiten:

Guaranteed: Requests = Limits

Burstable: Requests < Limits

BestEffort: Keine Resource Requests und Limits angegeben

Interessant wird das ganze, wenn aufgrund von Ressourcenknappheit einer Node diese unter Last gerät und Kubernetes anfäng, durch das verschieben von Pods die Last auf dieser Node zu reduzieren. Hierbei werden die Pods nämlich nicht zufällig terminiert und verschoben, sondern basierend auf der Termination Order:

Termination Order: BestEffort, Burstable, Guaranteed

Node Ressourcen einsehen

Um die Ressourcen einer Node einzusehen, kannst du den Befehl kubectl describe node NODENAME verwenden und die entsprechenden Ressourcen einsehen. Angaben, die hierbei einen Wert von 0 haben, zeigen euch auf, bei welchen Pods es bisher keine Limits und/oder Requests gibt:

# kubectl describe node NODENAME
[ . . . ]
Capacity:
  attachable-volumes-aws-ebs:  39
  cpu:                         2
  ephemeral-storage:           32461564Ki
  hugepages-2Mi:               0
  memory:                      4028180Ki
  pods:                        110
Allocatable:
  attachable-volumes-aws-ebs:  39
  cpu:                         1950m
  ephemeral-storage:           29916577333
  hugepages-2Mi:               0
  memory:                      3720980Ki
  pods:                        110
[ . . . ]
Non-terminated Pods:          (8 in total)
  Namespace                   Name                                                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                   ----                                                         ------------  ----------  ---------------  -------------  ---
  cloudpirate.                cloudpirate-deployment-7dd696b868-7d4qv.                     10m (0%)      100m (5%)   32Mi (0%)        72Mi (1%)      2d12h
  kiam                        kiam-agent-l2dh7                                             0 (0%)        0 (0%)      0 (0%)           0 (0%)         2d13h
  kube-system                 kube-proxy-ip-172-20-60-172.eu-central-1.compute.internal    100m (5%)     0 (0%)      0 (0%)           0 (0%)         2d13h
  kube-system                 weave-net-kph5l                                              100m (5%)     0 (0%)      400Mi (11%)      400Mi (11%)    2d13h
  monitoring                  prometheus-kube-state-metrics-65b96cd68c-z5bbd               10m (0%)      20m (1%)    16Mi (0%)        64Mi (1%)      2d12h
  monitoring                  prometheus-node-exporter-2r2ps                               50m (2%)      100m (5%)   12Mi (0%)        30Mi (0%)      2d13h
  monitoring                  prometheus-server-66b786978-76fc8                            1 (51%)       2 (102%)    3000Mi (82%)     3000Mi (82%)   2d13h
  monitoring                  promtail-rh6b6                                               100m (5%)     200m (10%)  34Mi (0%)        56Mi (1%)      2d13h
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                    Requests      Limits
  --------                    --------      ------
  cpu                         1370m (70%)   2420m (124%)
  memory                      3494Mi (96%)  3622Mi (99%)
  ephemeral-storage           0 (0%)        0 (0%)
  hugepages-2Mi               0 (0%)        0 (0%)
  attachable-volumes-aws-ebs  0             0

Standard Ressourcen setzen: LimitRanger

Innerhalb eines einzelnen Namespaces können Standard Ressourcen Requests und Limits hinterlegt werden die dafür sorgen, dass Pods, welche ohne Requests und Limits erstellt werden, automatisch die Angaben aus dem LimitRanger bekommen. Ein LimitRanger wird hierbei wie folgt angelegt:

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
  namespace: default
spec:
  limits:
  - default:
      memory: 512Mi
      cpu: 500m
    defaultRequest:
      memory: 256Mi
      cpu: 250m
    type: Container

Fazit

Wer das Prinzip hinter Request und Limits einmal verstanden hat, der kann mit dessen Hilfe Effektiv die Ressourcen innerhalb eines Clusters verwalten und so die Nutzung der vorhandenen Kapazitäten optimieren. Mit Hilfe von Kubernetes konnte ich so zum Beispiel die Kosten einer produktiv genutzen Infrastruktur um knapp 50% senken.

Solltet ihr Fragen zu diesem oder weiteren Themen Rund um Kubernetes haben, so schreibt mir gerne eine Nachricht oder besucht eine unserer öffentlichen Schulungen, in der wir unter anderem dieses Thema und noch viele weitere Themen rund um Kubernetes behandeln.