Auf dem richtigen Kurs mit Best Practices für Helm-Charts
Kubernetes, das beliebte Orchestrierungswerkzeug für Containeranwendungen, ist nach dem griechischen Wort für “Lotse” benannt, also demjenigen, der das Schiff steuert. Aber wie bei jeder Reise kann der Navigator nur so erfolgreich sein wie die verfügbare Karte.
Das Helm-Chart ist eine Karte, also eine Sammlung von Dateien, die von einem Helm-Chart-Repository, das einen zusammenhängenden Satz von K8s-Ressourcen beschreibt, eingesetzt werden können. Wenn Sie Ihre Helm-Charts auf die effektivste Art und Weise gestalten, hilft dies Kubernetes, durch die Untiefen zu manövrieren, wenn es Container in Ihrer Produktionsumgebung bereitstellt.
Aber es gibt auch andere Möglichkeiten, wie ich bei der Entwicklung von öffentlich zugänglichen K8s-Charts zum Deployment von Produkten festgestellt habe. Mit jeder Pull-Anfrage hat mir das Feedback der Helm-Community geholfen, bewährte Verfahren für Helm-Charts zu finden, mit denen die besten Ergebnisse für die Nutzung und Aktualisierung von Containern sichergestellt wurden.
Einige Dinge sind zu beachten, wenn Sie K8s-Charts schreiben, die von der Community oder Kunden in der Produktion verwendet werden sollen. Zu den Dingen, an die Sie denken müssen, gehört:
- Welche Abhängigkeiten müssen Sie definieren?
- Benötigt Ihre Anwendung einen persistenten Zustand, um zu funktionieren?
- Wie handhaben Sie die Sicherheit durch Vertraulichkeiten und Berechtigungen?
- Wie kontrollieren Sie die Ausführung von Kubelet-Containern?
- Wie stellen Sie sicher, dass Ihre Anwendungen laufen und Anrufe empfangen können?
- Wie stellen Sie die Dienste der Anwendung der Welt zur Verfügung?
- Wie testen Sie Ihr Chart?
Dieser Leitfaden bietet einige Best Practices zur Strukturierung und Spezifikation Ihrer Helm-Charts, die K8s dabei helfen, Ihre Container-Anwendungen reibungslos ins Dock zu bringen.
Erste Schritte
Bevor Sie beginnen, stellen Sie sicher, dass Sie mit den wesentlichen Verfahren zur Entwicklung von Helm-Charts vertraut sind.
In diesem Leitfaden erstellen wir ein Helm-Chart, das den empfohlenen Best Practices folgt, um eine zweistufige Erstellen, Lesen, Aktualisieren und Löschen-Anwendung (Create, Read, Update and Delete = CRUD) für die Mongo-Datenbank unter Verwendung von Express.js bereitzustellen.
Sie finden den Quellcode unserer Beispielanwendung in express-crud in GitHub.
Das Helm-Chart erstellen und ausfüllen
Lassen Sie uns das Vorlagen-Helm-Chart mit dem create-Befehl des Helm-Client erstellen:
$ helm create express-crud
Dadurch wird eine Verzeichnisstruktur für ein express-crud-Helm-Chart erstellt.
Aktualisieren Sie zunächst die Chart-Metadaten in der Chart.yaml-Datei, die gerade erstellt wurde. Stellen Sie sicher, dass Sie die richtigen Informationen für appVersion (die Anwendungsversion, die als Docker-Image-Tag verwendet werden soll), description, version (ein SemVer 2-Versionsstring), sources, maintainers und icon eingeben.
apiVersion: v1
appVersion: "1.0.0"
description: Ein Helm-Chart für die Express-Crud-Anwendung
name: express-crud
version: 0.1.0
sources:
- https://github.com/jainishshah17/express-mongo-crud
maintainers:
- name: myaccount
email: myacount@mycompany.com
icon: https://github.com/mycompany17/mycompany.com/blob/master/app/public/images/logo.jpg
home: https://mycompany.com/
Definieren von Abhängigkeiten
Wenn Ihre Anwendung Abhängigkeitenhat, dann müssen Sie eine requirements.yaml-Datei in der Verzeichnisstruktur des Helm-Charts erstellen, die diese spezifiziert. Da unsere Anwendung die mongodb-Datenbank benötigt, müssen wir sie in der Abhängigkeiten-Liste der requirements.yaml-Datei spezifizieren, die wir erstellen.
Eine requirements.yaml für dieses Beispiel enthält:
dependencies:
- name: mongodb
version: 3.0.4
repository: https://kubernetes-charts.storage.googleapis.com/
condition: mongodb.enabled
Sobald eine requirements.yaml-Datei erstellt ist, müssen Sie den dependency update-Befehl im Helm-Client ausführen:
$ helm dep update
Deployment-Dateien erstellen
Die Deployment-Dateien Ihres Helm-Charts befinden sich im \templates-Unterverzeichnis und geben an, wie K8s die Container-Anwendung bereitstellt.
Bei der Entwicklung Ihrer Deployment-Dateien müssen Sie einige wichtige Entscheidungen treffen.
Deployment-Objekt im Vergleich zum StatefulSet-Objekt
Welche Deployment-Datei Sie erstellen, hängt davon ab, ob es die Anwendung erforderlich macht, dass K8s sie als Deployment-Objekt oder als StatefulSet-Objekt verwaltet.
Ein Deployment-Objekt ist eine zustandslose Anwendung, die im Dateinamen deployment.yaml deklariert ist und die kind-Parameter als deployment spezifiziert.
Ein Stateful-Objekt ist für Anwendungen, die zustandsabhängig sind und mit verteilten Systemen verwendet werden. Sie werden im Dateinamen stateless.yaml deklariert und spezifizieren den kind-Parameter als stateful.
Deployment | StatefulSet |
Deployments sind für die zustandslose Nutzung gedacht und eher leichtgewichtig. | StatefulSets werden verwendet, wenn der Zustand persistiert muss. Daher werden volumeClaimTemplates auf persistenten Volumes verwendet, um sicherzustellen, dass sie den Status über Komponenten-Neustarts hinweg beibehalten können. |
Wenn Ihre Anwendung zustandslos ist oder wenn der Zustand während des Starts aus Backend-Systemen aufgebaut werden kann, dann verwenden Sie Deployments. | Wenn Ihre Anwendung zustandsabhängig ist oder wenn Sie zustandsabhängigen Speicher über Kubernetes bereitstellen möchten, verwenden Sie ein StatefulSet. |
Da bei dieser Anwendung der Zustand nicht persistiert werden muss, verwende ich ein Deployment-Objekt.
Die deployment.yaml-Datei ist bereits durch den helm create-Befehl erstellt worden.
Wir werden AppVersion als Docker-Image-Tag für unsere Anwendung verwenden. Dadurch sind wir in der Lage, das Helm-Chart mit einer neuen Version der Anwendung zu aktualisieren, indem wir einfach den Wert in Chart.yaml ändern
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}"
Secret im Vergleich zu ConfigMap
Sie müssen festlegen, welche der Anmeldeinformationen oder Konfigurationsdaten geeignet sind, um sie als Secrets zu speichern, und welche in einer ConfigMap enthalten sein können.
Secrets sind für sensible Informationen wie Passwörter, die K8s in einem verschlüsselten Format speichert.
Eine ConfigMap ist eine Datei mit Konfigurationsinformationen, die von Anwendungen gemeinsam genutzt werden können. Die Informationen in einer ConfigMap sind nicht verschlüsselt, sollten also keine sensiblen Informationen enthalten.
Secret | ConfigMap |
Diese Informationen in ein Secret zu packen ist sicherer und flexibler als sie wortwörtlich in eine Pod-Definition oder in ein Docker-Image zu packen; | Mit einer ConfigMap können Sie Konfigurationsartefakte vom Image-Inhalt entkoppeln, um containerisierte Anwendungen portabel zu halten |
Wird für vertrauliche Daten verwendet | Wird für nicht vertrauliche Daten verwendet |
Anwendungsbeispiele: API-Schlüssel, Passwort, Token und ssh-Schlüssel | Anwendungsbeispiele: Log-Rotatoren, Konfiguration ohne vertrauliche Daten |
In diesem Beispiel ermöglichen wir Helm, Images von privaten Docker-Registries unter Verwendung von Image-Pull-Secrets abzurufen.
Dieses Verfahren setzt voraus, dass dem Kubernetes-Cluster ein Secret zur Verfügung steht, das die Anmeldedaten für den Repository-Manager angibt. Dieses Secret kann über eine kubectl-Befehlszeile wie folgt erstellt werden:
$ kubectl create secret docker-registry regsecret --docker-server=$DOCKER_REGISTRY_RUL --docker-username=$USERNAME --docker-password=$PASSWORD --docker-email=$EMAIL
In der values.yaml-Datei Ihres Helm-Charts können Sie dann den Secret-Namen an einen Wert übergeben:
imagePullSecrets: regsecret
Sie können dann das Secret verwenden, um Helm den Zugriff auf die Docker-Registry über diese Zeilen in deployment.yaml zu gewähren:
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
- name: {{ .Values.imagePullSecrets }}
{{- end }}
Für Secrets, die für die Anwendung verfügbar sind, sollten Sie diese Informationen direkt in values.yaml eingeben.
Um zum Beispiel unsere Anwendung so zu konfigurieren, dass sie auf mongodb mit einem vorab erstellten Benutzer und einer Datenbank zugreift, geben Sie diese Informationen in values.yaml ein
mongodb:
enabled: true
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
Beachten Sie, dass wir hier keine Standard-Anmeldeinformationen in unserem Helm-Chart hartcodieren. Stattdessen verwenden wir eine Logik zur zufälligen Generierung des Passworts, wenn es nicht über –set flag oder values.yaml bereitgestellt wird
Wir verwenden ein Secret, um die Mongodb-Anmeldeinformationen an unsere Anwendung zu übergeben, und zwar durch diese Zeilen in deployment.yaml.
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-mongodb
key: mongodb-password
Sie können die Ausführung von Kubelet-Containern entweder durch spezialisierte Init-Container oder durch Container-Lifecycle-Hooks kontrollieren.
InitContainer | Container-Lifecycle-Hooks |
InitContainer sind spezialisierte Container, die vor App-Containern ausgeführt werden und Dienstprogramme oder Setup-Skripte enthalten können, die in einem App-Image nicht vorhanden sind. | Container können das Container-Lifecycle-Hook-Framework verwenden, um Code auszuführen, der durch Ereignisse während ihres Verwaltungslebenszyklus ausgelöst wird. |
Ein Pod kann einen oder mehrere Init-Container haben, die ausgeführt werden, bevor die App-Container gestartet werden.
|
Ein Pod kann nur einen PostStart– oder PreStop-Hook haben |
Der PostStart-Hook wird unmittelbar nach der Erstellung eines Containers ausgeführt. Es gibt jedoch keine Garantie, dass der Hook vor dem Container-ENTRYPOINT ausgeführt wird. Es werden keine Parameter an den Handler übergeben. z. B. Verschieben von Dateien, die mit ConfigMap/Secrets gemountet wurden, an einen anderen Ort. |
|
Der PreStop-Hook wird unmittelbar vor dem Beenden eines Containers aufgerufen. Er blockiert, d.h. er ist synchron und muss daher abgeschlossen werden, bevor der Aufruf zum Löschen des Containers gesendet werden kann.
z.B. Anwendung sanft herunterfahren |
|
Sie können initContainers verwenden, um Wartezeiten hinzuzufügen und so zu prüfen, ob abhängige Microservices funktionsfähig sind, bevor Sie fortfahren. | Sie können PostStart -Hook zum Aktualisieren von Dateien im selben Pod verwenden, z.B. um Konfigurationsdateien mit Service IP zu aktualisieren |
In unserem Beispiel fügen Sie diese initContainers -Spezifikationen der deployments.yaml hinzu, um den Start unserer Anwendung zu verzögern, bis die Datenbank eingerichtet ist.
initContainers:
- name: wait-for-db
image: "{{ .Values.initContainerImage }}"
command:
- 'sh'
- '-c'
- >
until nc -z -w 2 {{ .Release.Name }}-mongodb 27017 && echo mongodb ok;
do sleep 2;
done
Hinzufügen von Bereitschafts- und Aktivitätssonden
Es ist oft eine gute Idee, eine Readiness- und eine Liveness-Probe hinzuzufügen, um den laufenden Zustand der Anwendung zu überprüfen. Wenn Sie das nicht tun, könnte es sein, dass die Anwendung zwar zu laufen scheint, aber nicht auf Aufrufe oder Abfragen reagiert.
Diese Zeilen in der deployment.yaml-Datei fügen diese Probes hinzu, um regelmäßige Prüfungen durchzuführen:
livenessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
readinessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
Hinzufügen von RBAC-Unterstützung
Diese Prozeduren fügen Unterstützung für rollenbasierte Zugriffskontrolle (RBAC) unserem Chart hinzu, wenn sie von einer Anwendung benötigt wird.
Schritt 1: Erstellen Sie eine Rolle, indem Sie den folgenden Inhalt in einer role.yaml-Datei hinzufügen:
Eine Rolle kann nur verwendet werden, um Zugriff auf Ressourcen innerhalb eines einzelnen Namespace zu gewähren.
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
rules:
{{ toYaml .Values.rbac.role.rules }}
{{- end }}
Schritt 2: Erstellen Sie ein RoleBinding, indem Sie den folgenden Inhalt in einer rolebinding.yaml-Datei hinzufügen:
Eine ClusterRole kann verwendet werden, um die gleichen Berechtigungen wie eine Rolle zu gewähren, aber da sie zum Cluster-Geltungsbereich gehören, können sie auch verwendet werden, um Zugriff auf Folgendes zu gewähren:
- Cluster-bezogene Ressourcen (wie Knoten)
- nicht-Ressourcen-Endpunkte (wie “/healthz”)
- namespaced-Ressourcen (wie Pods) in allen Namespaces
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "express-crud.serviceAccountName" . }}
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: {{ template "express-crud.fullname" . }}
{{- end }}
Schritt 3: Erstellen Sie ein ServiceAccount durch Hinzufügen des folgenden Inhalts in eine serviceaccount.yaml-Datei:
Ein Service Account bietet eine Identität für Prozesse, die in einem Pod laufen.
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.serviceAccountName" . }}
{{- end }}
Schritt 4: Verwenden Sie die Hilfsvorlage, um den ServiceAccount-Namen festzulegen.
Wir tun dies, indem wir folgenden Inhalt in der _helpers.tpl-Datei hinzufügen
{{/*
Erstellen Sie den Namen des zu verwendenden Service Accounts
*/}}
{{- define "express-crud.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "express-crud.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
Einen Dienst hinzufügen
Jetzt ist es an der Zeit, unsere Anwendung der Öffentlichkeit über einen entsprechenden Dienst zugänglich zu machen.
Ein Dienst ermöglicht es Ihrer Anwendung, Datenverkehr über eine IP-Adresse zu empfangen. Dienste können auf unterschiedliche Weise exponiert werden, indem ein Typ angegeben wird:
ClusterIP | Der Dienst ist nur über eine interne IP von innerhalb des Clusters erreichbar. |
NodePort | Der Dienst ist von außerhalb des Clusters über die NodeIP und den NodePort erreichbar. |
LoadBalancer | Der Dienst ist von außerhalb des Clusters über einen externen Load Balancer erreichbar. Kann Ingress-Funktion für die Anwendung haben. |
Wir tun dies, indem wir den folgenden Inhalt der service.yaml-Datei hinzufügen:
apiVersion: v1
kind: Service
metadata:
name: {{ template "express-crud.fullname" . }}
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.externalPort }}
targetPort: http
protocol: TCP
name: http
selector:
app: {{ template "express-crud.name" . }}
release: {{ .Release.Name }}
Beachten Sie, dass wir im obigen Beispiel für unseren Dienst type auf eine Einstellung in unserer values.yaml verweisen:
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
Values.yaml-Zusammenfassung
Viele unserer Einstellungen in einer values.yaml -Datei zu definieren, ist eine gute Praxis, um die Helm-Charts wartbar zu halten.
So sieht die values.yaml-Datei für unser Beispiel aus, wobei die Vielfalt der Einstellungen ersichtlich ist, die wir für viele der oben besprochenen Funktionen definieren:
# Standardwerte für express-mongo-crud.
# Dies ist eine YAML-formatierte Datei.
# Deklarieren Sie Variablen, die an Ihre Vorlagen übergeben werden sollen.
## Rollenbasierte Zugriffskontrolle
## Ref: https://kubernetes.io/docs/admin/authorization/rbac/
rbac:
create: true
role:
## Zu erstellende Regeln. Gemäß Rollenspezifikation
rules:
- apiGroups:
- ''
resources:
- services
- endpoints
- pods
verbs:
- get
- watch
- list
## Service Account
## Ref: https://kubernetes.io/docs/admin/service-accounts-admin/
##
serviceAccount:
create: true
## Der Name des zu verwendenden ServiceAccounts.
## Wenn nicht eingestellt und create = true, wird ein Name unter Verwendung der fullname-Vorlage erzeugt
name:
## Konfigurationswerte für die mongodb-Abhängigkeit
## ref: https://github.com/kubernetes/charts/blob/master/stable/mongodb/README.md
##
mongodb:
enabled: true
image:
tag: 3.6.3
pullPolicy: IfNotPresent
persistence:
size: 50Gi
# resources:
# requests:
# memory: "12Gi"
# cpu: "200m"
# limits:
# memory: "12Gi"
# cpu: "2"
## Stellen Sie sicher, dass die --wiredTigerCacheSizeGB nicht mehr als die Hälfte der Speichergrenze beträgt!
## Dies ist entscheidend für den Schutz vor OOMKill durch Kubernetes!
mongodbExtraFlags:
- "--wiredTigerCacheSizeGB=1"
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
# livenessProbe:
# initialDelaySeconds: 60
# periodSeconds: 10
# readinessProbe:
# initialDelaySeconds: 30
# periodSeconds: 30
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
initContainerImage: "alpine:3.6"
imagePullSecrets:
replicaCount: 1
image:
repository: jainishshah17/express-mongo-crud
# tag: 1.0.1
pullPolicy: IfNotPresent
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
resources: {}
# Wir empfehlen in der Regel, keine Standard-Ressourcen anzugeben und dies bewusst so zu belassen
# Auswahl für den Benutzer. Dies erhöht auch die Chancen, dass die Charts in Umgebungen mit wenig
# Ressourcen laufen, wie z. B. Minikube. Wenn Sie Ressourcen angeben möchten, lassen Sie die folgenden
# Zeilen unkommentiert, passen Sie sie nach Bedarf an und entfernen Sie die geschweiften Klammern nach "resources:".
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
Das Helm-Chart testen und installieren
Es ist sehr wichtig, dass wir unser Helm-Diagramm testen; dies tun wir mit dem helm lint-Befehl.
$ helm lint ./ ## Output ==> Linting ./ Lint OK 1 chart(s) linted, no failures
Verwenden Sie den helm install-Befehl, um unsere Anwendung mithilfe von helm chart auf Kubernetes bereitzustellen.
$ helm install --name test1 ./ ## Output NAME: test1 LAST DEPLOYED: Sat Sep 15 09:36:23 2018 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1beta1/Deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE test1-mongodb 1 1 1 0 0s ==> v1beta2/Deployment test1-express-crud 1 1 1 0 0s ==> v1/Secret NAME TYPE DATA AGE test1-mongodb Opaque 2 0s ==> v1/PersistentVolumeClaim NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test1-mongodb Pending standard 0s ==> v1/ServiceAccount NAME SECRETS AGE test1-express-crud 1 0s ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE test1-mongodb ClusterIP 10.19.248.205 27017/TCP 0s test1-express-crud LoadBalancer 10.19.254.169 80:31994/TCP 0s ==> v1/Role NAME AGE test1-express-crud 0s ==> v1/RoleBinding NAME AGE test1-express-crud 0s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE test1-mongodb-67b6697449-tppk5 0/1 Pending 0 0s test1-express-crud-dfdbd55dc-rdk2c 0/1 Init:0/1 0 0s HINWEISE: 1. Rufen Sie die Anwendungs-URL ab, indem Sie diese Befehle ausführen: HINWEIS: Es kann ein paar Minuten dauern, bis die LoadBalancer-IP verfügbar ist. Sie können den Status von durch Ausführen von "kubectl get svc -w test1-express-crud" verfolgen export SERVICE_IP=$(kubectl get svc --namespace default test1-express-crud -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo https://$SERVICE_IP:80
Durch Ausführen des oben genannten helm install-Befehls wird eine External_IP für den Load Balancer erzeugt. Sie können diese IP-Adresse verwenden, um die Anwendung auszuführen.
So sieht unsere Anwendung aus, wenn sie ausgeführt wird:
Zusammenfassung
Wie Sie an diesem Beispiel sehen können, ist Helm ein extrem vielseitiges System, das Ihnen eine große Flexibilität bei der Strukturierung und Entwicklung eines Charts bietet. Werden dabei die Konventionen der Helm-Community befolgt, vereinfacht dies die Einreichung Ihrer Helm-Charts für die öffentliche Nutzung und die Pflege Ihrer Anwendung durch Aktualisierungen wird erheblich erleichtert.
Die fertiggestellten Helm-Charts für dieses Beispielprojekt finden Sie im express-crud-Repository auf GitHub. Außerdem können Sie diese funktionierenden Dateien überprüfen, um die Funktionsweise besser zu verstehen.
Um weitere Beispiele kennenzulernen, können Sie sich mein Beispiel-Repository von Helm-Charts für Bereitstellung von Produkten in Kubernetes ansehen.