'Wer nicht skaliert, verliert' oder 'Wem`s gelingt, gewinnt' sind Leitsätze von Kubernetes. Die Cluster- Container-Technologie aus dem Hause Google setzt dabei konsequent auf Hochverfügbarkeit von Netzwerkservices (protokollbasierte, adressierbare Dienste im Netz).
Kubernetes ist dabei die ideale Ergänzung zu Docker. Wo Docker aufwendig wird, z.B. beim Container- Monitoring und der (Re)start-Automatisierung, fängt Kubernetes an. Es kümmert sich neben der Hochverfügbarkeit (0-Downtime bzw. 24/7-Verfügbarkeit eines Netzwerkservice) von Services um den Lebenszyklus von Containern (run, start, stop, remove). Das betrifft alle Dinge rund um die Konfiguration und den Betrieb von Containern auf unterschiedlichen Docker-Hosts. Das oberste Ziel ist einfache Konfigurierbarkeit und maximale Automatisierung hochverfügbarer Netzwerkservices.
Die Idee von Kubernetes ist die Entkopplung der Container von ihrer Serviceadresse, was durch eine Serviceschicht umgesetzt ist. Somit kann jede Serviceanfrage an einen Service gestellt und von mehreren Containern beantwortet werden. Jeder Service ist somit horizontal skalierbar.
Zentrale Kubernetes-Instrumente sind dabei:
Jeder Pod wird von Kubernetes überwacht, um ihn entweder bei Bedarf neu zu starten oder ihm Zeit zur Selbstheilung zu geben, falls der Pod ausgelastet ist. Der Neustart wird durch die Liveness-Probe ausgelöst und die Selbstheilung durch die Readiness-Probe. Beide Probes können entweder HTTP-Endpunkte oder Container-Kommandos sein. Sie werden von Kubernetes in einem definierbaren Takt abgefragt. Im wiederholten Fehlerfall passiert folgendes:
Beide Probes können Bestandteil eines Pods sein und werden folgendermaßen im YAML-Format (YAML und JSON sind neben kubectl die zwei Konfigurationsformate, um die Kubernetes-REST-Endpunkte zu konfigurieren) konfiguriert:
apiVersion: v1 kind: Pod metadata: labels: app: demo name: demo-pod spec: containers: - image: jwausle/de.jwausle.kubernetes:demo imagePullPolicy: Never name: demo ports: - containerPort: 8080 protocol: TCP livenessProbe: # - Kubernetes started den Pod neu failureThreshold: 3 httpGet: path: ⁄liveness port: 8080 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 3 successThreshold: 1 timeoutSeconds: 1 readinessProbe: # - Kubernetes routet keinen weiteren Traffic zu diesem Pod failureThreshold: 3 httpGet: path: ⁄readiness port: 8080 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 3 successThreshold: 1 timeoutSeconds: 1
Das 'initialDelaySeconds: 30' definiert die initiale Wartezeit beim Podstart, wo Kubernetes keine Probeanfrage stellt. Danach stellt Kubernetes alle 3 Sekunden ( 'periodSeconds: 3' ) eine Probe-Anfrage. Sollte dreimal hintereinander ein Fehler passieren ( 'failureThreshold: 3' ), dann setzt Kubernetes die entsprechende Probe in den Fehlermodus. Um den Fehlermodus wieder zu verlassen, kann man mit 'successThreshold: N' die Anzahl erfolgreicher Anfragen für den Statuswechsel konfigurieren. Damit nicht jeder Pod einzeln konfiguriert werden muss, können alternativ beide Probes im ReplicationController, ReplicaSet oder Deployment konfiguriert werden.
Zusammen mit Punkt 2. "Automatisierter Up-scale von Services" kann Kubernetes Hochverfügbarkeit für jeden Service gewährleisten.
Kubernetes überwacht periodisch die Ressourcen aller Pods wie z.B. vCPU- und Speicherauslastung. Für jede Ressource kann ein Schwellwert (bezüglich aller Pods) definiert werden, ab welchem Kubernetes anfängt, automatisch zusätzliche Pods zu starten beziehungsweise bestehende herunterzufahren. Dieser Schwellwert wird im HorizontalPodAutoscaler konfiguriert:
apiVersion: v1 items: - apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: ${DEPLOYMENT_CONFIG_NAME} spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: apiVersion: v1 kind: ReplicationController name: ${DEPLOYMENT_CONFIG_NAME} targetCPUUtilizationPercentage: 20 kind: List
Die Anzahl der gestarteten Pods wird durch die folgende Formel bestimmt:
'desiredReplicas = currentReplicas * ( currentMetricValue / desiredMetricValue )'
Bsp.1 - Auslastung der bestehenden Pods führt zum Start von 2 weiteren:
Bsp.2 - Auslastung der bestehenden Pods führt zum Stopp von 2 existierenden:
Microservices eignen sich ideal für den Einsatz von Kubernetes. Statuslosigkeit und/oder Clusterfähigkeit sind dabei Haupteigenschaften eines Microservices.
Ein sehr gutes Beispiel sind 'Build Slaves' für Jenkins oder Gitlab, da ein wichtiges Merkmal deren Skalierfähigkeit pro Commit ist, natürlich je nach Ausbau des (C)ontinues-(I)ntegration-Prozesses. D.h., jeder Commit, der einen CI Build auslöst, soll einen atomaren 'Build Slave' erhalten.
Eher nicht geeignet sind zustandsabhängige Services wie klassische Datenbanken. Deren Clusterfähigkeit ist oft integraler Bestandteil des Gesamtsystems und nicht des einzelnen Containers. Datenbanken sollten besser außerhalb von Kubernetes betrieben werden. Das Overlay-Netzwerk von Kubernetes bietet auch dafür eine nahtlose Integrationsmöglichkeit.
Kubernetes ist die ideale Ergänzung zu Docker im Produktiveinsatz. Wo Docker aufwendig wird, setzt Kubernetes an. Es kümmert sich neben der Hochverfügbarkeit und des Routings von Services hauptsächlich um den Betrieb von Containern über einen längeren Zeitraum. Der automatisierte Neustart eines Pods ist dabei zentrales Instrument zur Skalierung.
Wie alles hat auch Kubernetes eine Kehrseite. Die Konfiguration erfolgt fast ausschliesslich über YAML-/JSON-Dateien, für die der Toolsupport meist mit dem Syntax-Highlighting aufhört. Das schafft ein erhöhtes Konfigurationsrisiko bei stark vernetzten Microservice-Infrastrukturen.