Kubernetes in Action - Marko Lukša - E-Book

Kubernetes in Action E-Book

Marko Lukša

0,0

Beschreibung

Mit Kubernetes große Container-Infrastrukturen ausfallsicher verwalten Nach einer Einführung in die typischen Problemstellungen, mit denen Softwareentwickler und Administratoren konfrontiert sind, und wie diese mit Kubernetes gelöst werden können, lernen Sie in einem ersten Beispielprojekt die praktische Umsetzung. Es wird gezeigt, wie eine einfache in einem Container laufende Web-Applikation über ein Kubernetes-Cluster verwaltet werden kann. Im zweiten Teil des Buches lernen Sie die zu Grunde liegenden Konzepte kennen, deren Verständnis unbedingt notwendig ist, um große Container-Cluster mit Kubernetes zu betreiben. Im letzten Teil wird die Funktionsweise von Kubernetes beschrieben und auf weiterführende Aspekte eingegangen. Hier wird außerdem das erworbene Wissen aus den ersten beiden Teilen zusammengeführt, damit Sie den vollen Nutzen aus der Kubernetes-Plattform ziehen können.

Sie lesen das E-Book in den Legimi-Apps auf:

Android
iOS
von Legimi
zertifizierten E-Readern

Seitenzahl: 863

Veröffentlichungsjahr: 2020

Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:

Android
iOS
Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Marko Lukša

Kubernetes in Action

Anwendungen in Kubernetes-Clustern bereitstellen und verwalten

Übersetzung: G & U Language & Publishing Services, Flensburg, www.gundu.com

Titel der Originalausgabe: „Kubernetes in Action“, © 2018 by Manning Publications Co. Original English language edition published by Manning Publications USA © 2018 by Manning Publications.German-language edition copyright © 2018 by Carl Hanser Verlag München. All rights reserved

Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder Teilen davon – entsteht.Ebenso übernehmen Autor und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.

Bibliografische Information der Deutschen Nationalbibliothek:Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

Dieses Werk ist urheberrechtlich geschützt.Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) – auch nicht für Zwecke der Unterrichtsgestaltung – reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.

Copyright für die deutsche Ausgabe:© 2018 Carl Hanser Verlag München, www.hanser-fachbuch.deLektorat: Sylvia HasselbachCopy editing: Sandra Gottmann, Münster-NienbergeUmschlagdesign: Marc Müller-Bremer, München, www.rebranding.deUmschlagrealisation: Stephan RönigkGesamtherstellung: Kösel, KrugzellAusstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702

Print-ISBN:        978-3-446-45510-8E-Book-ISBN:   978-3-446-45602-0E-Pub-ISBN:     978-3-446-45824-6

Für meine Eltern, die die Bedürfnisse ihrer Kinder stets über ihre eigenen gestellt haben.

Inhalt

Titelei

Impressum

Inhalt

Vorwort

Der Autor

Danksagung zur englischsprachigen Ausgabe

Über dieses Buch

Zielgruppe

Der Aufbau dieses Buches

Der Code

Das Forum zum Buch

Sonstige Onlinequellen

Teil I: Überblick

1 Einführung in Kubernetes

1.1 Der Bedarf für ein System wie Kubernetes

1.1.1 Von monolithischen Anwendungen zu Microservices

1.1.2 Eine konsistente Umgebung für Anwendungen bereitstellen

1.1.3 Übergang zu Continuous Delivery: DevOps und NoOps

1.2 Containertechnologien

1.2.1 Was sind Container?

1.2.2 Die Containerplattform Docker

1.2.3 Die Docker-Alternative rkt

1.3 Kubernetes

1.3.1 Die Ursprünge

1.3.2 Kubernetes im Überblick

1.3.3 Die Architektur eines Kubernetes-Clusters

1.3.4 Anwendungen auf Kubernetes ausführen

1.3.5 Vorteile der Verwendung von Kubernetes

1.4 Zusammenfassung

2 Erste Schritte mit Docker und Kubernetes

2.1 Containerimages mit Docker erstellen, ausführen und teilen

2.1.1 Docker installieren und einen Hello-world-Container ausführen

2.1.2 Eine triviale Node.js-Anwendung erstellen

2.1.3 Eine Docker-Datei für das Image erstellen

2.1.4 Das Containerimage erstellen

2.1.5 Das Containerimage ausführen

2.1.6 Das Innenleben eines laufenden Containers untersuchen

2.1.7 Container anhalten und entfernen

2.1.8 Das Image zu einer Registry hochladen

2.2 Kubernetes-Cluster einrichten

2.2.1 Einen lokalen Kubernetes-Cluster mit einem Knoten mithilfe von Minikube ausführen

2.2.2 Gehostete GKE-Cluster

2.2.3 Einen Alias und die Befehlszeilenvervollständigung für kubectl einrichten

2.3 Eine erste Anwendung in Kubernetes ausführen

2.3.1 Die Node.js-Anwendung bereitstellen

2.3.2 Auf die Webanwendung zugreifen

2.3.3 Die logischen Bestandteile des Systems

2.3.4 Anwendungen horizontal skalieren

2.3.5 Auf welchen Knoten läuft die Anwendung?

2.3.6 Das Kubernetes-Dashboard

2.4 Zusammenfassung

Teil II: Grundlagen

3 Pods: Container in Kubernetes ausführen

3.1 Einführung in Pods

3.1.1 Wozu benötigen wir Pods?

3.1.2 Grundlagen von Pods

3.1.3 Container auf Pods verteilen

3.2 Pods aus YAML- und JSON-Deskriptoren erstellen

3.2.1 Den YAML-Deskriptor eines bestehenden Pods untersuchen

3.2.2 Einen einfachen YAML-Deskriptor für einen Pod schreiben

3.2.3 Einen Pod mit kubectl create erstellen

3.2.4 Anwendungsprotokolle anzeigen

3.2.5 Anforderungen an den Pod senden

3.3 Pods mithilfe von Labels ordnen

3.3.1 Einführung in Labels

3.3.2 Labels beim Erstellen eines Pods angeben

3.3.3 Labels vorhandener Pods ändern

3.4 Eine Auswahl der Pods mithilfe von Labelselektoren auflisten

3.4.1 Pods anhand eines Labelselektors auflisten

3.4.2 Labelselektoren mit mehreren Bedingungen

3.5 Die Podzuweisung mithilfe von Labels und Selektoren einschränken

3.5.1 Labels zur Klassifizierung von Arbeitsknoten

3.5.2 Pods bestimmten Knoten zuweisen

3.5.3 Zuweisung zu einem einzelnen Knoten

3.6 Pods mit Anmerkungen versehen

3.6.1 Die Anmerkungen zu einem Objekt einsehen

3.6.2 Anmerkungen hinzufügen und ändern

3.7 Ressourcen mithilfe von Namespaces gruppieren

3.7.1 Der Bedarf für Namespaces

3.7.2 Andere Namespaces und die zugehörigen Pods finden

3.7.3 Namespaces erstellen

3.7.4 Objekte in anderen Namespaces verwalten

3.7.5 Die Trennung der Namespaces

3.8 Pods stoppen und entfernen

3.8.1 Pods unter Angabe des Namens löschen

3.8.2 Pods mithilfe von Labelselektoren löschen

3.8.3 Pods durch Entfernen eines ganzen Namespaces löschen

3.8.4 Alle Pods in einem Namespace löschen und den Namespace erhalten

3.8.5 (Fast) alle Ressourcen in einem Namespace löschen

3.9 Zusammenfassung

4 Replikationscontroller & Co.: Verwaltete Pods bereitstellen

4.1 Pods funktionsfähig halten

4.1.1 Aktivitätssonden

4.1.2 HTTP-Aktivitätssonden erstellen

4.1.3 Eine Aktivitätssonde in Aktion

4.1.4 Weitere Eigenschaften der Aktivitätssonde festlegen

4.1.5 Wirkungsvolle Aktivitätssonden erstellen

4.2 Replikationscontroller

4.2.1 Die Funktionsweise von Replikationscontrollern

4.2.2 Einen Replikationscontroller erstellen

4.2.3 Der Replikationscontroller in Aktion

4.2.4 Pods in den Gültigkeitsbereich eines Replikationscontrollers bringen und daraus entfernen

4.2.5 Das Pod-Template ändern

4.2.6 Pods horizontal skalieren

4.2.7 Einen Replikationscontroller löschen

4.3 Replikationssätze anstelle von Replikationscontrollern verwenden

4.3.1 Replikationssätze und Replikationscontroller im Vergleich

4.3.2 Einen Replikationssatz definieren

4.3.3 Einen Replikationssatz erstellen und untersuchen

4.3.4 Die ausdrucksstärkeren Labelselektoren des Replikationssatzes

4.3.5 Zusammenfassung: Replikationssätze

4.4 Daemonsets zur Ausführung einer Instanz eines Pods auf jedem Knoten

4.4.1 Einen Pod auf allen Knoten ausführen

4.4.2 Einen Pod nur auf einigen Knoten ausführen

4.5 Pods für endliche Aufgaben

4.5.1 Jobs

4.5.2 Einen Job definieren

4.5.3 Ein Job in Aktion

4.5.4 Mehrere Podinstanzen in einem Job ausführen

4.5.5 Die Zeit zum Abschließen eines Job-Pods begrenzen

4.6 Jobs regelmäßig oder zu einem späteren Zeitpunkt ausführen

4.6.1 Einen Cron-Job erstellen

4.6.2 Die Ausführung geplanter Jobs

4.7 Zusammenfassung

5 Dienste: Pods finden und mit ihnen kommunizieren

5.1 Dienste

5.1.1 Dienste erstellen

5.1.2 Dienste finden

5.2 Verbindungen zu Diensten außerhalb des Clusters

5.2.1 Dienstendpunkte

5.2.2 Manuell eingerichtete Dienstendpunkte

5.2.3 Einen Alias für einen externen Dienst erstellen

5.3 Dienste für externe Clients verfügbar machen

5.3.1 Einen NodePort-Dienst verwenden

5.3.2 Einen Dienst über einen externen Load Balancer verfügbar machen

5.3.3 Besondere Eigenschaften von externen Verbindungen

5.4 Dienste über eine Ingress-Ressource extern verfügbar machen

5.4.1 Eine Ingress-Ressource erstellen

5.4.2 Über den Ingress auf den Dienst zugreifen

5.4.3 Mehrere Dienste über denselben Domänennamen verfügbar machen

5.4.4 Einen Ingress für TLS-Datenverkehr einrichten

5.5 Die Bereitschaft eines Pods zur Annahme von Verbindungen signalisieren

5.5.1 Bereitschaftssonden

5.5.2 Einem Pod eine Bereitschaftssonde hinzufügen

5.5.3 Bereitschaftssonden in der Praxis

5.6 Headless-Dienste zur Ermittlung einzelner Pods

5.6.1 Einen headless-Dienst erstellen

5.6.2 Pods über DNS finden

5.6.3 Alle Pods finden – auch diejenigen, die nicht bereit sind

5.7 Fehlerbehebung bei Diensten

5.8 Zusammenfassung

6 Volumes: Festplattenspeicher zu Containern hinzufügen

6.1 Volumes

6.1.1 Ein Beispiel

