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:

Result

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.