Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
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:
Seitenzahl: 863
Veröffentlichungsjahr: 2020
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
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.
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
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.
Marko Lukša ist Softwareentwickler mit mehr als 20 Jahren Berufserfahrung, 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.
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!
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.
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.
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.
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 erklä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.
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.
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 beinhaltet:
Einführung in Kubernetes(Kapitel 1)
Erste Schritte mit Docker und Kubernetes(Kapitel 2)
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.
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.
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.
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.
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.
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.
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.
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 Umgebung 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.
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.
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.
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.
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.
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.
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.
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.
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.).
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.
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.
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.
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.
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
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
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.
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.
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 erleichtert 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