6.1.2 Arten von Volumes

6.2 Gemeinsame Datennutzung durch die Container

6.2.1 emptyDir-Volumes

6.2.2 Ein Git-Repository als Ausgangspunkt für ein Volume verwenden

6.3 Zugriff auf Dateien im Dateisystem des Arbeitsknotens

6.3.1 HostPath-Volumes

6.3.2 Systempods mit hostPath-Volumes

6.4 Dauerhafte Speicherung

6.4.1 Eine GCE Persistent Disk in einem Pod-Volume

6.4.2 Andere Arten von Volumes mit zugrunde liegendem persistenten Speicher

6.5 Pods von der zugrunde liegenden Speichertechnologie entkoppeln

6.5.1 Persistente Volumes und Claims

6.5.2 Ein persistentes Volume erstellen

6.5.3 Mit einem Claim ein persistentes Volume beanspruchen

6.5.4 Einen Claim in einem Pod verwenden

6.5.5 Vorteile der Verwendung von persistenten Volumes und Claims

6.5.6 Persistente Volumes wiederverwenden

6.6 Persistente Volumes dynamisch bereitstellen

6.6.1 Die verfügbaren Speichertypen mit Speicherklassen definieren

6.6.2 Die Speicherklasse in einem Claim angeben

6.6.3 Dynamische Bereitstellung ohne Angabe einer Speicherklasse

6.7 Zusammenfassung

7 Konfigurationszuordnungen und Secrets: Anwendungen konfigurieren

7.1 Konfiguration von Anwendungen im Allgemeinen

7.2 Befehlszeilenargumente an Container übergeben

7.2.1 Den Befehl und die Argumente in Docker definieren

7.2.2 Den Befehl und die Argumente in Kubernetes überschreiben

7.3 Umgebungsvariablen für einen Container einrichten

7.3.1 Eine Umgebungsvariable in einer Containerdefinition festlegen

7.3.2 Im Wert einer Variablen auf andere Umgebungsvariablen verweisen

7.3.3 Die Nachteile hartkodierter Umgebungsvariablen

7.4 Die Konfiguration mit einer Konfigurationszuordnung entkoppeln

7.4.1 Einführung in Konfigurationszuordnungen

7.4.2 Eine Konfigurationszuordnung erstellen

7.4.3 Einen Konfigurationseintrag als Umgebungsvariable an einen Container übergeben

7.4.4 Alle Einträge einer Konfigurationszuordnung auf einmal als Umgebungsvariablen übergeben

7.4.5 Einen Konfigurationseintrag als Befehlszeilenargument übergeben

7.4.6 Konfigurationsdateien mithilfe eines configMap-Volumes verfügbar machen

7.4.7 Die Konfiguration einer Anwendung ohne Neustart ändern

7.5 Sensible Daten mithilfe von Geheimnissen an Container übergeben

7.5.1 Einführung in Geheimnisse

7.5.2 Das Geheimnis default-token

7.5.3 Ein Geheimnis erstellen

7.5.4 Unterschiede zwischen Konfigurationszuordnungen und Geheimnissen

7.5.5 Das Geheimnis in einem Pod verwenden

7.5.6 Geheimnisse zum Abrufen von Images

7.6 Zusammenfassung

8 Von Anwendungen aus auf Podmetadaten und andere Ressourcen zugreifen

8.1 Metadaten über die Downward-API übergeben

8.1.1 Die verwendbaren Metadaten

8.1.2 Metadaten über Umgebungsvariablen verfügbar machen

8.1.3 Metadaten über Dateien in einem downwardAPI-Volume übergeben

8.2 Kommunikation mit dem Kubernetes-API-Server

8.2.1 Die REST-API von Kubernetes

8.2.2 Von einem Pod aus mit dem API-Server kommunizieren

8.2.3 Botschaftercontainer zur Vereinfachung der Kommunikation mit dem API-Server

8.2.4 Clientbibliotheken zur Kommunikation mit dem API-Server

8.3 Zusammenfassung

9 Deployments: Anwendungen deklarativ aktualisieren

9.1 Anwendungen in Pods aktualisieren

9.1.1 Alte Pods löschen und anschließend durch neue ersetzen

9.1.2 Neue Pods starten und danach die alten löschen

9.2 Automatische schrittweise Aktualisierung mit einem Replikationscontroller

9.2.1 Die ursprüngliche Version der Anwendung ausführen

9.2.2 Die schrittweise Aktualisierung mit kubectl durchführen

9.2.3 Warum ist kubectl rolling-update veraltet?

9.3 Deployments zur deklarativen Verwaltung von Anwendungen

9.3.1 Ein Deployment erstellen

9.3.2 Ein Deployment aktualisieren

9.3.3 Eine Bereitstellung zurücknehmen

9.3.4 Die Rolloutrate festlegen

9.3.5 Den Rolloutvorgang anhalten

9.3.6 Das Rollout fehlerhafter Versionen verhindern

9.4 Zusammenfassung

10 StatefulSets: Replizierte statusbehaftete Anwendungen bereitstellen

10.1 Statusbehaftete Pods replizieren

10.1.1 Mehrere Replikate mit jeweils eigenem Speicher ausführen

10.1.2 Eine unveränderliche Identität für jeden Pod bereitstellen

10.2 Statussätze

10.2.1 Statussätze und Replikationssätze im Vergleich

10.2.2 Unveränderliche Netzwerkidentität

10.2.3 Eigenen beständigen Speicher für jede Podinstanz zuweisen

10.2.4 Garantien von Statussätzen

10.3 Statussätze nutzen

10.3.1 Die Anwendung und das Containerimage erstellen

10.3.2 Die Anwendung mithilfe eines Statussatzes bereitstellen

10.3.3 Die Pods untersuchen

10.4 Peers im Statussatz finden

10.4.1 Die Peer-Ermittlung über DNS einrichten

10.4.2 Einen Statussatz aktualisieren

10.4.3 Den Clusterdatenspeicher ausprobieren

10.5 Umgang mit Knotenausfällen

10.5.1 Die Trennung eines Knotens vom Netzwerk simulieren

10.5.2 Den Pod manuell löschen

10.6 Zusammenfassung

Teil III: Fortgeschrittene Themen

11 Interne Mechanismen von Kubernetes

11.1 Die Architektur

11.1.1 Die verteilte Natur der Kubernetes-Komponenten

11.1.2 Verwendung von etcd

11.1.3 Aufgaben des API-Servers

11.1.4 Benachrichtigungen des API-Servers über Ressourcenänderungen

11.1.5 Der Scheduler

11.1.6 Die Controller im Controller-Manager

11.1.7 Die Rolle des Kubelets

11.1.8 Die Rolle des Kubernetes-Dienstproxys

11.1.9 Kubernetes-Add-ons

11.1.10 Zusammenfassung

11.2 Kooperation der Controller

11.2.1 Die betroffenen Komponenten

11.2.2 Die Abfolge der Ereignisse

11.2.3 Clusterereignisse beobachten

11.3 Laufende Pods

11.4 Das Podnetzwerk

11.4.1 Anforderungen an das Netzwerk

11.4.2 Funktionsweise des Netzwerks

11.4.3 CNI

11.5 Implementierung von Diensten

11.5.1 Der Kube-Proxy

11.5.2 Iptables-Regeln

11.6 Cluster mit hoher Verfügbarkeit

11.6.1 Anwendungen hochverfügbar machen

11.6.2 Die Komponenten der Kubernetes-Steuerebene hochverfügbar machen

11.7 Zusammenfassung

12 Sicherheit des Kubernetes-API-Servers

12.1 Authentifizierung

12.1.1 Benutzer und Gruppen

12.1.2 Dienstkonten

12.1.3 Dienstkonten erstellen

12.1.4 Ein Dienstkonto mit einem Pod verknüpfen

12.2 Rollengestützte Zugriffssteuerung

12.2.1 Das RBAC-Autorisierungs-Plug-in

12.2.2 RBAC-Ressourcen

12.2.3 Rollen und Rollenbindungen

12.2.4 Clusterrollen und Clusterrollenbindungen

12.2.5 Standardclusterrollen und -clusterrollenbindungen

12.2.6 Berechtigungen bedachtsam gewähren

12.3 Zusammenfassung

13 Sicherheit der Clusterknoten und des Netzwerks

13.1 Die Namespaces des Hostknotens in einem Pod verwenden

13.1.1 Den Netzwerknamespace des Knotens in einem Pod verwenden

13.1.2 Bindung an einen Hostport ohne Verwendung des Host-Netzwerknamespace

13.1.3 Den PID- und den IPC-Namespace des Knotens verwenden

13.2 Den Sicherheitskontext eines Containers einrichten

13.2.1 Einen Container unter einer bestimmten Benutzer-ID ausführen

13.2.2 Die Ausführung eines Containers als root verhindern

13.2.3 Pods im privilegierten Modus ausführen

13.2.4 Einem Container einzelne Kernelfähigkeiten hinzufügen

13.2.5 Fähigkeiten von einem Container entfernen

13.2.6 Prozesse am Schreiben im Dateisystem des Containers hindern

13.2.7 Gemeinsame Nutzung von Volumes durch Container mit verschiedenen Benutzer-IDs

13.3 Die Bearbeitung der Sicherheitsmerkmale in einem Pod einschränken

13.3.1 Podsicherheitsrichtlinien

13.3.2 Die Richtlinien runAsUser, fsGroup und supplementalGroups

13.3.3 Zulässige, unzulässige und Standardfähigkeiten festlegen

13.3.4 Die verwendbaren Arten von Volumes einschränken

13.3.5 Benutzern und Gruppen unterschiedliche Podsicherheitsrichtlinien zuweisen

13.4 Das Podnetzwerk isolieren

13.4.1 Die Netzwerkisolierung in einem Namespace aktivieren

13.4.2 Einzelnen Pods im Namespace die Verbindung zu einem Serverpod erlauben

13.4.3 Das Netzwerk zwischen Kubernetes-Namespaces isolieren

13.4.4 Verwendung der CIDR-Schreibweise zur Isolierung

13.4.5 Den ausgehenden Datenverkehr von Pods einschränken

13.5 Zusammenfassung

14 Die Computerressourcen eines Pods verwalten

14.1 Ressourcen für die Container eines Pods anfordern

14.1.1 Pods mit Ressourcenanforderungen erstellen

14.1.2 Einfluss der Ressourcenanforderungen auf die Zuteilung zu Knoten

14.1.3 Der Einfluss der CPU-Anforderungen auf die CPU-Zeitzuteilung

14.1.4 Benutzerdefinierte Ressourcen definieren und anfordern

14.2 Die verfügbaren Ressourcen für einen Container einschränken

14.2.1 Harte Grenzwerte für die von einem Container verwendeten Ressourcen festlegen

14.2.2 Überschreiten der Grenzwerte

14.2.3 Grenzwerte aus der Sicht der Anwendungen in den Containern

14.3 QoS-Klassen für Pods

14.3.1 Die QoS-Klasse eines Pods festlegen

14.3.2 Auswahl des zu beendenden Prozesses bei zu wenig Speicher

14.4 Standardanforderungen und -grenzwerte für die Pods in einem Namespace festlegen

14.4.1 Der Grenzwertbereich

