101 Series: 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.