Kapitel 1. Einrichten eines Basisdienstes
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In diesem Kapitel wird beschrieben, wie du eine einfache Multitier-Anwendung in Kubernetes einrichtest. Das Beispiel, das wir durchgehen, besteht aus zwei Tiers: einer einfachen Webanwendung und einer Datenbank. Auch wenn es sich nicht um die komplizierteste Anwendung handelt, ist sie ein guter Ausgangspunkt, um zu lernen, wie man eine Anwendung in Kubernetes verwaltet.
Anwendungsübersicht
Die Anwendung , die wir für unser Beispiel verwenden werden, ist ziemlich einfach. Es handelt sich um einen einfachen Journaldienst mit den folgenden Details:
-
Er verfügt über einen separaten statischen Dateiserver mit NGINX.
-
Es hat eine RESTful Application Programming Interface (API) https://some-host-name.io/api auf dem Pfad /api.
-
Es hat einen Dateiserver auf der Haupt-URL, https://some-host-name.io.
-
Es nutzt den Dienst Let's Encrypt für die Verwaltung von Secure Sockets Layer (SSL).
Abbildung 1-1 zeigt ein Diagramm dieser Anwendung. Mach dir keine Sorgen, wenn du nicht alle Teile auf Anhieb verstehst; sie werden im Laufe des Kapitels noch genauer erklärt. Wir werden diese Anwendung Schritt für Schritt aufbauen, zuerst mit YAML-Konfigurationsdateien und dann mit Helm-Diagrammen.
Konfigurationsdateien verwalten
Bevor wir in die Details gehen, wie wir diese Anwendung in Kubernetes aufbauen, lohnt es sich, darüber zu sprechen, wie wir die Konfigurationen selbst verwalten. Bei Kubernetes wird alles deklarativ dargestellt. Das heißt, du schreibst den gewünschten Zustand der Anwendung im Cluster auf (in der Regel in YAML- oder JSON-Dateien), und diese deklarierten gewünschten Zustände definieren alle Teile deiner Anwendung. Dieser deklarative Ansatz ist bei weitem einem imperativen Ansatz vorzuziehen, bei dem der Zustand deines Clusters die Summe einer Reihe von Änderungen am Cluster ist. Wenn ein Cluster zwingend konfiguriert ist, ist es schwierig zu verstehen und zu reproduzieren, wie der Cluster in diesen Zustand gekommen ist, was es schwierig macht, Probleme mit deiner Anwendung zu verstehen oder zu beheben.
Wenn es darum geht, den Zustand deiner Anwendung zu deklarieren, ziehen die meisten Leute YAML gegenüber JSON vor, obwohl Kubernetes beide unterstützt. Der Grund dafür ist, dass YAML etwas weniger ausführlich ist und von Menschen besser bearbeitet werden kann als JSON. Allerdings ist zu beachten, dass YAML auf Einrückungen reagiert. Oft lassen sich Fehler in Kubernetes-Konfigurationen auf eine falsche Einrückung in YAML zurückführen. Wenn sich die Dinge nicht wie erwartet verhalten, ist die Überprüfung der Einrückung ein guter Ausgangspunkt für die Fehlersuche. Die meisten Editoren unterstützen die Syntaxhervorhebung sowohl für JSON als auch für YAML. Wenn du mit diesen Dateien arbeitest, ist es eine gute Idee, solche Tools zu installieren, um sowohl Autoren- als auch Dateifehler in deinen Konfigurationen leichter zu finden. Es gibt auch eine hervorragende Erweiterung für Visual Studio Code, die eine umfassendere Fehlerprüfung für Kubernetes-Dateien unterstützt.
Da der deklarative Zustand, der in diesen YAML-Dateien enthalten ist, die Quelle der Wahrheit für deine Anwendung ist, ist die korrekte Verwaltung dieses Zustands entscheidend für den Erfolg deiner Anwendung. Wenn du den gewünschten Zustand deiner Anwendung änderst, musst du in der Lage sein, die Änderungen zu verwalten, zu überprüfen, ob sie korrekt sind, zu kontrollieren, wer die Änderungen vorgenommen hat, und sie möglicherweise rückgängig zu machen, wenn sie fehlschlagen. Glücklicherweise haben wir im Rahmen der Softwareentwicklung bereits die notwendigen Werkzeuge entwickelt, um sowohl Änderungen am deklarativen Zustand als auch Audits und Rollbacks zu verwalten. Die bewährten Methoden zur Versionskontrolle und Codeüberprüfung lassen sich direkt auf die Verwaltung des deklarativen Zustands deiner Anwendung anwenden.
Heutzutage speichern die meisten Leute ihre Kubernetes-Konfigurationen in Git. Auch wenn die genauen Details des Versionskontrollsystems unwichtig sind, erwarten viele Tools im Kubernetes-Ökosystem Dateien in einem Git-Repository. Bei der Codeüberprüfung gibt es eine viel größere Heterogenität; obwohl GitHub sehr beliebt ist, verwenden andere Tools oder Dienste für die Codeüberprüfung vor Ort. Unabhängig davon, wie du die Codeüberprüfung für deine Anwendungskonfiguration durchführst, solltest du sie mit der gleichen Sorgfalt und dem gleichen Fokus behandeln, wie du es bei der Versionskontrolle tust.
Wenn es darum geht, das Dateisystem für deine Anwendung einzurichten, lohnt es sich, die mit dem Dateisystem gelieferte Verzeichnisstruktur zu verwenden, um deine Komponenten zu organisieren. Normalerweise wird ein einzelnes Verzeichnis verwendet, um einen Anwendungsdienst zusammenzufassen. Die Definition eines Anwendungsdienstes kann von Team zu Team unterschiedlich sein, aber im Allgemeinen handelt es sich um einen Dienst, der von einem Team aus 8-12 Personen entwickelt wird. Innerhalb dieses Verzeichnisses werden Unterverzeichnisse für Teilkomponenten der Anwendung verwendet.
Für unsere Anwendung legen wir die Dateien wie folgt an:
journal/ frontend/ redis/ fileserver/
In jedem Verzeichnis befinden sich die konkreten YAML-Dateien, die zur Definition des Dienstes benötigt werden. Wie du später sehen wirst, wird dieses Dateilayout immer komplizierter, wenn wir unsere Anwendung in mehreren Regionen oder Clustern einsetzen .
Einen replizierten Dienst mit Hilfe von Deployments erstellen
Um unsere Anwendung zu beschreiben, fangen wir mit dem Frontend an und arbeiten uns nach unten. Die Frontend-Anwendung für das Journal ist eine in TypeScript implementierte Node.js-Anwendung. Die komplette Anwendung ist zu umfangreich, um sie in das Buch aufzunehmen, deshalb haben wir sie auf unserem GitHub gehostet. Dort findest du auch den Code für künftige Beispiele, es lohnt sich also, ein Lesezeichen zu setzen. Die Anwendung stellt einen HTTP-Dienst auf Port 8080 zur Verfügung, der Anfragen an den Pfad /api/* weiterleitet und das Redis-Backend nutzt, um die aktuellen Journaleinträge hinzuzufügen, zu löschen oder zurückzugeben. Wenn du die folgenden YAML-Beispiele auf deinem lokalen Rechner durcharbeiten willst, solltest du diese Anwendung mit dem Dockerfile in ein Container-Image bauen und in dein eigenes Image-Repository pushen. Anstatt den Namen unserer Beispieldatei zu verwenden, solltest du dann den Namen deines Container-Images in deinen Code einbauen.
Bewährte Methoden für die Bildverwaltung
Obwohl die Erstellung und Pflege von Container-Images im Allgemeinen den Rahmen dieses Buches sprengen würde, lohnt es sich, einige bewährte Methoden für die Erstellung und Benennung von Images zu nennen. Im Allgemeinen kann der Erstellungsprozess von Images anfällig für "Supply-Chain-Angriffe" sein. Bei solchen Angriffen fügt ein böswilliger Benutzer Code oder Binärdateien aus einer vertrauenswürdigen Quelle in eine Abhängigkeit ein, die dann in deine Anwendung eingebaut wird. Wegen des Risikos solcher Angriffe ist es wichtig, dass du bei der Erstellung deiner Bilder nur auf bekannte und vertrauenswürdige Bildanbieter zurückgreifst. Alternativ kannst du auch alle deine Images von Grund auf neu erstellen. Bei einigen Sprachen (z. B. Go), die statische Binärdateien erstellen können, ist das einfach, aber bei interpretierten Sprachen wie Python, JavaScript oder Ruby ist es deutlich komplizierter.
Die anderen bewährten Methoden für Images beziehen sich auf die Namensgebung. Obwohl die Version eines Container-Images in einer Image-Registry theoretisch veränderbar ist, solltest du das Versions-Tag als unveränderbar behandeln. Insbesondere eine Kombination aus der semantischen Version und dem SHA-Hash des Commits, in dem das Image erstellt wurde, ist eine gute Praxis für die Benennung von Images (z. B. v1.0.1-bfeda01f). Wenn du keine Image-Version angibst, wird standardmäßig latest
verwendet. Das kann zwar in der Entwicklung praktisch sein, ist aber für den produktiven Einsatz keine gute Idee, da latest
jedes Mal, wenn ein neues Image erstellt wird, geändert wird.
Replizierte Anwendung erstellen
Unsere Frontend-Anwendung ist zustandslos; sie verlässt sich für ihren Zustand vollständig auf das Redis-Backend. Daher können wir sie beliebig oft replizieren, ohne den Datenverkehr zu beeinträchtigen. Auch wenn es unwahrscheinlich ist, dass unsere Anwendung in großem Umfang genutzt wird, ist es dennoch eine gute Idee, mindestens zwei Replikate zu betreiben, damit du einen unerwarteten Absturz bewältigen oder eine neue Version der Anwendung ohne Ausfallzeiten einführen kannst.
In Kubernetes ist die Ressource ReplicaSet diejenige, die direkt die Replikation einer bestimmten Version deiner containerisierten Anwendung verwaltet. Da sich die Version aller Anwendungen im Laufe der Zeit ändert, wenn du den Code veränderst, ist es keine bewährte Methode, ein ReplicaSet direkt zu verwenden. Stattdessen verwendest du die Bereitstellungsressource. Ein Deployment kombiniert die Replikationsfähigkeiten von ReplicaSet mit der Versionierung und der Möglichkeit, einen stufenweisen Rollout durchzuführen. Mit einem Deployment kannst du die in Kubernetes integrierten Werkzeuge nutzen, um von einer Version der Anwendung zur nächsten zu wechseln.
Die Bereitstellungsressource von Kubernetes für unsere Anwendung sieht wie folgt aus:
apiVersion
:
apps/v1
kind
:
Deployment
metadata
:
labels
:
# All pods in the Deployment will have this label
app
:
frontend
name
:
frontend
namespace
:
default
spec
:
# We should always have at least two replicas for reliability
replicas
:
2
selector
:
matchLabels
:
app
:
frontend
template
:
metadata
:
labels
:
app
:
frontend
spec
:
containers
:
-
image
:
my-repo/journal-server:v1-abcde
imagePullPolicy
:
IfNotPresent
name
:
frontend
# TODO: Figure out what the actual resource needs are
resources
:
request
:
cpu
:
"1.0"
memory
:
"1G"
limits
:
cpu
:
"1.0"
memory
:
"1G"
Es gibt mehrere Dinge, die du bei diesem Einsatz beachten solltest. Erstens verwenden wir Labels, um den Einsatz, die ReplicaSets und die Pods zu identifizieren, die der Einsatz erstellt. Wir haben das Label app: frontend
zu all diesen Ressourcen hinzugefügt, damit wir alle Ressourcen für eine bestimmte Schicht in einer einzigen Anfrage untersuchen können. Du wirst sehen, dass wir beim Hinzufügen weiterer Ressourcen genauso vorgehen werden.
Außerdem haben wir an einigen Stellen in der YAML Kommentare eingefügt. Diese Kommentare werden zwar nicht in der Kubernetes-Ressource auf dem Server gespeichert, aber genau wie Kommentare im Code dienen sie als Anleitung für Personen, die diese Konfiguration zum ersten Mal sehen.
Du solltest auch beachten, dass wir für die Container im Deployment sowohl Request als auch Limit Ressourcenanforderungen angegeben haben und Request gleich Limit gesetzt haben. Wenn eine Anwendung läuft, ist Request die Reservierung, die auf dem Host-Rechner, auf dem sie läuft, garantiert wird. Das Limit ist die maximale Ressourcennutzung, die dem Container zugestanden wird. Wenn du anfängst, führt die Einstellung Request gleich Limit zu einem möglichst vorhersehbaren Verhalten deiner Anwendung. Diese Vorhersehbarkeit geht auf Kosten der Ressourcennutzung. Da die Einstellung Request gleich Limit verhindert, dass deine Anwendung zu viele Ressourcen verbraucht, kannst du die maximale Auslastung nur erreichen, wenn du Request und Limit sehr, sehr sorgfältig einstellst. Wenn du das Kubernetes-Ressourcenmodell besser verstehst, kannst du Request und Limit für deine Anwendung unabhängig voneinander anpassen, aber im Allgemeinen finden die meisten Nutzer, dass die Stabilität der Vorhersagbarkeit die geringere Auslastung wert ist.
Wie unser Kommentar andeutet, ist es oft schwierig, die richtigen Werte für diese Ressourcengrenzen zu ermitteln. Ein guter Ansatz ist es, die Schätzungen zunächst zu hoch anzusetzen und dann mithilfe der Überwachung die richtigen Werte zu ermitteln. Wenn du jedoch einen neuen Dienst einführst, solltest du dich daran erinnern, dass der Ressourcenbedarf beim ersten großen Datenverkehr wahrscheinlich deutlich ansteigen wird. Außerdem gibt es einige Sprachen, vor allem Sprachen mit Speicherbereinigung, die gerne den gesamten verfügbaren Speicher verbrauchen, was es schwierig machen kann, das richtige Minimum für den Speicher zu bestimmen. In diesem Fall kann eine Form der binären Suche notwendig sein, aber erinnere dich daran, dies in einer Testumgebung zu tun, damit es sich nicht auf deine Produktion auswirkt!
Nachdem wir nun die Bereitstellungsressource definiert haben, checken wir sie in die Versionskontrolle ein und stellen sie in Kubernetes bereit:
gitadd
frontend/deployment.yaml
git
commit
-m
"Added deployment"
frontend/deployment.yaml
kubectl
apply
-f
frontend/deployment.yaml
Es ist auch eine bewährte Methode, sicherzustellen, dass der Inhalt deines Clusters genau mit dem Inhalt deiner Versionsverwaltung übereinstimmt. Das beste Muster, um dies zu gewährleisten, ist ein GitOps-Ansatz, bei dem die Bereitstellung für die Produktion nur von einem bestimmten Zweig der Quellcodekontrolle aus erfolgt und die Automatisierung der kontinuierlichen Integration/kontinuierlichen Bereitstellung (CI/CD) genutzt wird. Auf diese Weise ist sichergestellt, dass die Quellcodekontrolle und die Produktion übereinstimmen. Auch wenn eine vollständige CI/CD-Pipeline für eine einfache Anwendung übertrieben erscheinen mag, ist die Automatisierung an sich, unabhängig von der Zuverlässigkeit, die sie bietet, in der Regel den Zeitaufwand für ihre Einrichtung wert. Außerdem ist es extrem schwierig, CI/CD in eine bestehende Anwendung, die unbedingt eingesetzt werden muss, einzubauen.
Wir werden in späteren Abschnitten auf diese Anwendungsbeschreibung YAML zurückkommen, um weitere Elemente wie die ConfigMap und die geheimen Volumes sowie als Pod Quality of Service zu untersuchen.
Einrichten eines externen Ingress für HTTP-Verkehr
Die Container für unsere Anwendung sind jetzt bereitgestellt, aber es ist derzeit nicht möglich, dass jemand auf die Anwendung zugreift. Standardmäßig sind die Ressourcen des Clusters nur innerhalb des Clusters selbst verfügbar. Um unsere Anwendung der Welt zugänglich zu machen, müssen wir einen Dienst und einen Load Balancer einrichten, die eine externe IP-Adresse bereitstellen und den Datenverkehr zu unseren Containern leiten. Für die externe Bereitstellung werden wir zwei Kubernetes-Ressourcen verwenden. Die erste ist ein Dienst, der den Datenverkehr über das Transmission Control Protocol (TCP) oder das User Datagram Protocol (UDP) ausbalanciert. In unserem Fall verwenden wir das TCP-Protokoll. Die zweite ist eine Ingress-Ressource, die einen HTTP(S)-Lastausgleich mit intelligentem Routing von Anfragen auf Basis von HTTP-Pfaden und Hosts ermöglicht. Bei einer einfachen Anwendung wie dieser fragst du dich vielleicht, warum wir den komplexeren Ingress verwenden, aber wie du in den folgenden Abschnitten sehen wirst, bedient selbst diese einfache Anwendung HTTP-Anfragen von zwei verschiedenen Diensten. Außerdem ermöglicht ein Ingress an der Kante die Flexibilität, unseren Dienst in Zukunft zu erweitern.
Hinweis
Die Ingress-Ressource ist eine der älteren Ressourcen in Kubernetes, und im Laufe der Jahre wurden zahlreiche Probleme mit der Art und Weise, wie sie den HTTP-Zugriff auf Microservices modelliert, angesprochen. Dies hat zur Entwicklung der Gateway API für Kubernetes geführt. Die Gateway API wurde als Erweiterung für Kubernetes entwickelt und erfordert zusätzliche Komponenten, die in deinem Cluster installiert werden müssen. Wenn du feststellst, dass Ingress deine Anforderungen nicht erfüllt, solltest du einen Wechsel zur Gateway API in Betracht ziehen.
Bevor die Ingress-Ressource definiert werden kann, muss ein Kubernetes-Dienst für existieren, auf den der Ingress verweist. Wir verwenden Labels, um den Dienst zu den Pods zu leiten, die wir im vorherigen Abschnitt erstellt haben. Der Service ist wesentlich einfacher zu definieren als das Deployment und sieht wie folgt aus:
apiVersion
:
v1
kind
:
Service
metadata
:
labels
:
app
:
frontend
name
:
frontend
namespace
:
default
spec
:
ports
:
-
port
:
8080
protocol
:
TCP
targetPort
:
8080
selector
:
app
:
frontend
type
:
ClusterIP
Nachdem du den Service definiert hast, kannst du eine Ingress-Ressource definieren. Im Gegensatz zu Service-Ressourcen muss für Ingress ein Ingress-Controller-Container im Cluster installiert sein. Du kannst zwischen verschiedenen Implementierungen wählen, die entweder von deinem Cloud-Provider angeboten oder mit Open-Source-Servern umgesetzt werden. Wenn du dich für einen Open-Source-Ingress-Provider entscheidest, solltest du ihn mit dem Helm-Paketmanager installieren und warten. Die Ingress-Anbieter nginx
oder haproxy
sind eine beliebte Wahl:
apiVersion
:
networking.k8s.io/v1
kind
:
Ingress
metadata
:
name
:
frontend-ingress
spec
:
rules
:
-
http
:
paths
:
-
path
:
/testpath
pathType
:
Prefix
backend
:
service
:
name
:
test
port
:
number
:
8080
Nachdem wir unsere Ingress-Ressource erstellt haben, ist unsere Anwendung bereit, den Datenverkehr von Webbrowsern aus der ganzen Welt zu bedienen. Als Nächstes sehen wir uns an, wie du deine Anwendung für eine einfache Konfiguration und Anpassung einrichten kannst.
Eine Anwendung mit ConfigMaps konfigurieren
Jede Anwendung benötigt ein gewisses Maß an Konfiguration. Das kann die Anzahl der Journaleinträge sein, die pro Seite angezeigt werden sollen, die Farbe eines bestimmten Hintergrunds, eine spezielle Feiertagsanzeige oder viele andere Arten der Konfiguration. In der Regel ist es eine bewährte Methode, solche Konfigurationsinformationen von der Anwendung selbst zu trennen.
Es gibt mehrere Gründe für diese Trennung. Der erste ist, dass du dieselbe Anwendungsbinärdatei je nach Einstellung mit unterschiedlichen Konfigurationen konfigurieren möchtest. In Europa möchtest du vielleicht ein Osterspecial beleuchten, während du in China ein Special zum chinesischen Neujahrsfest anzeigen möchtest. Neben dieser Spezialisierung auf die Umgebung gibt es auch Gründe für die Agilität dieser Trennung. Wenn du diese Funktionen per Code aktivierst, kannst du die aktiven Funktionen nur ändern, indem du eine neue Binärdatei erstellst und veröffentlichst, was ein teurer und langsamer Prozess sein kann.
Die Verwendung der Konfiguration zur Aktivierung einer Reihe von Funktionen bedeutet, dass du Funktionen schnell (und sogar dynamisch) aktivieren und deaktivieren kannst, um auf Benutzeranforderungen oder Fehler im Anwendungscode zu reagieren. Die Funktionen können für jede einzelne Funktion ein- und ausgeschaltet werden. Diese Flexibilität stellt sicher, dass du mit den meisten Funktionen kontinuierlich vorankommst, auch wenn einige zurückgenommen werden müssen, um Probleme mit der Leistung oder der Korrektheit zu beheben.
In Kubernetes wird diese Art der Konfiguration durch eine Ressource namens ConfigMap dargestellt. Eine ConfigMap enthält mehrere Schlüssel/Wertpaare, die Konfigurationsinformationen oder eine Datei darstellen. Diese Konfigurationsinformationen können einem Container in einem Pod entweder über Dateien oder Umgebungsvariablen zur Verfügung gestellt werden. Stell dir vor, du möchtest deine Online-Journal-Anwendung so konfigurieren, dass sie eine konfigurierbare Anzahl von Journaleinträgen pro Seite anzeigt. Um dies zu erreichen, kannst du eine ConfigMap wie folgt definieren:
kubectlcreate
configmap
frontend-config
--from-literal
=
journalEntries
=
10
Um deine Anwendung zu konfigurieren, legst du die Konfigurationsinformationen als Umgebungsvariable in der Anwendung selbst offen. Dazu fügst du der container
Ressource im Deployment, die du zuvor definiert hast, Folgendes hinzu:
...
# The containers array in the PodTemplate inside the Deployment
containers
:
-
name
:
frontend
...
env
:
-
name
:
JOURNAL_ENTRIES
valueFrom
:
configMapKeyRef
:
name
:
frontend-config
key
:
journalEntries
...
Obwohl hier gezeigt wird, wie du eine ConfigMap zur Konfiguration deiner Anwendung verwenden kannst, wirst du in der realen Welt der Deployments regelmäßige Änderungen an dieser Konfiguration vornehmen wollen, mindestens wöchentlich. Es mag verlockend sein, einfach die ConfigMap selbst zu ändern, aber das ist keine bewährte Methode für. Der erste Grund ist, dass eine Änderung der Konfiguration keine Aktualisierung der bestehenden Pods auslöst. Die Konfiguration wird erst dann übernommen, wenn der Pod neu gestartet wird. Daher ist der Rollout nicht gesundheitsorientiert und kann ad hoc oder zufällig erfolgen. Ein weiterer Grund ist, dass die einzige Versionierung der ConfigMap in deiner Versionskontrolle liegt und es sehr schwierig sein kann, ein Rollback durchzuführen.
Ein besserer Ansatz ist es, eine Versionsnummer in den Namen der ConfigMap selbst einzufügen. Anstatt sie frontend-config
zu nennen, nenne sie frontend-config-v1
. Wenn du eine Änderung vornehmen willst, erstellst du eine neue ConfigMap v2
und aktualisierst die Bereitstellungsressource, um diese Konfiguration zu verwenden. Wenn du dies tust, wird automatisch ein Bereitstellungs-Rollout ausgelöst, der die entsprechenden Zustandsprüfungen und Pausen zwischen den Änderungen nutzt. Solltest du jemals einen Rollback durchführen müssen, befindet sich die Konfiguration v1
im Cluster und der Rollback ist so einfach wie eine erneute Aktualisierung des Deployments .
Authentifizierung mit Geheimnissen verwalten
haben wir uns bisher noch nicht mit dem Redis-Dienst befasst, mit dem sich unser Frontend verbindet. Aber in jeder realen Anwendung müssen wir die Verbindungen zwischen unseren Diensten absichern. Zum einen, um die Sicherheit der Nutzer und ihrer Daten zu gewährleisten, und zum anderen, um Fehler wie die Verbindung eines Entwicklungs-Frontends mit einer Produktionsdatenbank zu vermeiden.
Die Redis-Datenbank wird mit einem einfachen Passwort authentifiziert. Es mag bequem sein, dieses Passwort im Quellcode deiner Anwendung oder in einer Datei in deinem Image zu speichern, aber das sind beides schlechte Ideen, und zwar aus einer Reihe von Gründen. Der erste Grund ist, dass du dein Geheimnis (das Kennwort) in eine Umgebung einschleust, in der du nicht unbedingt an die Zugriffskontrolle denkst. Wenn du ein Kennwort in deiner Quellcode-Kontrolle hinterlegst, verbindest du den Zugriff auf deinen Quellcode mit dem Zugriff auf alle Geheimnisse. Das ist nicht die beste Vorgehensweise, denn du wirst wahrscheinlich eine größere Anzahl von Nutzern haben, die auf deinen Quellcode zugreifen können, als sie eigentlich Zugriff auf deine Redis-Instanz haben sollten. Ebenso sollte jemand, der Zugriff auf dein Container-Image hat, nicht unbedingt auch Zugriff auf deine Produktionsdatenbank haben.
Neben den Bedenken hinsichtlich der Zugriffskontrolle gibt es noch einen weiteren Grund, Geheimnisse nicht an die Quellcodekontrolle und/oder Images zu binden: Parametrisierung. Du möchtest denselben Quellcode und dieselben Images in verschiedenen Umgebungen verwenden können (z. B. in der Entwicklungs-, Canary- und Produktionsumgebung). Wenn die Geheimnisse fest im Quellcode oder in einem Image gebunden sind, brauchst du für jede Umgebung ein anderes Image (oder einen anderen Code).
Nachdem du im vorherigen Abschnitt ConfigMaps gesehen hast, denkst du vielleicht sofort, dass das Passwort als Konfiguration gespeichert und dann als anwendungsspezifische Konfiguration in die Anwendung eingefügt werden könnte. Du hast völlig recht, wenn du glaubst, dass die Trennung von Konfiguration und Anwendung dasselbe ist wie die Trennung von Geheimnissen und Anwendung. Aber in Wahrheit ist ein Geheimnis ein wichtiges Konzept für sich. Wahrscheinlich willst du die Zugriffskontrolle, die Handhabung und die Aktualisierung von Geheimnissen anders handhaben als eine Konfiguration. Noch wichtiger ist, dass du möchtest, dass deine Entwickler beim Zugriff auf Secrets anders denken als beim Zugriff auf die Konfiguration. Aus diesen Gründen hat Kubernetes eine eingebaute Secret-Ressource für die Verwaltung geheimer Daten.
Du kannst ein geheimes Passwort für deine Redis-Datenbank wie folgt erstellen:
kubectlcreate
secret
generic
redis-passwd
--from-literal
=
passwd
=
${
RANDOM
}
Natürlich möchtest du vielleicht etwas anderes als eine Zufallszahl für dein Passwort verwenden. Außerdem möchtest du wahrscheinlich einen Dienst zur Verwaltung von Geheimnissen/Schlüsseln nutzen, entweder über deinen Cloud-Provider, wie Microsoft Azure Key Vault, oder ein Open-Source-Projekt, wie Vault von HashiCorp. Wenn du einen Schlüsselverwaltungsdienst verwendest, ist dieser in der Regel enger mit den Kubernetes-Geheimnissen verbunden.
Nachdem du das Redis-Passwort als Geheimnis in Kubernetes gespeichert hast, musst du dieses Geheimnis an die laufende Anwendung binden, wenn du sie in Kubernetes bereitstellst. Dazu kannst du ein Kubernetes Volume verwenden. Ein Volume ist eine Datei oder ein Verzeichnis, das in einen laufenden Container an einem benutzerdefinierten Ort eingebunden werden kann. Im Fall von Secrets wird das Volume als RAM-gestütztes tmpfs-Dateisystem erstellt und dann in den Container eingebunden. Dadurch wird sichergestellt, dass es für einen Angreifer sehr viel schwieriger ist, an die Geheimnisse heranzukommen, selbst wenn der Rechner physisch kompromittiert wird (was in der Cloud eher unwahrscheinlich, im Rechenzentrum aber möglich ist).
Hinweis
Die Geheimnisse in Kubernetes werden standardmäßig unverschlüsselt gespeichert. Wenn du die Geheimnisse verschlüsselt speichern möchtest, kannst du einen Schlüsselanbieter einbinden, der dir einen Schlüssel liefert, mit dem Kubernetes alle Geheimnisse im Cluster verschlüsselt. Beachte, dass dies zwar die Schlüssel gegen direkte Angriffe auf die etcd
Datenbank schützt, du aber trotzdem sicherstellen musst, dass der Zugriff über den Kubernetes-API-Server ordnungsgemäß gesichert ist.
Um ein geheimes Volume zu einem Deployment hinzuzufügen, musst du zwei neue Einträge in der YAML für das Deployment angeben. Der erste ist ein volume
Eintrag für den Pod, der das Volume zu dem Pod hinzufügt:
...
volumes
:
-
name
:
passwd-volume
secret
:
secretName
:
redis-passwd
Container Storage Interface (CSI)-Treiber ermöglichen es dir, Schlüsselverwaltungssysteme (KMS) zu nutzen, die sich außerhalb deines Kubernetes-Clusters befinden. Dies ist oft eine Voraussetzung für die Einhaltung von Vorschriften und Sicherheit in großen oder regulierten Organisationen. Wenn du einen dieser CSI-Treiber verwendest, würde dein Volume stattdessen wie folgt aussehen:
...
volumes
:
-
name
:
passwd-volume
csi
:
driver
:
secrets-store.csi.k8s.io
readOnly
:
true
volumeAttributes
:
secretProviderClass
:
"azure-sync"
...
Unabhängig davon, welche Methode du verwendest, musst du das im Pod definierte Volume in einen bestimmten Container mounten. Das tust du über das Feld volumeMounts
in der Containerbeschreibung:
...
volumeMounts
:
-
name
:
passwd-volume
readOnly
:
true
mountPath
:
"/etc/redis-passwd"
...
Dadurch wird das geheime Volume in das Verzeichnis redis-passwd
eingebunden, damit der Client-Code darauf zugreifen kann. Wenn du das alles zusammennimmst, hast du die vollständige Bereitstellung wie folgt:
apiVersion
:
apps/v1
kind
:
Deployment
metadata
:
labels
:
app
:
frontend
name
:
frontend
namespace
:
default
spec
:
replicas
:
2
selector
:
matchLabels
:
app
:
frontend
template
:
metadata
:
labels
:
app
:
frontend
spec
:
containers
:
-
image
:
my-repo/journal-server:v1-abcde
imagePullPolicy
:
IfNotPresent
name
:
frontend
volumeMounts
:
-
name
:
passwd-volume
readOnly
:
true
mountPath
:
"/etc/redis-passwd"
resources
:
requests
:
cpu
:
"1.0"
memory
:
"1G"
limits
:
cpu
:
"1.0"
memory
:
"1G"
volumes
:
-
name
:
passwd-volume
secret
:
secretName
:
redis-passwd
An dieser Stelle haben wir die Client-Anwendung so konfiguriert, dass sie über ein Geheimnis verfügt, mit dem sie sich beim Redis-Dienst authentifizieren kann. Die Konfiguration von Redis zur Verwendung dieses Passworts ist ähnlich: Wir binden es in den Redis-Pod ein und laden das Passwort aus der Datei .
Einsatz einer einfachen Stateful-Datenbank
Obwohl die Bereitstellung einer zustandsabhängigen Anwendung konzeptionell ähnlich ist wie die Bereitstellung eines Clients wie unseres Frontends, bringt der Zustand mehr Komplikationen mit sich. Erstens kann ein Pod in Kubernetes aus verschiedenen Gründen neu geplant werden, z. B. wegen des Zustands eines Knotens, eines Upgrades oder eines Rebalancings. In diesem Fall kann der Pod auf einen anderen Rechner umziehen. Wenn sich die Daten, die mit der Redis-Instanz verbunden sind, auf einem bestimmten Rechner oder im Container selbst befinden, gehen diese Daten verloren, wenn der Container migriert oder neu gestartet wird. Um dies zu verhindern, ist es wichtig, bei zustandsbehafteten Workloads in Kubernetes entfernte PersistentVolumeszu verwenden, um den mit der Anwendung verbundenen Zustand zu verwalten.
Es gibt eine Vielzahl von Implementierungen von PersistentVolumes in Kubernetes, aber sie haben alle gemeinsame Merkmale. Wie die zuvor beschriebenen geheimen Volumes sind sie mit einem Pod verbunden und werden an einem bestimmten Ort in einen Container eingebunden. Im Gegensatz zu Secrets handelt es sich bei PersistentVolumes in der Regel um eine entfernte Speicherung, die über eine Art Netzwerkprotokoll eingebunden wird, entweder dateibasiert (z. B. Network File System (NFS) oder Server Message Block (SMB)) oder blockbasiert (iSCSI, Cloud-basierte Festplatten usw.). Für Anwendungen wie Datenbanken sind blockbasierte Festplatten in der Regel vorzuziehen, da sie eine bessere Leistung bieten, aber wenn die Leistung weniger wichtig ist, bieten dateibasierte Festplatten manchmal mehr Flexibilität.
Hinweis
Die Verwaltung von Zuständen ist im Allgemeinen kompliziert, und Kubernetes ist da keine Ausnahme. Wenn du in einer Umgebung arbeitest, die zustandsabhängige Dienste unterstützt (z. B. MySQL als Dienst, Redis als Dienst),ist es in der Regel eine gute Idee, diese zustandsabhängigen Dienste zu nutzen. Auf den ersten Blick mag der Preisaufschlag für eine zustandsabhängige Software as a Service (SaaS) teuer erscheinen, aber wenn du alle betrieblichen Anforderungen des Zustands (Backup, Datenlokalität, Redundanz usw.) berücksichtigst und die Tatsache, dass die Anwesenheit von Zuständen in einem Kubernetes-Cluster es schwierig macht, Anwendungen zwischen Clustern zu verschieben, wird klar, dass die Speicherung als SaaS in den meisten Fällen den Preisaufschlag wert ist. In lokalen Umgebungen, in denen Speicher-SaaS nicht verfügbar ist, ist es auf jeden Fall besser, ein spezielles Team mit der Speicherung als Service für das gesamte Unternehmen zu beauftragen, als wenn jedes Team die Speicherung selbst entwickelt.
Für die Bereitstellung unseres Redis-Dienstes verwenden wir eine StatefulSet-Ressource. StatefulSets wurden nach dem ersten Kubernetes-Release als Ergänzung zu ReplicaSet-Ressourcen eingeführt und bieten etwas stärkere Garantien wie konsistente Namen (keine zufälligen Hashes!) und eine definierte Reihenfolge für Scale-up und Scale-down. Wenn du ein Singleton deployen willst, ist das weniger wichtig, aber wenn du einen replizierten Zustand deployen willst, sind diese Attribute sehr praktisch.
Um ein PersistentVolume für unser Redis zu erhalten, verwenden wir einen PersistentVolumeClaim. Du kannst dir einen Claim als "Anfrage nach Ressourcen" vorstellen. Unser Redis gibt abstrakt an, dass er 50 GB Speicherung benötigt, und der Kubernetes-Cluster bestimmt, wie er ein entsprechendes PersistentVolume bereitstellen kann. Hierfür gibt es zwei Gründe. Der erste ist, dass wir ein StatefulSet schreiben können, das zwischen verschiedenen Clouds und vor Ort portabel ist, wo die Details der Festplatten unterschiedlich sein können. Der andere Grund ist, dass viele PersistentVolume-Typen zwar nur auf einen einzigen Pod gemountet werden können, wir aber mit Hilfe von Volume-Ansprüchen eine Vorlage schreiben können, die repliziert werden kann und trotzdem jedem Pod ein eigenes PersistentVolume zuweist.
Das folgende Beispiel zeigt ein Redis StatefulSet mit PersistentVolumes:
apiVersion
:
apps/v1
kind
:
StatefulSet
metadata
:
name
:
redis
spec
:
serviceName
:
"redis"
replicas
:
1
selector
:
matchLabels
:
app
:
redis
template
:
metadata
:
labels
:
app
:
redis
spec
:
containers
:
-
name
:
redis
image
:
redis:5-alpine
ports
:
-
containerPort
:
6379
name
:
redis
volumeMounts
:
-
name
:
data
mountPath
:
/data
volumeClaimTemplates
:
-
metadata
:
name
:
data
spec
:
accessModes
:
[
"ReadWriteOnce"
]
resources
:
requests
:
storage
:
10Gi
Damit wird eine einzelne Instanz deines Redis-Dienstes eingerichtet. Angenommen, du möchtest den Redis-Cluster replizieren, um Lesezugriffe zu skalieren und gegen Ausfälle gewappnet zu sein. Dazu musst du natürlich die Anzahl der Replikate auf drei erhöhen, aber du musst auch sicherstellen, dass die beiden neuen Replikate mit dem Schreibmaster für Redis verbunden sind. Wie du diese Verbindung herstellst, erfährst du im folgenden Abschnitt.
Wenn du den Headless Service für das Redis StatefulSet erstellst, wird ein DNS-Eintragredis-0.redis
erstellt; das ist die IP-Adresse des ersten Replikats. Damit kannst du ein einfaches Skript erstellen, das in allen Containern gestartet werden kann:
#!/bin/sh
PASSWORD
=
$(
cat/etc/redis-passwd/passwd
)
if
[[
"
${
HOSTNAME
}
"
==
"redis-0"
]]
;
then
redis-server
--requirepass
${
PASSWORD
}
else
redis-server
--slaveof
redis-0.redis
6379
--masterauth
${
PASSWORD
}
--requirepass
${
PASSWORD
}
fi
Du kannst dieses Skript als ConfigMap erstellen:
kubectlcreate
configmap
redis-config
--from-file
=
./launch.sh
Dann fügst du diese ConfigMap zu deinem StatefulSet hinzu und verwendest sie als Befehl für den Container. Fügen wir auch das Passwort für die Authentifizierung hinzu, das wir weiter oben im Kapitel erstellt haben.
Das vollständige Replikat von Redis sieht wie folgt aus:
apiVersion
:
apps/v1
kind
:
StatefulSet
metadata
:
name
:
redis
spec
:
serviceName
:
"redis"
replicas
:
3
selector
:
matchLabels
:
app
:
redis
template
:
metadata
:
labels
:
app
:
redis
spec
:
containers
:
-
name
:
redis
image
:
redis:5-alpine
ports
:
-
containerPort
:
6379
name
:
redis
volumeMounts
:
-
name
:
data
mountPath
:
/data
-
name
:
script
mountPath
:
/script/launch.sh
subPath
:
launch.sh
-
name
:
passwd-volume
mountPath
:
/etc/redis-passwd
command
:
-
sh
-
-c
-
/script/launch.sh
volumes
:
-
name
:
script
configMap
:
name
:
redis-config
defaultMode
:
0777
-
name
:
passwd-volume
secret
:
secretName
:
redis-passwd
volumeClaimTemplates
:
-
metadata
:
name
:
data
spec
:
accessModes
:
[
"ReadWriteOnce"
]
resources
:
requests
:
storage
:
10Gi
Jetzt ist dein Redis für Fehlertoleranz geclustert. Wenn eines der drei Redis-Replikate aus irgendeinem Grund fehlschlägt, kann deine Anwendung mit den beiden verbleibenden Replikaten weiterlaufen, bis das dritte Replikat wiederhergestellt ist .
Erstellen eines TCP Load Balancers mit Hilfe von Diensten
Nachdem wir den zustandsbehafteten Redis-Dienst bereitgestellt haben, müssen wir ihn für unser Frontend verfügbar machen. Dazu erstellen wir zwei verschiedene Kubernetes Services. Der erste ist der Dienst zum Lesen der Daten aus Redis. Da Redis die Daten an alle drei Mitglieder des StatefulSet repliziert, ist es für uns egal, an welchen Service unsere Anfrage geht. Daher verwenden wir einen Basisdienst für die Lesevorgänge:
apiVersion
:
v1
kind
:
Service
metadata
:
labels
:
app
:
redis
name
:
redis
namespace
:
default
spec
:
ports
:
-
port
:
6379
protocol
:
TCP
targetPort
:
6379
selector
:
app
:
redis
sessionAffinity
:
None
type
:
ClusterIP
Um Schreibzugriffe zu ermöglichen, musst du den Redis-Master (Replikat Nr. 0) anvisieren. Dazu erstellst du einen Headless Service. Ein headless Service hat keine Cluster-IP-Adresse, sondern programmiert einen DNS-Eintrag für jeden Pod im StatefulSet. Das bedeutet, dass wir unseren Master über den DNS-Namen redis-0.redis
erreichen können:
apiVersion
:
v1
kind
:
Service
metadata
:
labels
:
app
:
redis-write
name
:
redis-write
spec
:
clusterIP
:
None
ports
:
-
port
:
6379
selector
:
app
:
redis
Wenn wir uns also für Schreibvorgänge oder transaktionale Lese-/Schreibpaare mit Redis verbinden wollen, können wir einen separaten Schreib-Client erstellen, der mit dem redis-0.redis-write
Server verbunden ist.
Ingress zur Weiterleitung des Datenverkehrs an einen statischen Dateiserver verwenden
Die letzte Komponente unserer Anwendung ist ein statischer Dateiserver. Der statische Dateiserver ist für die Bereitstellung von HTML-, CSS-, JavaScript- und Bilddateien zuständig. Es ist sowohl effizienter als auch zielgerichteter, wenn wir die Bereitstellung statischer Dateien von unserem zuvor beschriebenen API-Frontend trennen. Wir können einen leistungsstarken statischen Dateiserver wie NGINX für die Bereitstellung von Dateien verwenden, während sich unsere Entwicklungsteams auf den Code konzentrieren können, der für die Implementierung unserer API erforderlich ist.
Zum Glück macht die Ingress-Ressource diese Art von Mini-Microservice-Architektur sehr einfach. Genau wie im Frontend können wir eine Bereitstellungsressource verwenden, um einen replizierten NGINX-Server zu beschreiben. Wir bauen die statischen Images in den NGINX-Container ein und verteilen sie an jedes Replikat. Die Bereitstellungsressource sieht wie folgt aus:
apiVersion
:
apps/v1
kind
:
Deployment
metadata
:
labels
:
app
:
fileserver
name
:
fileserver
namespace
:
default
spec
:
replicas
:
2
selector
:
matchLabels
:
app
:
fileserver
template
:
metadata
:
labels
:
app
:
fileserver
spec
:
containers
:
# This image is intended as an example, replace it with your own
# static files image.
-
image
:
my-repo/static-files:v1-abcde
imagePullPolicy
:
Always
name
:
fileserver
terminationMessagePath
:
/dev/termination-log
terminationMessagePolicy
:
File
resources
:
requests
:
cpu
:
"1.0"
memory
:
"1G"
limits
:
cpu
:
"1.0"
memory
:
"1G"
dnsPolicy
:
ClusterFirst
restartPolicy
:
Always
Nachdem nun ein replizierter statischer Webserver in Betrieb ist, erstellst du ebenfalls eine Service-Ressource, die als Load Balancer fungiert:
apiVersion
:
v1
kind
:
Service
metadata
:
labels
:
app
:
fileserver
name
:
fileserver
namespace
:
default
spec
:
ports
:
-
port
:
80
protocol
:
TCP
targetPort
:
80
selector
:
app
:
fileserver
sessionAffinity
:
None
type
:
ClusterIP
Da du nun einen Dienst für deinen statischen Dateiserver hast, erweitere die Ingress-Ressource um den neuen Pfad. Wichtig ist, dass du den Pfad /
nach dem Pfad /api
einfügst, da er sonst /api
subsumiert und API-Anfragen an den statischen Dateiserver weiterleitet. Der neue Ingress sieht wie folgt aus:
apiVersion
:
networking.k8s.io/v1
kind
:
Ingress
metadata
:
name
:
frontend-ingress
spec
:
rules
:
-
http
:
paths
:
-
path
:
/api
pathType
:
Prefix
backend
:
service
:
name
:
fileserver
port
:
number
:
8080
# NOTE: this should come after /api or else it will hijack requests
-
path
:
/
pathType
:
Prefix
backend
:
service
:
name
:
fileserver
port
:
number
:
80
Jetzt, wo du eine Ingress-Ressource für deinen Dateiserver eingerichtet hast, zusätzlich zum Ingress für die API, die du zuvor eingerichtet hast, ist die Benutzeroberfläche der Anwendung einsatzbereit. Die meisten modernen Anwendungen kombinieren statische Dateien, in der Regel HTML und JavaScript, mit einem dynamischen API-Server, der in einer serverseitigen Programmiersprache wie Java, .NET oder Go implementiert ist.
Parametrisierung deiner Anwendung mit Helm
Alles, was wir bisher besprochen haben, konzentriert sich auf die Bereitstellung einer einzelnen Instanz unseres Dienstes in einem einzigen Cluster. In der Realität muss jedoch fast jeder Dienst und jedes Dienstteam in mehreren Umgebungen bereitgestellt werden (selbst wenn sie sich einen Cluster teilen). Selbst wenn du nur ein einziger Entwickler bist, der an einer einzigen Anwendung arbeitet, möchtest du wahrscheinlich mindestens eine Entwicklungs- und eine Produktionsversion deiner Anwendung haben, damit du iterieren und entwickeln kannst, ohne die Produktionsbenutzer zu beeinträchtigen. Wenn du Integrationstests und CI/CD mit einbeziehst, ist es wahrscheinlich, dass du selbst mit einem einzigen Dienst und einer Handvoll Entwickler mindestens drei verschiedene Umgebungen bereitstellen musst, und möglicherweise mehr, wenn du Ausfälle auf Rechenzentrumsebene in Betracht ziehst. Sehen wir uns ein paar Optionen für die Bereitstellung an.
Ein anfänglicher Fehler vieler Teams besteht darin, die Dateien einfach von einem Cluster in einen anderen zu kopieren. Anstelle eines einzigen frontend/-Verzeichnissesgibt es dann zwei Verzeichnisse: frontend-production/ und frontend-development/. Das ist zwar eine praktikable Option, aber auch gefährlich, weil du jetzt dafür verantwortlich bist, dass diese Dateien miteinander synchronisiert bleiben. Wenn sie völlig identisch sein sollten, wäre das einfach, aber da du neue Funktionen entwickelst, ist eine gewisse Abweichung zwischen Entwicklung und Produktion zu erwarten. Es ist wichtig, dass diese Abweichung gewollt und leicht zu handhaben ist .
Eine andere Möglichkeit, dies zu erreichen, wäre die Verwendung von Zweigen und Versionskontrolle, wobei die Produktions- und Entwicklungszweige von einem zentralen Repository abgehen und die Unterschiede zwischen den Zweigen deutlich sichtbar sind. Für manche Teams kann dies eine praktikable Option sein, aber die Mechanismen des Wechsels zwischen Zweigen sind eine Herausforderung, wenn du Software gleichzeitig in verschiedenen Umgebungen bereitstellen willst (z. B. ein CI/CD-System, das in verschiedenen Cloud-Regionen eingesetzt wird).
Daher verwenden die meisten Leute ein Templating-System. Ein Templating-System kombiniert Templates, die das zentrale Rückgrat der Anwendungskonfiguration bilden, mit Parametern, die das Template auf eine bestimmte Umgebungskonfigurationspezialisieren. Auf diese Weise kannst du eine allgemein geteilte Konfiguration haben, die du bei Bedarf absichtlich (und leicht verständlich) anpassen kannst. Es gibt eine Vielzahl von Templating-Systemen für Kubernetes, aber das mit Abstand beliebteste ist Helm.
In Helm wird eine Anwendung in eine Sammlung von Dateien gepackt, die Chart genannt wird (in der Welt der Container und Kubernetes gibt es viele nautische Witze).
Ein Diagramm beginnt mit einer chart.yaml-Datei, die die Metadaten für das Diagramm selbst definiert:
apiVersion
:
v1
appVersion
:
"1.0"
description
:
A Helm chart for our frontend journal server.
name
:
frontend
version
:
0.1.0
Diese Datei befindet sich im Stammverzeichnis des Diagramms (z. B. frontend/). Innerhalb dieses Verzeichnisses gibt es ein Verzeichnis templates, in dem die Templates abgelegt werden. Eine Vorlage ist im Grunde eine YAML-Datei aus den vorherigen Beispielen, wobei einige der Werte in der Datei durch Parameterreferenzen ersetzt werden. Stell dir zum Beispiel vor, dass du die Anzahl der Replikate in deinem Frontend parametrisieren willst. Zuvor hatte das Deployment folgende Werte:
...
spec
:
replicas
:
2
...
In der Vorlagendatei(frontend-deployment.tmpl) sieht es stattdessen wie folgt aus:
...
spec
:
replicas
:
{{
.replicaCount
}}
...
Das bedeutet, dass du bei der Bereitstellung der Karte den Wert für Replikate durch den entsprechenden Parameter ersetzt. Die Parameter selbst werden in einer values.yaml-Datei definiert. Für jede Umgebung, in der die Anwendung eingesetzt werden soll, gibt es eine Wertedatei. Die Wertedatei für dieses einfache Diagramm würde wie folgt aussehen:
replicaCount
:
2
Wenn du das alles begriffen hast, kannst du dieses Diagramm mit dem Tool helm
wie folgt einsetzen:
helminstall
path/to/chart
--values
path/to/environment/values.yaml
Dadurch wird deine Anwendung parametrisiert und in Kubernetes deployed. Mit der Zeit werden diese Parametrisierungen wachsen, um die Vielfalt der Umgebungen für deine Anwendung zu erfassen.
Bewährte Methoden für den Einsatz von Diensten
Kubernetes ist ein mächtiges System, das komplex erscheinen kann. Wenn du die folgenden bewährten Methoden befolgst, kann die Einrichtung einer einfachen Anwendung jedoch ganz einfach sein:
-
Die meisten Dienste sollten als Bereitstellungsressourcen bereitgestellt werden. Einsätze schaffen identische Replikate für Redundanz und Skalierung.
-
Einsätze können mit Hilfe eines Dienstes ausgesetzt werden, der quasi ein Load Balancer ist. Ein Service kann entweder innerhalb eines Clusters (Standard) oder extern bereitgestellt werden. Wenn du eine HTTP-Anwendung bereitstellen willst, kannst du einen Ingress-Controller verwenden, um Dinge wie Request Routing und SSL hinzuzufügen.
-
Irgendwann wirst du deine Anwendung parametrisieren wollen, um ihre Konfiguration in verschiedenen Umgebungen wiederverwendbar zu machen. Paketierungs-Tools wie Helm sind die beste Wahl für diese Art der Parametrisierung.
Zusammenfassung
Die in diesem Kapitel erstellte Anwendung ist einfach, aber sie enthält fast alle Konzepte, die du brauchst, um größere, kompliziertere Anwendungen zu erstellen. Um erfolgreich mit Kubernetes arbeiten zu können, musst du verstehen, wie die Teile zusammenpassen und wie du die grundlegenden Kubernetes-Komponenten verwendest.
Wenn du mit Versionskontrolle, Codeüberprüfung und kontinuierlicher Bereitstellung deines Dienstes das richtige Fundament legst, kannst du sicher sein, dass das, was du baust, auch wirklich solide gebaut ist. Wenn wir in den folgenden Kapiteln die fortgeschrittenen Themen durchgehen, solltest du diese grundlegenden Informationen im Hinterkopf behalten.
Get Kubernetes Best Practices, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.