14.4.2 Einen Grenzwertbereich erstellen

14.4.3 Die Grenzwerte durchsetzen

14.4.4 Standardanforderungen und -grenzwerte anwenden

14.5 Die in einem Namespace insgesamt verfügbaren Ressourcen beschränken

14.5.1 Ressourcenkontingente

14.5.2 Kontingente für persistenten Speicher festlegen

14.5.3 Die Höchstzahl der Objekte in einem Namespace beschränken

14.5.4 Kontingente für einzelne Podstatus und QoS-Klassen festlegen

14.6 Die Ressourcennutzung der Pods überwachen

14.6.1 Die tatsächliche Ressourcennutzung erfassen

14.6.2 Verlaufsdaten des Ressourcenverbrauchs speichern und analysieren

14.7 Zusammenfassung

15 Automatische Skalierung von Pods und Clusterknoten

15.1 Automatische horizontale Podskalierung

15.1.1 Der Vorgang der automatischen Skalierung

15.1.2 Skalierung auf der Grundlage der CPU-Nutzung

15.1.3 Skalierung auf der Grundlage der Speichernutzung

15.1.4 Skalierung auf der Grundlage anderer Messgrößen

15.1.5 Geeignete Messgrößen für die automatische Skalierung auswählen

15.1.6 Herunterskalieren auf null Replikate

15.2 Automatische vertikale Podskalierung

15.2.1 Ressourcenanforderungen automatisch einrichten

15.2.2 Ressourcenanforderungen von laufenden Pods ändern

15.3 Horizontale Skalierung von Clusterknoten

15.3.1 Der Cluster-Autoskalierer

15.3.2 Den Cluster-Autoskalierer aktivieren

15.3.3 Die Unterbrechung von Diensten beim Herunterskalieren des Clusters minimieren

15.4 Zusammenfassung

16 Erweiterte Planung

16.1 Pods mithilfe von Mängeln und Tolerierungen von bestimmten Knoten fernhalten

16.1.1 Mängel und Tolerierungen

16.1.2 Einem Knoten benutzerdefinierte Mängel hinzufügen

16.1.3 Tolerierungen zu Pods hinzufügen

16.1.4 Verwendungszwecke für Mängel und Tolerierungen

16.2 Knotenaffinität

16.2.1 Feste Knotenaffinitätsregeln aufstellen

16.2.2 Knotenprioritäten bei der Zuteilung eines Pods

16.3 Pods mit Affinitäts- und Antiaffinitätsregeln auf denselben Knoten unterbringen

16.3.1 Podaffinitätsregeln zur Bereitstellung von Pods auf demselben Knoten

16.3.2 Pods im selben Schaltschrank, in derselben Verfügbarkeitszone oder derselben geografischen Region bereitstellen

16.3.3 Präferenzen statt fester Regeln für die Podaffinität angeben

16.3.4 Pods mit Antiaffinitätsregeln voneinander getrennt halten

16.4 Zusammenfassung

17 Best Practices für die Anwendungsentwicklung

17.1 Das Gesamtbild

17.2 Der Lebenszyklus eines Pods

17.2.1 Beendigung und Verlegung von Anwendungen

17.2.2 Tote oder teilweise tote Pods neu bereitstellen

17.2.3 Pods in einer bestimmten Reihenfolge starten

17.2.4 Lebenszyklushooks

17.2.5 Pods herunterfahren

17.3 Die ordnungsgemäße Verarbeitung aller Clientanforderungen sicherstellen

17.3.1 Unterbrechungen von Clientverbindungen beim Hochfahren eines Pods verhindern

17.3.2 Unterbrechungen von Clientverbindungen beim Herunterfahren eines Pods verhindern

17.4 Einfache Ausführung und Handhabung von Anwendungen in Kubernetes

17.4.1 Einfach zu handhabende Containerimages erstellen

17.4.2 Images sauber kennzeichnen

17.4.3 Mehrdimensionale statt eindimensionaler Labels

17.4.4 Ressourcen mit Anmerkungen beschreiben

17.4.5 Gründe für die Beendigung eines Prozesses angeben

17.4.6 Anwendungsprotokolle

17.5 Empfohlene Vorgehensweisen für Entwicklung und Tests

17.5.1 Anwendungen während der Entwicklung außerhalb von Kubernetes ausführen

17.5.2 Minikube für die Entwicklung

17.5.3 Versionssteuerung und Manifeste zur automatischen Bereitstellung von Ressourcen

17.5.4 Ksonnet als Alternative zu YAML- und JSON-Manifesten

17.5.5 Continuous Integration und Continuous Delivery (CI/CD)

17.6 Zusammenfassung

18 Kubernetes erweitern

18.1 Eigene API-Objekte definieren

18.1.1 Eigene Ressourcendefinitionen

18.1.2 Benutzerdefinierte Ressourcen mit benutzerdefinierten Controllern automatisieren

18.1.3 Benutzerdefinierte Objekte validieren

18.1.4 Einen benutzerdefinierten API-Server für benutzerdefinierte Objekte bereitstellen

18.2 Kubernetes mit dem Kubernetes-Dienstkatalog erweitern

18.2.1 Der Dienstkatalog

18.2.2 Der API-Server des Dienstkatalogs und der Controller-Manager

18.2.3 Dienstbroker und die API OpenServiceBroker

18.2.4 Dienste bereitstellen und nutzen

18.2.5 Aufheben der Bindung und der Bereitstellung

18.2.6 Vorteile des Dienstkatalogs

18.3 Plattformen auf der Grundlage von Kubernetes

18.3.1 Die Containerplattform Red Hat OpenShift

18.3.2 Deis Workflow und Helm

18.4 Zusammenfassung

Anhang A: Verwendung von kubectl für mehrere Cluster

A.1 Umschalten zwischen Minikube und Google Kubernetes Engine

A.1.1 Umschalten zu Minikube

A.1.2 Umschalten zu GKE

A.2 Verwendung von kubectl für mehrere Cluster oder Namespaces

A.2.1 Den Speicherort der Konfigurationsdatei festlegen

A.2.2 Der Inhalt der Konfigurationsdatei

A.2.3 Konfigurationseinträge auflisten, hinzufügen und ändern

A.2.4 Verwendung von kubectl mit verschiedenen Clustern, Benutzern und Kontexten

A.2.5 Umschalten zwischen Kontexten

A.2.6 Kontexte und Cluster auflisten

A.2.7 Kontexte und Cluster löschen

Anhang B: Einen Cluster mit mehreren Knoten mit kubeadm erstellen

B.1 Das Betriebssystem und die erforderlichen Pakete einrichten

B.1.1 Die virtuelle Maschine erstellen

B.1.2 Den Netzwerkadapter für die VM einrichten

B.1.3 Das Betriebssystem installieren

B.1.4 Docker und Kubernetes installieren

B.1.5 Die VM klonen

B.2 Den Master mit kubeadm konfigurieren

B.2.1 Ausführung der Komponenten durch kubeadm

B.3 Arbeitsknoten mit kubeadm einrichten

B.3.1 Das Containernetzwerk einrichten

B.4 Vom lokalen Computer auf den Cluster zugreifen

Anhang C: Andere Containerlaufzeitumgebungen verwenden

C.1 Docker durch rkt ersetzen

C. 1.1 Kubernetes zur Verwendung von rkt einrichten

C.1.2 rkt in Minikube ausprobieren

C.2 Andere Containerlaufzeiten über die CRI verwenden

C.2.1 CRI-O

C.2.2 Anwendungen in VMs statt in Containern ausführen

Anhang D: Clusterverbund

D.1 Der Kubernetes-Clusterverbund

D.2 Die Architektur

D.3 Verbund-API-Objekte

D.3.1 Verbundversionen der Kubernetes-Ressourcen

D.3.2 Funktionsweise von Verbundressourcen

Anhang E: Kubernetes-Ressourcen in diesem Buch

Vorwort

Nachdem ich schon einige Jahre für Red Hat gearbeitet hatte, wurde ich Ende 2014 einem neuen Team namens Cloud Enablement zugeteilt. Unsere Aufgabe bestand darin, die Middleware-Produktpalette unseres Unternehmens auf die Containerplattform OpenShift zu übertragen, die zu diesem Zeitpunkt auf der Grundlage von Kubernetes entwickelt wurde. Damals steckte Kubernetes noch in den Kinderschuhen – noch nicht einmal Version 1.0 war veröffentlicht worden!

In unserem Team mussten wir uns mit den Interna von Kubernetes schnell vertraut machen, um unsere Software in die richtige Richtung lenken und alle Möglichkeiten ausnutzen zu können, die Kubernetes bot. Wenn wir auf ein Problem stießen, konnten wir oft schlecht unterscheiden, ob wir irgendetwas falsch machten oder ob es an einem der Bugs lag, unter denen Kubernetes in seiner Frühzeit litt.

Sowohl Kubernetes als auch meine Kenntnisse darüber haben sich seitdem erheblich weiterentwickelt. Als ich begann, damit zu arbeiten, hatten die meisten noch nie davon gehört. Heute kennt es praktisch jeder Softwareentwickler, und unter allen Möglichkeiten, um Anwendungen sowohl in der Cloud als auch in Rechenzentren am eigenen Standort auszuführen, gehört es zu denen mit der weitesten Verbreitung und dem schnellsten Wachstum.

Während der ersten Monate meiner Arbeit mit Kubernetes schrieb ich einen zweiteiligen Blogpost darüber, wie man einen JBoss-WildFly-Anwendungsservercluster in OpenShift/Kubernetes ausführt. Damals ahnte ich es noch nicht, aber dieser einfache Post führte schließlich dazu, dass der Verlag Manning mit der Bitte an mich herantrat, ein Buch über Kubernetes zu schreiben. Natürlich konnte ich ein solches Angebot nicht ablehnen, obwohl ich sicher war, dass Manning auch andere mögliche Autoren angesprochen hatte und sich letzten Endes für jemand anderen entscheiden würde.

Wie Sie sehen, geschah das nicht. Nach mehr als anderthalb Jahren Schreib- und Recherchearbeit ist das Buch nun fertig geworden. Es war ein großartiges Erlebnis. Ein Buch über ein technisches Thema zu schreiben ist die beste Möglichkeit, um die betreffende Technologie viel ausführlicher kennenzulernen, als es durch reine Anwendung möglich wäre. Da sich nicht nur meine Kenntnisse über Kubernetes erweitert haben, sondern Kubernetes selbst weiterentwickelt wurde, musste ich ständig bereits fertiggestellte Kapitel umschreiben und ergänzen. Als Perfektionist werde ich niemals absolut zufrieden mit diesem Buch sein, aber ich freue mich zu hören, dass viele Personen, die Vorabversionen über das Manning Early Access Program gelesen haben, es für einen großartigen Leitfaden zum Thema Kubernetes halten.

Mein Ziel bestand darin, den Lesern die Technologie von Kubernetes nahezubringen und den Gebrauch der Werkzeuge für eine wirkungsvolle und rationelle Entwicklung und Bereitstellung von Anwendungen in Kubernetes-Clustern vorzuführen. Die Einrichtung und Wartung eines hochverfügbaren Kubernetes-Clusters ist jedoch kein Schwerpunkt dieses Buchs, allerdings dürfte Ihnen der letzte Teil solide Kenntnisse darüber vermitteln, woraus ein solcher Cluster besteht, sodass Sie andere Quellen zu diesem Thema besser verstehen können.

Ich hoffe, dass Sie die Lektüre genießen können und das Buch Ihnen zeigt, wie Sie den größten Nutzen aus Kubernetes ziehen können.

Der Autor

Marko Lukša ist Softwareentwickler mit mehr als 20 Jahren Be­rufserfahrung, wobei die Palette seiner Projekte von einfachen Webanwendungen bis zu vollständigen ERP-Systemen, Frameworks und Middleware reicht. Seine ersten Programmierversuche hat er bereits 1985 im Alter von sechs Jahren auf einem ZX Spectrum gemacht, den sein Vater gebraucht für ihn gekauft hatte. In der Grundschule wurde er Landesmeister im Logo-Programmierwettbewerb und nahm an Programmierferienlagern teil, in denen er Pascal lernte. Seitdem hat er Software in einer breiten Palette von Programmiersprachen entwickelt.

In der weiterführenden Schule begann er damit, dynamische Websites zu erstellen, als das Web noch ziemlich jung war. Während seines Studiums der Informatik an der Universität von Ljubljana in Slowenien entwickelte er bei einem ortsansässigen Unternehmen Software für das Gesundheitswesen und die Telekommunikationsbranche. Schließlich begann er für Red Hat zu arbeiten. Dort entwickelte er zu Anfang eine Open-Source-Implementierung der Google App Engine API, die die Middleware JBoss von Red Hat verwendete. Außerdem arbeitete er an Projekten wie CDI/Weld, Infinispan/JBoss Data Grid u. a. mit.

Seit Ende 2014 gehört er zum Cloud-Enablement-Team von Red Hat. Zu seinen Aufgaben gehört es dabei, sich über die neuesten Entwicklungen bei Kubernetes und verwandten Technologien auf dem neuesten Stand zu halten und dafür zu sorgen, dass die Middleware des Unternehmens die Möglichkeiten von Kubernetes und OpenShift voll ausnutzt.

Danksagung zur englischsprachigen Ausgabe

Bevor ich mit dem Schreiben dieses Buches begann, hatte ich keine Vorstellung davon, wie viele Personen daran beteiligt sind, um aus einem ersten Manuskript eine fertige Veröffentlichung zu machen. Es gibt viele Menschen, denen ich Dank schulde.

Als Erstes möchte ich Erin Twohey danken, die mich gebeten hat, dieses Buch zu schreiben, und Michael Stephens, der von Anfang an volles Vertrauen darin gesetzt hat, dass ich es schaffen kann. Seine ermutigenden Worte haben mich zu Anfang stark motiviert und diese Motivation während der letzten anderthalb Jahre aufrechterhalten.

Ich möchte auch meinem ursprünglichen Entwicklungsredakteur Andrew Warren danken, der mir half, das erste Kapitel fertigzustellen, und seiner Nachfolgerin Elesha Hyde, die mit mir danach bis zum letzten Kapitel gearbeitet hat. Vielen Dank dafür, dass sie es mit mir ausgehalten haben, auch wenn der Umgang mit mir nicht ganz leicht ist und ich ziemlich oft einfach vom Radar verschwinde.

Ich möchte auch Jeanne Boyarsky danken, die als erste Lektorin meine Kapitel las und kommentierte, während ich noch daran schrieb. Jeanne und Elesha trugen erheblich dazu bei, das Buch so gut zu machen, wie es hoffentlich ist. Ohne ihre Kommentare hätte das Buch niemals so positive Bewertungen von externen Gutachtern und Lesern bekommen können.

Ich möchte auch meinem Fachlektor Antonio Magnaghi und natürlich allen externen Gutachtern danken: Al Krinker, Alessandro Campeis, Alexander Myltsev, Csaba Sari, David DiMaria, Elias Rangel, Erisk Zelenka, Fabrizio Cucci, Jared Duncan, Keith Donaldson, Michael Bright, Paolo Antinori, Peter Perlepes und Tiklu Ganguly. Ihre positive Rückmeldung hat mich durchhalten lassen, wenn ich manchmal das Gefühl hatte, mein Text sei fürchterlich geschrieben und völlig nutzlos, und ihre konstruktive Kritik hat mir geholfen, die Abschnitte zu verbessern, die ich ohne große Anstrengung flott zusammengestoppelt hatte. Vielen Dank dafür, dass Sie mich auf schwer verständliche Stellen hingewiesen und Vorschläge zur Verbesserung des Buches gemacht haben. Vielen Dank auch dafür, die richtigen Fragen zu stellen, die mir deutlich machten, dass ich ein oder zwei Dinge in der ursprünglichen Version meines Manuskripts falsch dargestellt hatte.

Ich möchte auch den Lesern danken, die Vorabversionen dieses Buches über das Early-Access-Programm von Manning (MEAP) erworben und ihre Kommentare im Onlineforum abgegeben oder mich direkt angesprochen haben, insbesondere Vimal Kansal, Paolo Patierno und Roland Huß, die einige Inkonsistenzen und andere Fehler gefunden haben. Des Weiteren möchte ich allen Manning-Mitarbeitern danken, die an der Produktion dieses Buches beteiligt waren. Bevor ich zum Schluss komme, möchte ich meinem Kollegen und Schulfreund Aleš Justin danken, der mich zu Red Hat gebracht hat, und meinen wunderbaren Kollegen im Cloud-Enablement-Team. Wäre ich nicht bei Red Hat und in diesem Team gewesen, so wäre ich nicht derjenige gewesen, der dieses Buch geschrieben hat.

Abschließend möchte ich meiner Frau und meinem Sohn danken, die während der letzten 18 Monate mehr als verständnisvoll waren und mich unterstützt haben, obwohl ich mich in mein Büro verkrochen habe, anstatt Zeit mit ihnen zu verbringen.

Vielen Dank euch allen!

Über dieses Buch

Dieses Buch soll Sie zu einem kompetenten Kubernetes-Benutzer machen. Sie lernen hier praktisch alle Prinzipien kennen, die Sie beherrschen müssen, um Anwendungen in einer Kubernetes-Umgebung zu entwickeln und auszuführen.

Bevor es mit Kubernetes losgeht, erhalten Sie einen Überblick über Containertechnologien wie Docker und das Erstellen von Containern, damit Sie den weiteren Ausführungen auch dann folgen können, wenn Sie damit noch nicht gearbeitet haben. Danach werden Sie Schritt für Schritt in alles eingeführt, was Sie über Kubernetes wissen müssen, von den Grundprinzipien zu den verborgenen Mechanismen.

Zielgruppe

Dieses Buch richtet sich hauptsächlich an Anwendungsentwickler, bietet aber auch einen Überblick über die Verwaltung von Anwendungen. Es ist für alle gedacht, die sich für die Ausführung und Verwaltung von Containeranwendungen auf mehr als einem einzigen Server interessieren.

Sowohl Anfänger als auch erfahrene Softwareentwickler, die etwas über Containertechnologien lernen möchten, erhalten hier die notwendigen Kenntnisse, um ihre Anwendungen in einer Kubernetes-Umgebung zu entwickeln und in Containern auszuführen.

Vorkenntnisse in Containertechnologien und Kubernetes sind nicht erforderlich. Die Erklärungen in diesem Buch bauen aufeinander auf, und es wird kein Beispielcode verwendet, der nur für Experten verständlich wäre.

Leser sollten allerdings Grundkenntnisse in Programmierung, Computernetzwerken, einfachen Linux-Befehlen und Standardprotokollen wie HTTP mitbringen.

Der Aufbau dieses Buches

Dieses Buch besteht aus 18 Kapiteln, die in drei Teile gegliedert sind.

Teil 1 gibt eine kurze Einführung in Docker und Kubernetes. Sie erfahren hier, wie Sie einen Kubernetes-Cluster einrichten und darin eine einfache Anwendung ausführen. Dieser Teil umfasst nur zwei Kapitel:

       Kapitel 1 erklärt, was Kubernetes ist, wie es entstand und wie es hilft, die heutigen Probleme der Verwaltung und Skalierung von Anwendungen zu lösen.

       Kapitel 2 enthält eine praktische Anleitung, um ein Containerimage zu erstellen und in einem Kubernetes-Cluster auszuführen. Es erklärt auch, wie Sie lokale Kubernetes-Cluster mit einem Knoten und richtige Cluster mit mehreren Knoten in der Cloud ausführen.

Teil 2 stellt die Grundprinzipien vor, mit denen Sie vertraut sein müssen, um Anwendungen in Kubernetes auszuführen. Er besteht aus folgenden Kapiteln:

       Kapitel 3 stellt die Grundbausteine von Kubernetes vor – die Pods – und erklärt, wie Pods und andere Kubernetes-Objekte mithilfe von Labels geordnet werden können.

       Kapitel 4 erklärt, wie Kubernetes Anwendungen durch den automatischen Neustart von Containern in funktionsfähigem Zustand hält. Hier erfahren Sie auch, wie Sie verwaltete Pods ausführen, horizontal skalieren, gegen den Ausfall von Clusterknoten absichern und zu vorherbestimmten Zeitpunkten oder regelmäßig ausführen.

       Kapitel 5 zeigt, wie Pods ihre Dienste für Clients im und außerhalb des Clusters verfügbar machen und wie Pods im Cluster Dienste innerhalb oder außerhalb des Clusters entdecken und nutzen können.

       Kapitel 6 erklärt, wie mehrere Container im selben Pod Dateien gemeinsam nutzen können und wie Sie persistenten Speicher verwalten und für die Pods zugänglich machen.

       Kapitel 7 zeigt, wie Sie Konfigurationsdaten und sensible Informationen, z. B. Anmeldeinformationen, an Anwendungen innerhalb der Pods übergeben.

       Kapitel 8 beschreibt, wie Anwendungen Informationen über die Kubernetes-Umgebung beziehen, in der sie laufen, und wie sie mit Kubernetes kommunizieren, um den Status des Clusters zu ändern.

       Kapitel 9 gibt eine Einführung in das Prinzip von Deployments und erklärt, wie Sie Anwendungen in einer Kubernetes-Umgebung ordnungsgemäß ausführen und aktualisieren.

       Kapitel 10 stellt eine Möglichkeit zur Ausführung statusbehafteter Anwendungen vor, die gewöhnlich eine stabile Identität benötigen und ihren Status erhalten müssen.

In Teil 3 geht es um die internen Mechanismen von Kubernetes-Clustern. Es werden hier nicht nur einige neue Konzepte eingeführt, sondern auch alle Funktionsprinzipien, die Sie in den ersten beiden Teilen gelernt haben, von einer höheren Warte aus untersucht. Dieser Teil umfasst folgende Kapitel:

       Kapitel 11 stößt unter die Oberfläche von Kubernetes vor und beschreibt die Komponenten, aus denen ein Kubernetes-Cluster besteht, und ihre Funktionsweise. Außerdem wird hier erklärt, wie Pods über das Netzwerk kommunizieren und wie Dienste die Last auf mehrere Pods verteilen.

       Kapitel 12 erklärt, wie Sie Ihren Kubernetes-API-Server und den Cluster mithilfe von Authentifizierung und Autorisierung sicherer gestalten können.

       Kapitel 13 zeigt, wie Pods auf die Ressourcen des Knotens zugreifen und wie ein Clusteradministrator sie daran hindern kann.

       Kapitel 14 beschreibt, wie Sie den Verbrauch von Computerressourcen durch die einzelne Anwendung einschränken, die garantierte Dienstqualität der Anwendungen festlegen, die Ressourcennutzung überwachen und Benutzer daran hindern, zu viele Ressourcen zu verbrauchen.

       Kapitel 15 erklärt, wie Sie Kubernetes einrichten, um die Anzahl der Replikate einer Anwendung automatisch skalieren zu lassen, und wie Sie die Größe des Clusters erhöhen, wenn die vorhandenen Clusterknoten keine weiteren Anwendungen mehr aufnehmen können.

       Kapitel 16 zeigt, wie Sie dafür sorgen, dass Pods bestimmten Knoten zugeteilt oder nicht zugeteilt werden. Außerdem erfahren Sie, wie Sie Pods zusammen oder getrennt zuteilen können.

       Kapitel 17 beschreibt, wie Sie Ihre Anwendungen auf clustergerechte Weise entwickeln sollten. Sie erhalten dabei auch einige Hinweise dazu, wie Sie bei der Entwicklung und dem Testen vorgehen sollten, um Störungen zu vermeiden.

       Kapitel 18 zeigt, wie Sie Kubernetes mit eigenen Objekten erweitern können und wie andere dies bereits getan und damit professionelle Anwendungsplattformen erstellt haben.

Bei der Lektüre werden Sie nicht nur die einzelnen Bestandteile von Kubernetes kennenlernen, sondern auch Ihre Kenntnisse des Befehlszeilentools kubectl nach und nach erweitern.

Der Code

Dieses Buch enthält nicht viel Quellcode, aber dafür eine Menge Manifeste für Kubernetes-Ressourcen im YAML-Format sowie Shellbefehle und deren Ausgabe. All diese Elemente sind in nichtproportionaler Schrift dargestellt, um sie vom normalen Fließtext abzuheben.

Die Shellbefehle sind gewöhnlich in fetter nichtproportionaler Schrift angegeben, um sie von der Ausgabe zu unterscheiden. Manchmal sind jedoch nur die wichtigsten Teile eines Befehls oder auch einige besondere Teile der Ausgabe fett hervorgehoben. Die Ausgabe wurde meistens umformatiert, sodass sie in eine Buchzeile passt. Da das Kubernetes-Befehlszeilenwerkzeug kubectl ständig weiterentwickelt wird, kann es sein, dass sich die Ausgabe neuerer Versionen von dem unterscheidet, was Sie in diesem Buch sehen. Die Listings enthalten oft auch Anmerkungen, die die wichtigsten Teile hervorheben und er­klären.

Alle Beispiele in diesem Buch wurden mit Kubernetes 1.8 in der Google Kubernetes Engine und in einem lokalen Minikube-Cluster getestet. Der vollständige Quellcode und die YAML-Manifeste sind auf https://github.com/luksa/kubernetes-in-action zu finden.

Das Forum zum Buch

Manning Publications, der Herausgeber der Originalausgabe, unterhält ein Forum (in englischer Sprache), in dem Sie das Buch kommentieren, fachliche Fragen stellen und Hilfe sowohl vom Autor als auch von anderen Lesern erhalten können. Dieses Forum finden Sie auf https://forums.manning.com/forums/kubernetes-in-action. Um mehr über die Manning-Foren und die Verhaltensregeln dafür zu erfahren, schauen Sie auf https://forums.manning.com/forums/about nach.

Manning möchte damit eine Möglichkeit für den Austausch zwischen Lesern und zwischen Leser und Autor geben. Der Autor ist dabei in keiner Form zu irgendeiner garantierten Form der Beteiligung verpflichtet, da er alle seine Beiträge freiwillig (und unbezahlt) leistet. Wir raten Ihnen, anspruchsvolle Fragen zu stellen, falls sein Interesse nachlassen sollte. Das Forum und die Archive früherer Diskussionen sind auf der Website von Manning so lange zugänglich, wie die Originalausgabe dieses Buches in Druck ist.

Sonstige Onlinequellen

Informationen über Kubernetes sind auch an folgenden Orten zu finden:

       Auf der Kubernetes-Website https://kubernetes.io

       Im Kubernetes-Blog auf http://blog.kubernetes.io, in dem regelmäßig interessante Informationen erscheinen

       Im Slack-Kanal der Kubernetes-Community auf http://slack.k8s.io

       In den YouTube-Kanälen von Kubernetes und der Cloud Native Computing Foundation:

       https://www.youtube.com/channel/UCZ2bu0qutTOM0tHYa_jkIwg

       https://www.youtube.com/channel/UCvqbFHwN-nwalWPjPUKpvTA

Um mehr über einzelne Themen zu erfahren oder auch um selbst zu Kubernetes beitragen zu können, wenden Sie sich an die Kubernetes-Interessengruppen (Special Interest Groups, SIGs) auf https://github.com/kubernetes/kubernetes/wiki/Special-Interest-Groups-(SIGs).

Da es sich bei Kubernetes um Open-Source-Software handelt, enthält auch der Kubernetes-Quellcode einen Schatz an Informationen. Sie finden ihn in https://github.com/kubernetes/kubernetes und verwandten Repositorys.

Teil I: Überblick

Teil I beinhaltet:

       Einführung in Kubernetes(Kapitel 1)

       Erste Schritte mit Docker und Kubernetes(Kapitel 2)

1Einführung in Kubernetes

Der Inhalt dieses Kapitels

       Veränderungen bei der Entwicklung und Ausführung von Anwendungen

       Isolierung und Ausgleich von Unterschieden zwischen Umgebungen mithilfe von Containern

       Docker und seine Nutzung in Kubernetes

       Vorteile von Kubernetes für Entwickler und Systemadministratoren

Früher waren die meisten Softwareanwendungen riesige, monolithische Einrichtungen, die entweder als ein einziger Prozess oder als eine sehr geringe Anzahl von Prozessen auf nur einer Handvoll Servern liefen. Diese Anwendungen hatten sehr langsame Release-Lebenszyklen und wurden relativ selten aktualisiert. Am Endes eines Release-Zyklus verpackten die Entwickler die Anwendung und übergaben sie an das Betriebsteam, das die Software dann bereitstellte und überwachte – und manuell migrierte, wenn die Hardware, auf der sie lief, versagte.

Heute werden solche großen, monolithischen Anwendungen langsam in kleinere, unabhängige Komponenten zerlegt, sogenannte Microservices. Da sie voneinander entkoppelt sind, können sie einzeln entwickelt, bereitgestellt, aktualisiert und skaliert werden. Dadurch können wir jede davon schnell und sooft wie nötig ändern, um mit den rapide wechselnden geschäftlichen Bedingungen unserer Zeit Schritt zu halten.

Doch mit einer größeren Zahl bereitstellbarer Komponenten und immer riesigeren Rechenzentren wird es zunehmend schwieriger, das gesamte System zu verwalten und ordnungsgemäß am Laufen zu halten. Es wird viel schwerer herauszufinden, wo wir die einzelnen Komponenten platzieren müssen, um für eine ausreichend hohe Ressourcennutzung zu sorgen und die Hardwarekosten niedrig zu halten. All das lässt sich manuell nur sehr schwer erledigen. Wir brauchen eine Form von Automatisierung einschließlich automatischer Zeitplanung, Konfiguration, Überwachung und Fehlerbehandlung. An dieser Stelle kommt Kubernetes ins Spiel.

Kubernetes ermöglicht Entwicklern, ihre Anwendungen selbst und sooft sie wollen bereitzustellen – ohne dazu die Hilfe des Betriebsteams zu benötigen. Es unterstützt auch das Betriebsteam durch die ständige Beobachtung und automatische Neuplanung der betreffenden Anwendungen im Fall eines Hardwareausfalls. Der Schwerpunkt der Arbeit von Systemadministratoren verlagert sich von der Überwachung einzelner Anwendungen größtenteils zur Überwachung und Verwaltung von Kubernetes und der restlichen Infrastruktur.

HINWEIS:Kubernetes ist das griechische Wort für einen Lotsen oder Steuermann.

Kubernetes verdeckt die Hardwareinfrastruktur und stellt Ihr gesamtes Rechenzentrum als eine einzige, enorme Rechenressource dar. Dadurch können Sie Ihre Softwarekomponenten bereitstellen und ausführen, ohne sich darum zu kümmern, welche Server konkret unterhalb dieser Schicht laufen. Bei der Bereitstellung von Anwendungen mit mehreren Komponenten wählt Kubernetes für jede dieser Komponenten einen Server aus, stellt sie bereit und ermöglicht es ihr, die anderen Komponenten zu finden und mit ihnen zu kommunizieren.

Zwar stellt Kubernetes schon eine hervorragende Lösung für die lokale Infrastruktur einzelner Unternehmen dar, doch seinen wahren Nutzen zeigt es erst in großen Rechenzentren, z. B. denen von Cloudanbietern. Es ermöglicht ihnen, Entwicklern eine einfachere Plattform zur Bereitstellung und Ausführung beliebiger Arten von Anwendungen bieten können, ohne dass die Systemadministratoren des Providers etwas über die Tausenden oder gar Millionen von Anwendungen wissen müssten, die auf ihrer Hardware laufen.

Da mehr und mehr Unternehmen das Kubernetes-Modell als die beste Möglichkeit zur Ausführung von Anwendungen akzeptieren, ist es auf dem besten Wege, zum Standard zur Ausführung von verteilten Anwendungen in Cloud sowie in den örtlichen Infrastrukturen von Firmenstandorten zu werden.

1.1Der Bedarf für ein System wie Kubernetes

Bevor wir Kubernetes ausführlicher kennenlernen, wollen wir uns zunächst kurz ansehen, wie sich die Entwicklung und Bereitstellung von Anwendungen in den letzten Jahren verändert hat. Dieser Wandel ist eine Folge der Aufteilung großer, monolithischer Anwendungen in kleinere Microservices und der Änderungen in der Infrastruktur, in der sie ausgeführt werden. Wenn Sie diese Änderungen kennen, werden Sie die Vorteile der Verwendung von Kubernetes und Containertechnologien wie Docker besser schätzen können.

1.1.1Von monolithischen Anwendungen zu Microservices

Monolithische Anwendungen bestehen aus Komponenten, die eng gekoppelt sind und gemeinsam entwickelt, bereitgestellt und verwaltet werden, da sie alle als ein einziger Betriebssystemprozess laufen. Änderungen an einem Teil der Anwendung erfordern eine erneute Bereitstellung der gesamten Anwendung. Der Mangel an festen Grenzen zwischen den Einzelteilen führt mit der Zeit zu erhöhter Komplexität und aufgrund der uneingeschränkten Vermehrung von Abhängigkeiten zwischen den Teilen zu sinkender Qualität des Gesamtsystems.

Die Ausführung einer monolithischen Anwendung erfordert gewöhnlich einen leistungsstarken Server, der ausreichend Ressourcen für die gesamte Anwendung bieten kann. Um mit der wachsenden Belastung des Systems fertig zu werden, müssen Sie entweder den Server vertikal skalieren, indem Sie noch mehr CPUs, Arbeitsspeicher und andere Serverkomponenten hinzufügen (Scale-up), oder das gesamte System horizontal skalieren, indem Sie mehrere Server betreiben, die jeweils eine eigene Instanz ausführen (Scale-out). Bei der vertikalen Skalierung sind zwar keine Änderungen an der Anwendung erforderlich, aber dafür wird diese Vorgehensweise relativ schnell teuer und funktioniert auch nur bis zu einer bestimmten Obergrenze. Für die horizontale Skalierung dagegen können erhebliche Änderungen am Anwendungscode notwendig sein, und sie ist auch nicht in allen Fällen möglich. Manche Teile eines Systems lassen sich nur extrem schwierig oder gar nicht horizontal skalieren (z. B. relationale Datenbanken). Ist aber ein Teil einer monolithischen Anwendung nicht skalierbar, so ist die ganze Anwendung nicht skalierbar, es sei denn, es gelingt Ihnen auf irgendeine Weise, sie aufzuteilen.

Anwendungen in Microservices aufteilen

Diese und andere Probleme haben uns dazu gebracht, komplexe, monolithische Anwendungen in kleinere, unabhängig bereitstellbare Komponenten aufzuteilen, die als Microservices bezeichnet werden. Jeder Microservice wird als unabhängiger Prozess ausgeführt (siehe Bild 1.1) und kommuniziert durch einfache, wohldefinierte Schnittstellen (APIs) mit anderen Microservices.

Bild 1.1Komponenten monolithischer Anwendungen und eigenständiger Microservices im Vergleich

Diese Kommunikation der Microservices erfolgt entweder über synchrone Protokolle wie HTTP, über die gewöhnlich REST-fähige APIs (Representational State Transfer) zur Verfügung stehen, oder über asynchrone wie AMQP (Advanced Message Queuing Protocol). Diese Protokolle sind den meisten Entwicklern gut bekannt und nicht an bestimmte Programmiersprachen gebunden. Dadurch kann jeder Microservice jeweils in der Sprache geschrieben werden, die am besten für ihn geeignet ist.

Da jeder Microservice ein eigenständiger Prozess mit einer relativ statischen externen API ist, lassen sich die einzelnen Microservices getrennt voneinander entwickeln und bereitstellen. Eine Änderung an einem von ihnen erfordert keine Änderung oder Neubereitstellung der anderen, sofern sich die API nicht oder nur in einer abwärtskompatiblen Weise ändert.

Microservices skalieren

Im Gegensatz zu monolithischen Systemen, die wir als Ganzes skalieren müssen, lassen sich Microservices einzeln skalieren. Das bedeutet, dass Sie die Möglichkeit haben, nur die Dienste zu skalieren, die mehr Ressourcen benötigen, während sie andere in ihrer bisherigen Größenordnung belassen. In dem Beispiel aus Bild 1.2 werden einige Komponenten repliziert und in Form mehrerer Prozesse auf verschiedenen Servern ausgeführt, während andere als ein einziger Anwendungsprozess laufen. Wenn sich eine monolithische Anwendung nicht im Ganzen horizontal skalieren lässt, da einige ihrer Teile nicht skalierbar sind, können wir sie in Microservices aufteilen und dann die Teile horizontal skalieren, bei denen es möglich ist, und die anderen vertikal.

Bild 1.2Jeder Microservice kann einzeln skaliert werden.

Microservices bereitstellen

Natürlich haben Microservices auch Nachteile. Wenn Ihr System nur aus einer geringen Anzahl bereitstellbarer Komponenten besteht, dann ist deren Verwaltung einfach. Die Entscheidung, wo die einzelnen Komponenten bereitgestellt werden sollen, ist trivial, da es nicht so viele Möglichkeiten gibt. Mit steigender Anzahl der Komponenten aber werden die Bereitstellungsentscheidungen immer schwieriger, da nicht nur die Anzahl der Bereitstellungskombinationen zunimmt, sondern auch die Anzahl der Abhängigkeiten zwischen den Komponenten.

Microservices erledigen ihre Aufgaben sozusagen im Team, weshalb sie einander finden und miteinander kommunizieren müssen. Bei ihrer Bereitstellung müssen sie von irgendjemand oder irgendetwas ordnungsgemäß konfiguriert werden, sodass sie als ein System zusammenarbeiten können. Mit zunehmender Anzahl von Microservices wird diese Aufgabe jedoch mühselig und fehleranfällig. Denken Sie nur daran, was das Betriebs- oder Administrationsteam alles tun muss, wenn ein Server ausfällt!

Darüber hinaus bringen Microservices noch weitere Probleme mit sich. Beispielsweise wird es schwierig, Ausführungsaufrufe zu debuggen und zu verfolgen, da sie mehrere Prozesse und Computer überspannen. Zum Glück gibt es durch verteilte Ablaufverfolgungssysteme wie Zipkin inzwischen Lösungen für diese Probleme.

Unterschiedliche Anforderungen

Wie bereits erwähnt, werden Microservices als unabhängige Komponenten entwickelt und ausgeführt. Gewöhnlich werden die einzelnen Dienste von unterschiedlichen Teams entwickelt, und aufgrund dieser Unabhängigkeit hindert die Teams nichts daran, eine beliebige Bibliothek zu verwenden und sie zu wechseln, wann immer es nötig ist. Dies führt dazu, dass die einzelnen Anwendungskomponenten unterschiedliche Abhängigkeiten aufweisen. Die Situation aus Bild 1.3, in der die Anwendungen oder kurz Apps jeweils verschiedene Versionen derselben Bibliotheken benötigen, ist dabei unvermeidlich.

Bild 1.3Mehrere Anwendungen auf demselben Host können unterschiedliche Abhängigkeiten aufweisen.

Dynamisch verlinkte Anwendungen, die unterschiedliche Versionen gemeinsamer Bibliotheken oder unterschiedliche Umgebungen erfordern, auf Produktionsservern bereitzustellen und zu verwalten, kann sich für die dafür zuständigen Systemadministratoren schnell zu einem Albtraum auswachsen. Je mehr Komponenten auf einem Host bereitgestellt werden müssen, umso schwieriger wird es, all ihre Abhängigkeiten zu verwalten, um ihre Anforderungen zu erfüllen.

1.1.2Eine konsistente Umgebung für Anwendungen bereitstellen

Unabhängig von der Anzahl der entwickelten und bereitgestellten Komponenten besteht eines der größten Probleme, mit denen die Entwicklungs- und Betriebsteams immer zu kämpfen haben, darin, dass es immer enorme Unterschiede zwischen den Umgebungen gibt, in denen Anwendungen ausgeführt werden. Dabei handelt es sich nicht nur um die Unterschiede zwischen der Entwicklungs- und der Produktionsumgebung, sondern auch zwischen den einzelnen Produktionsrechnern. Des Weiteren ist es unvermeidlich, dass sich auch die Umgebung eines einzelnen Produktionsrechners im Laufe der Zeit ändert.

Diese Unterschiede reichen von der Hardware über die Betriebssysteme bis zu den Bibliotheken, die auf den einzelnen Computern zur Verfügung stehen. Produktionsumgebungen werden üblicherweise von Betriebsteams verwaltet, während sich die Entwickler selbst um ihre Entwicklungslaptops kümmern. Da diese beiden Personengruppen unterschiedlich viel von Systemadministration verstehen, führt das zu relativ großen Unterschieden zwischen den beiden Systemen; ganz zu schweigen davon, dass Systemadministratoren gewöhnlich mehr Gewicht darauf legen, das System mit den aktuellsten Sicherheitspatches auf dem neuesten Stand zu halten, während sich viele Entwickler nicht so sehr darum kümmern.

Des Weiteren ist auf Produktionssystemen gewöhnlich Software mehrerer Entwickler oder Entwicklungsteams untergebracht, was bei den Computern der Entwickler nicht unbedingt der Fall sein muss. Ein Produktionssystem muss die passenden Bibliotheken für alle Anwendungen bereitstellen, die auf ihm vorhanden sind, selbst wenn die einzelnen Anwendungen unterschiedliche oder sogar konkurrierende Versionen dieser Bibliotheken benötigen.

Um die Anzahl der Probleme zu verringern, die in der Produktion auftreten, würden wir die Anwendungen gern in der Entwicklung und in der Produktion in exakt der gleichen Um­gebung ausführen, also mit identischem Betriebssystem, identischen Bibliotheken, identischer Systemkonfiguration, identischer Netzwerkumgebung usw. Des Weiteren wünschen wir nicht, dass sich diese Umgebung mit der Zeit allzu sehr ändert; am besten überhaupt nicht. Nach Möglichkeit möchten wir in der Lage sein, einem Server zusätzliche Anwendungen hinzuzufügen, ohne die bereits auf ihm vorhandenen zu beeinträchtigen.

1.1.3Übergang zu Continuous Delivery: DevOps und NoOps

In den letzten Jahren haben sich auch der gesamte Vorgang der Anwendungsentwicklung und der Umgang mit Anwendungen in der Produktion gewandelt. Früher hatte das Entwicklungsteam die Aufgabe, eine Anwendung zu erstellen und dann an das Betriebsteam zu übergeben, das sie bereitgestellt, sich um sie gekümmert und sie im Laufen gehalten hat. Mittlerweile haben viele Organisationen aber erkannt, dass es besser ist, wenn das Team, das die Anwendung entwickelt hat, auch an ihrer Bereitstellung und ihrer Pflege während ihres gesamten Lebenszyklus beteiligt wird. Das bedeutet, dass Entwicklungs-, QS- und Betriebssystems jetzt während des gesamten Prozesses zusammenarbeiten. Diese Vorgehensweise wird DevOps genannt.

Die Vorteile

Dadurch, dass die Entwickler stärker in die Ausführung der Anwendung in der Produktion einbezogen werden, erhalten sie ein besseres Verständnis der Bedürfnisse und Probleme der Benutzer sowie der Schwierigkeiten, die sich den Systemadministratoren bei der Wartung der Anwendung stellen. Entwickler sind jetzt auch eher geneigt, ihre Anwendung den Benutzern früher zur Verfügung zu stellen und deren Rückmeldung einzuholen, um die weitere Entwicklung zu steuern.

Um häufiger neue Versionen von Anwendungen veröffentlichen zu können, müssen wir den Bereitstellungsprozess rationalisieren. Im Idealfall sollten die Entwickler in der Lage sein, die Anwendungen selbst bereitzustellen, ohne erst auf die Systemadministratoren warten zu müssen. Allerdings erfordert die Bereitstellung einer Anwendung gewöhnlich Kenntnisse der zugrunde liegenden Infrastruktur und der Anordnung der Hardware im Rechenzentrum. Normalerweise kennen Entwickler die Einzelheiten der Infrastruktur nicht und wollen sie meistens nicht einmal kennen.

Entwickler und Systemadministratoren tun lassen, was sie am besten können

Auch wenn Entwickler und Systemadministratoren auf dasselbe Ziel hinarbeiten, nämlich eine Softwareanwendung erfolgreich als Dienst für die Kunden auszuführen, verfolgen sie doch unterschiedliche individuelle Ziele und werden von unterschiedlicher Motivation angetrieben. Entwickler erfreuen sich daran, neue Funktionen zu erstellen und das Benutzererlebnis zu verbessern. Normalerweise wollen sie nichts damit zu tun haben, dass das zugrunde liegende Betriebssystem auf dem neuesten Stand ist, über alle Sicherheitspatches verfügt usw. Solche Aufgaben überlassen sie lieber den Systemadministratoren.

Das Betriebsteam ist für die Produktionsbereitstellungen und für die Hardwareinfrastruktur verantwortlich, auf denen diese laufen. Seine Mitglieder kümmern sich um die Systemsicherheit, die Nutzung und andere Aspekte, die für die Entwickler keine hohe Priorität haben. Sie wollen nichts mit den impliziten Abhängigkeiten zwischen den Anwendungskomponenten zu tun haben und möchten auch nicht darüber nachgrübeln, wie sich Änderungen am Betriebssystem oder der Infrastruktur auf den Betrieb der Anwendung als Ganzes auswirken – aber sie müssen das tun.

Im Idealfall sollten die Entwickler daher in der Lage sein, Anwendungen selbst bereitzustellen, ohne irgendetwas über die Hardwareinfrastruktur zu wissen und ohne auf die Hilfe des Betriebsteams zurückzugreifen. Diese Vorgehensweise wird als NoOps bezeichnet. Natürlich werden dabei immer noch Personen gebraucht, die sich um die Hardwareinfrastruktur kümmern, aber im Idealfall sollten sie sich dabei nicht mit den Eigenheiten der Anwendungen beschäftigen müssen, die darauf laufen.

Wie wir noch sehen werden, können wir genau das mit Kubernetes erreichen. Es verbirgt die konkreten Einzelheiten der Hardware und stellt sie als eine geschlossene Plattform zum Bereitstellen und Ausführen von Anwendungen dar, sodass die Entwickler in der Lage sind, ihre Anwendungen ohne Hilfe der Systemadministratoren zu konfigurieren und bereitzustellen, während sich die Systemadministratoren darum kümmern können, dass die zugrunde liegende Infrastruktur läuft, ohne etwas über die konkreten Anwendungen zu wissen, die darauf ausgeführt werden.

1.2Containertechnologien

In Abschnitt 1.1 habe ich einige der Probleme aufgeführt, denen sich die Entwicklungs- und Betriebsteams von heute gegenübersehen. Es gibt viele Möglichkeiten, damit umzugehen, aber in diesem Buch konzentrieren wir uns darauf, wie sie sich mithilfe von Kubernetes lösen lassen.

Kubernetes verwendet Linux-Containertechnologie, um die laufenden Anwendungen zu isolieren. Daher müssen wir uns zunächst mit den Grundlagen von Containern vertraut machen, um zu verstehen, was Kubernetes selbst tut und was es an Containertechnologien wie Docker und rkt (sprich: „rock it“) auslagert.

1.2.1Was sind Container?

In Abschnitt 1.1.1 haben wir den Fall angesehen, dass auf einem Rechner mehrere Softwarekomponenten laufen, die unterschiedliche und möglicherweise sogar konkurrierende Versionen unabhängiger Bibliotheken benötigen oder ganz allgemein unterschiedliche Anforderungen an die Umgebung stellen.

Wenn eine Anwendung nur aus einer kleinen Anzahl großer Komponenten besteht, ist es durchaus akzeptabel, jeder Komponente eine eigene virtuelle Maschine (VM) zuzuweisen und jeder eine eigene Instanz des Betriebssystems bereitzustellen, um ihre Umgebungen zu isolieren. Aber wenn die Komponenten kleiner werden und ihre Anzahl zunimmt, dann ist es nicht mehr möglich, jeder von ihnen eine eigene VM zu geben, ohne Hardwareressourcen zu verschwenden und die Hardwarekosten zu steigern. Aber das ist noch nicht alles. Da jede VM einzeln konfiguriert und verwaltet werden muss, führt eine Erhöhung der Anzahl von VMs auch zu einer Verschwendung von Personal, da dadurch die Arbeitsbelastung der Systemadministratoren erheblich steigt.

Komponenten mithilfe von Linux-Containertechnologie isolieren

Anstatt die Umgebungen der einzelnen Microservices (oder von Softwareprozessen allgemein) mithilfe von virtuellen Maschinen zu isolieren, wenden Entwickler ihre Aufmerksamkeit den Linux-Containertechnologien zu, die es uns erlauben, mehrere Dienste auf demselben Hostcomputer auszuführen. Dabei wird nicht nur jedem dieser Dienste eine eigene Umgebung bereitgestellt, sondern sie werden auch ähnlich wie bei der Verwendung von VMs voneinander isoliert, aber mit viel weniger Aufwand.

Ein Prozess, der in einem Container läuft, wird in Wirklichkeit wie alle anderen Prozesse innerhalb des Hostbetriebssystems ausgeführt (anders als bei VMs, wo die Prozesse in unterschiedlichen Betriebssystemen laufen). Trotzdem ist der Prozess im Container von den anderen Prozessen getrennt. Für ihn sieht es so aus, als ob er auf einem eigenen Rechner mit einem eigenen Betriebssystem ganz für ihn allein läuft.

Virtuelle Maschinen und Container im Vergleich

Container sind viel schlanker als VMs, weshalb es damit möglich ist, mehr Softwarekomponenten auf derselben Hardware auszuführen. Das liegt insbesondere daran, dass jede VM ihren eigenen Satz von Systemprozessen ausführen muss, was zusätzliche Rechenressourcen neben denen erfordert, die von den Prozessen der Komponenten verbraucht werden. Ein Container dagegen ist lediglich ein einziger, isolierter Prozess, der im Hostbetriebssystem läuft und nur die Ressourcen nutzt, die die Anwendung verbraucht. Dadurch entsteht kein Mehraufwand für zusätzliche Prozesse.

Normalerweise stehen nicht genügend Ressourcen bereit, um jeder Anwendung eine eigene VM zuzuweisen, weshalb Sie Anwendungen gewöhnlich gruppieren und auf wenigen VMs bereitstellen. Dagegen kann (und sollte) jede Anwendung einen eigenen Container haben, wie Sie in Bild 1.4 sehen. Unter dem Strich können Sie dadurch viel mehr Anwendungen auf einem Rechner unterbringen.

Bild 1.4Isolierung von Anwendungsgruppen mit VMs im Gegensatz zur Isolierung einzelner Anwendungen mit Containern

Wenn Sie auf einem Host drei VMs ausführen, teilen sich drei komplett getrennte Betriebssysteme die Hardwareressourcen. Unter den VMs befindet sich der Hypervisor, der die tatsächlichen Hardwareressourcen auf virtuelle Ressourcen geringeren Umfangs für die Betriebssysteme in den einzelnen VMs aufteilt. Die Anwendungen in den VMs richten Systemaufrufe an den Kernel im Gastbetriebssystem der VM, und dieser Kernel wiederum führt x86-Anweisungen über den Hypervisor auf der physischen CPU des Hosts aus.

HINWEIS: Es gibt zwei Arten von Hypervisors: Ein Hypervisor vom Typ 2 verwendet ein Hostbetriebssystem, ein Hypervisor vom Typ 1 dagegen nicht.

Container dagegen führen alle Systemaufrufe in genau demselben Kernel aus, und dieser eine Kernel ist auch derjenige, der die x86-Anweisungen auf der CPU des Hosts ausführt. Die CPU braucht auch keine Art der Virtualisierung, wie sie für VMs erforderlich ist (siehe Bild 1.5).

Bild 1.5Unterschiedlicher Zugriff auf die CPU in VMs und in Containern

Der Hauptvorteil virtueller Maschinen besteht darin, dass sie eine vollständige Isolierung bieten, da sie nur die Hardware gemeinsam nutzen, während Container Aufrufe an denselben Linux-Kernel richten, was ganz klar ein Sicherheitsrisiko darstellt. Stehen Ihnen nur eingeschränkte Hardwareressourcen zur Verfügung, sind virtuelle Maschinen nur dann sinnvoll, wenn Sie eine geringe Zahl von Prozessen isolieren möchten. Um eine größere Anzahl von isolierten Prozessen auf demselben Rechner auszuführen, bilden Container aufgrund des geringeren Mehraufwands die bessere Wahl. Schließlich führt jede VM ihren eigenen Satz von Systemdiensten aus, was bei Containern nicht der Fall ist, da sie schließlich alle unter demselben Betriebssystem laufen. Um einen Container auszuführen, muss anders als bei einer VM auch nichts hochgefahren werden. Ein Prozess in einem Container startet sofort.

Die Mechanismen für die Containerisolierung

Vielleicht wundern Sie sich jetzt, wie die Container Prozesse isolieren können, die doch auf demselben Betriebssystem laufen. Dies wird durch zwei verschiedene Mechanismen ermöglicht: Linux-Namespaces (Namensräume) stellen sicher, dass jeder Prozess seine persönliche Sicht auf das System hat (Dateien, Prozesse, Netzwerkschnittstellen, Hostname usw.), und Linux-Cgroups (Control Groups) beschränken die Menge an Ressourcen, die ein Prozess verbrauchen kann (CPU, Arbeitsspeicher, Netzwerkbandbreite usw.).

Prozesse mithilfe von Linux-Namespaces isolieren

Standardmäßig hat jedes Linux-System ursprünglich nur einen einzigen Namespace, zu dem alle Systemressourcen wie Dateisysteme, Prozess-IDs, Benutzer-IDs, Netzwerkschnittstellen usw. gehören. Es ist jedoch möglich, zusätzliche Namespaces einzurichten und Ressourcen auf sie zu verteilen. Es ist auch möglich, einen Prozess in einem dieser zusätzlichen Namespaces auszuführen, sodass er nur die Ressourcen sieht, die sich in demselben Namespace befinden. In Wirklichkeit gibt es verschiedene Arten von Namespaces, sodass ein Prozess niemals nur zu einem einzigen gehört, aber dafür immer nur zu einem einzigen Namespace einer Art.

Es gibt folgende Arten von Namespaces:

       Mount (mnt)

       Prozess-ID (pid)

       Netzwerk (net)

       Interprozesskommunikation (ipc)

       UTS

       Benutzer-ID (user)

Die verschiedenen Arten von Namespaces dienen dazu, einzelne Gruppen von Ressourcen voneinander zu trennen. Beispielsweise bestimmt der UTS-Namespace, welchen Host- und welchen Domänennamen der in ihm laufende Prozess sieht. Durch die Zuweisung zweier verschiedener UTS-Namespaces zu zwei Prozessen können Sie dafür sorgen, dass jeder einen anderen Hostnamen sieht. Für die beiden Prozesse sieht es daher so aus, als würden sie auf zwei verschiedenen Computern laufen (zumindest was die Hostnamen angeht).

Ebenso bestimmt der Netzwerk-Namespace eines Prozesses, welche Netzwerkschnittstellen die Anwendung innerhalb des Prozesses sieht. Jede Netzwerkschnittstelle gehört zu genau einem Namespace, kann aber von einem Namespace zu einem anderen verschoben werden. Jeder Container verwendet seinen eigenen Netzwerk-Namespace, weshalb er seinen eigenen Satz von Netzwerkschnittstellen sieht.

Das sollte Ihnen eine grundlegende Vorstellung davon geben, wie Namespaces verwendet werden, um die Anwendungen in Containern voneinander zu isolieren.

Die für einen Prozess verfügbaren Ressourcen einschränken

Beim zweiten Mechanismus für die Containerisolierung geht es darum, die Menge der Systemressourcen einzuschränken, die ein Container verbrauchen kann. Das wird mithilfe von Cgroups erreicht, einer Funktion des Linux-Kernels, die es ermöglicht, die Ressourcennutzung eines Prozesses (oder einer Gruppe von Prozessen) zu begrenzen. Ein Prozess kann nicht mehr CPU, Arbeitsspeicher, Netzwerkbrandbreite usw. verbrauchen, als für ihn eingestellt ist. Das verhindert, dass Prozesse Ressourcen horten können, die für andere Prozesse reserviert sind – ganz so, als ob jeder einzelne Prozess auf einem eigenen Computer laufen würde.

1.2.2Die Containerplattform Docker

Containertechnologien gibt es zwar schon seit langer Zeit, aber einen größeren Bekanntheitsgrad haben sie erst mit dem Aufkommen der Containerplattform Docker gewonnen. Docker war das erste Containersystem, das nicht nur die Anwendung, sondern auch all ihre Bibliotheken und anderen Abhängigkeiten, sogar das gesamte Dateisystem des Betriebssystems, zu einem einfachen, portierbaren Paket schnüren konnte, mit dem sich die Anwendung auf einem anderen Computer mit Docker zur Verfügung stellen ließ.

Wenn Sie eine mit Docker gepackte Anwendung ausführen, sieht diese immer genau die Dateisysteminhalte, die Sie für sie vorbereitet haben. Unabhängig davon, ob sie auf dem Entwicklungs- oder einem Produktionsrechner ausgeführt wird, hat die Anwendung immer die gleichen Dateien zur Verfügung, selbst wenn der Produktionsserver ein ganz anderes Linux-Betriebssystem hat. Sie sieht nichts von dem Server, auf dem sie ausgeführt wird, weshalb es auch keine Rolle spielt, ob dort ganz andere Bibliotheken installiert sind als auf dem Entwicklungscomputer.

Wenn Sie die Anwendung beispielsweise mit sämtlichen Dateien von Red Hat Enterprise Linux gepackt haben, dann glaubt sie immer, dass sie auf RHEL ausgeführt wird, ganz gleich, ob sie nun auf Ihrem Entwicklungscomputer mit Fedora oder auf einem Server mit Debian oder einer anderen Linux-Distribution läuft. Nur der Kernel kann sich unterscheiden.

Das ist ähnlich wie bei einer VM, in der Sie erst ein Betriebssystem und dann die Anwendung installieren und schließlich das komplette VM-Image verteilen und ausführen. Docker nutzt jedoch keine VMs, sondern die im vorigen Abschnitt beschriebene Linux-Containertechnologie, um (fast) den gleichen Grad an Isolation zu erreichen wie mit VMs. Statt riesiger, monolithischer VM-Images werden dabei Containerimages verwendet, die gewöhnlich kleiner sind.

Der große Unterschied zwischen Docker-Containerimages und VM-Images besteht darin, dass Containerimages aus Schichten aufgebaut sind, die von mehreren Images gemeinsam genutzt und wiederverwendet werden können. Dadurch müssen nur einzelne Schichten eines Images heruntergeladen werden, wenn die anderen Schichten bereits zuvor bei der Ausführung eines anderen Containerimages mit den gleichen Schichten abgerufen wurden.

Grundlagen von Docker

Docker ist eine Plattform zum Verpacken, Verteilen und Ausführen von Anwendungen. Wie bereits erwähnt, können Sie Ihre Anwendungen damit zusammen mit ihrer gesamten Umgebung packen. Diese kann einige wenige Bibliotheken umfassen, die die Anwendung braucht, aber auch sämtliche Dateien, die im Dateisystem des installierten Betriebssystems gewöhnlich zur Verfügung stehen. Mit Docker ist es möglich, dieses Paket an ein zentrales Repository zu übermitteln, von wo aus es an jeden Computer, auf dem Docker läuft, übertragen und dort ausgeführt werden kann (meistens, aber nicht immer, wie wir noch sehen werden).

An diesem Vorgang sind die drei folgenden Hauptelemente von Docker beteiligt:

       Images: Das Docker-Containerimage ist der Behälter, in den Sie die Anwendung und deren Umgebung packen. Es enthält das Dateisystem, das der Anwendung zur Verfügung steht, sowie andere Metadaten, z. B. den Pfad zu der ausführbaren Datei, die gestartet werden soll, wenn das Image ausgeführt wird.

       Registrys: Eine Docker-Registry ist ein Repository, in dem Ihre Docker-Images gespeichert sind und das die gemeinsame Nutzung dieser Images durch verschiedene Personen und Computer erleichtert. Wenn Sie das Image erstellen, können Sie es auf demselben Computer ausführen, aber auch zu einer Registry hochladen und von dort auf einen anderen Rechner herunterladen, um es dort auszuführen. Einige Registrys sind öffentlich, sodass jeder die dort gespeicherten Images herunterladen kann, andere dagegen privat und damit nur für bestimmte Personen oder Computer zugänglich.

       Container: Ein Docker-Container ist ein laufendes Docker-Image. Ein laufender Container ist ein Prozess, de auf dem Computer mit Docker ausgeführt wird, aber vollständig vom Host und von allen anderen darauf laufenden Prozessen isoliert ist. Der Prozess ist auch ressourcenbeschränkt, was bedeutet, dass er nur auf die Menge von Ressourcen (CPU, RAM usw.) zugreifen und sie nutzen kann, die ihm zugewiesen sind.

Docker-Images erstellen, verteilen und ausführen

Bild 1.6 zeigt alle drei Elemente und die Beziehungen zwischen ihnen. Ein Entwickler erstellt zunächst ein Image und lädt es zur Registry hoch, wodurch es für alle Personen verfügbar ist, die Zugriff auf die Registry haben. Diese Personen können das Image auf beliebige Rechner herunterladen, auf denen Docker läuft, und es dort ausführen. Docker erstellt isolierte Container auf der Grundlage des Images und führt die binäre ausführbare Datei aus, die als Teil des Images festgelegt wurde.

Bild 1.6Images, Registrys und Container in Docker

Virtuelle Maschinen und Docker-Container im Vergleich

Ich habe bereits erklärt, dass Linux-Container im Allgemeinen VMs ähneln, aber viel schlanker sind. Im Folgenden wollen wir Docker-Container mit VMs (und Docker-Images mit VM-Images) vergleichen. Bild 1.7 zeigt sechs Anwendungen, die einmal in VMs und einmal in Docker-Containern ausgeführt werden.

Wie Sie sehen, haben die beiden Anwendungen A und B Zugriff auf dieselben Binärdateien und Bibliotheken, und zwar unabhängig davon, ob sie in einer VM oder in zwei separaten Containern ausgeführt werden. In der VM ist das offensichtlich, da beide Anwendungen dasselbe Dateisystem sehen (nämlich das der VM). Aber wenn doch jeder Container wie zuvor gesagt über sein eigenes isoliertes Dateisystem verfügt, wie können dann A und B dieselben Dateien nutzen?

Bild 1.7Ausführung von sechs Anwendungen auf drei VMs und in Docker-Containern

Imageschichten

Ich habe schon gesagt, dass Docker-Images aus Schichten bestehen. Da jedes Docker-Image auf anderen Images aufbaut, können verschiedene Images dieselben Schichten enthalten. Das bedeutet, dass zwei verschiedene Images dasselbe Eltern-Image als Grundlage nutzen können. Das beschleunigt die Verteilung von Images über das Netzwerk, da die bereits als Teil eines Images übertragenen Schichten nicht noch einmal für ein anderes Image übertragen werden müssen, das sie ebenfalls enthält.

Nicht nur die Verteilung wird durch die Verwendung von Schichten effizienter, auch der Speicherbedarf der Images sinkt. Jede Schicht wird nur einmal gespeichert. Zwei Container, die aus zwei Images mit denselben Grundschichten gebildet wurden, können zwar dieselben Dateien lesen, doch wenn ein Container eine Datei in einer der gemeinsam genutzten Schichten ändert, wirkt sich diese Änderung nicht auf den anderen Container aus. Selbst wenn die Container Dateien gemeinsam nutzen, bleiben sie voneinander isoliert. Das funktioniert, weil Container-Imageschichten schreibgeschützt sind. Bei der Ausführung eines Containers wird immer eine neue schreibbare Schicht oberhalb der anderen Schichten erstellt. Wenn der Prozess in dem Container in eine Datei einer der unteren Schichten schreibt, wird in der obersten Schicht eine Kopie dieser Datei erstellt, in die der Prozess dann schreibt.

Portierbarkeitseinschränkungen von Docker-Images

Theoretisch kann ein Docker-Image auf jedem Linux-Computer ausgeführt werden, auf dem Docker läuft, allerdings gibt es eine kleine Einschränkung, die damit zu tun hat, dass alle Container auf einem Host auf dessen Linux-Kernel laufen. Wenn eine Anwendung bestimmte Anforderungen an den Kernel stellt, funktioniert sie auf manchen Computern möglicherweise nicht. Wenn ein Rechner eine andere Version des Linux-Kernels ausführt oder nicht dieselben Kernel-Module zur Verfügung stellt, kann die Anwendung offensichtlich nicht auf ihm laufen.

Container bilden zwar eine schlankere Lösung als VMs, unterwerfen aber auch die Anwendungen, die in ihnen laufen, gewissen Einschränkungen. Da VMs jeweils ihren eigenen Kernel ausführen, gibt es solche Einschränkungen bei ihnen nicht.

Es geht auch nicht nur um den Kernel. Eine Anwendung in einem Container, die für eine bestimmte Hardwarearchitektur oder ein bestimmtes Betriebssystem geschrieben wurde, kann natürlich nur auf Computern mit genau dieser Architektur bzw. diesem Betriebssystem laufen. Es ist nicht möglich, eine Anwendung, die für die x86-Architektur geschrieben wurde, in einem Container auf einem ARM-Rechner auszuführen, nur weil dort ebenfalls Docker läuft. Dafür benötigen Sie immer noch eine VM.

1.2.3Die Docker-Alternative rkt

Docker war die erste Containerplattform, die Container zu einer populären Technik machte. Ich hoffe, ich habe deutlich gemacht, dass Docker selbst keine Containerisierung von Prozessen leistet. Die eigentliche Isolierung der Container erfolgt auf der Ebene des Linux-Kernels mithilfe von Kernel-Merkmalen wie Linux-Namespaces und Cgroups. Docker er­leichtert lediglich die Verwendung.

Nach dem Erfolg von Docker wurde die Open Container Initiative (OCI) gegründet, um offene Branchenstandards rund um Containerformate und Containerlaufzeitumgebungen zu erstellen. Docker gehört zu dieser Initiative – ebenso wie rkt