Zustandsbehaftete Anwendungen mit persistentem Speicher ausführen

KubernetesBeginner
Jetzt üben

Einführung

Deployments behandeln Replikate als austauschbar. Zustandsbehaftete Anwendungen benötigen häufig stabile Pod-Namen, eine vorhersehbare Startreihenfolge und Speicher, der den Austausch eines Pods übersteht. StatefulSets verbinden diese Identitäten mit eigenen Claims für jeden Pod.

Sie untersuchen die dynamische Speicherbereitstellung, stellen ein StatefulSet mit zwei Replikaten bereit, schreiben Daten, ersetzen einen Pod und weisen nach, dass Identität und Daten erhalten bleiben.

Betrachten Sie drei Identitäten getrennt: Das StatefulSet steuert die gewünschte Anzahl an Replikaten, jeder Pod erhält eine stabile Ordnungsnummer wie -0, und jede Ordnungsnummer erhält ihren eigenen PersistentVolumeClaim. Ein Pod kann ersetzt werden, während der Claim und die gespeicherten Daten erhalten bleiben.

Dynamische Speicherbereitstellung untersuchen

In diesem Schritt untersuchen Sie zunächst die StorageClass des Clusters, bevor Sie Speicher anfordern.

Ein PersistentVolumeClaim beschreibt, was eine Anwendung benötigt; eine StorageClass legt fest, wie der Cluster diesen Speicher bereitstellen kann. Wenn Sie zuerst die verfügbaren Klassen prüfen, vermeiden Sie Claims, die niemals gebunden werden können.

cd /home/labex/project/stateful-storage
kubectl get storageclasses
kubectl get storageclass -o custom-columns='NAME:.metadata.name,PROVISIONER:.provisioner,DEFAULT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class'

Die standardmäßig verwendete StorageClass erstellt dynamisch ein PersistentVolume, wenn ein Claim storageClassName nicht angibt. Dadurch ist in den Lernmanifesten keine knotenspezifische hostPath-Konfiguration erforderlich.

Achten Sie in der normalen Tabelle auf (default) oder in der benutzerdefinierten Spalte DEFAULT auf true. Der Name des Provisioners ist für diese Umgebung ein Implementierungsdetail. Entscheidend ist, dass eine Standardklasse vorhanden ist.

In dieser Umgebung lautet das Ergebnis:

NAME                 PROVISIONER                ...
standard (default)   k8s.io/minikube-hostpath   ...

NAME       PROVISIONER                DEFAULT
standard   k8s.io/minikube-hostpath   true

standard ist somit die Standardklasse. Da das StatefulSet storageClassName nicht angibt, wählt Kubernetes diese Klasse automatisch aus.

StatefulSet und Headless-Service erstellen

In diesem Schritt erstellen Sie einen Headless-Service für stabile DNS-Namen sowie ein StatefulSet mit einem Claim pro Replikat.

Dieses Manifest führt mehrere miteinander verbundene Felder ein. Lesen Sie sie vor dem Anwenden aufmerksam durch:

  • clusterIP: None macht den Service zu einem Headless-Service, sodass DNS einzelne Pods abbilden kann.
  • serviceName: stateful-web verknüpft das StatefulSet mit diesem Service.
  • volumeClaimTemplates erstellt für jedes Replikat einen Claim, dessen Name sich aus dem Vorlagennamen und der Ordnungsnummer des Pods zusammensetzt.
  • volumeMounts bindet den eigenen Claim jedes Pods in das Dokumentverzeichnis von NGINX ein.

Die Labels übernehmen weiterhin die bekannte Zuordnung von Services zu Pods, die Sie im ersten Kurs kennengelernt haben.

cd /home/labex/project/stateful-storage
cat <<'EOF' > stateful-web.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-stateful}
---
apiVersion: v1
kind: Service
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
  clusterIP: None
  selector: {app: stateful-web}
  ports: [{name: http, port: 80, targetPort: http}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
  serviceName: stateful-web
  replicas: 2
  selector: {matchLabels: {app: stateful-web}}
  template:
    metadata: {labels: {app: stateful-web}}
    spec:
      containers:
        - name: web
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports: [{name: http, containerPort: 80}]
          volumeMounts: [{name: web-data, mountPath: /usr/share/nginx/html}]
  volumeClaimTemplates:
    - metadata: {name: web-data}
      spec:
        accessModes: [ReadWriteOnce]
        resources: {requests: {storage: 100Mi}}
EOF

Wenden Sie das Manifest an und warten Sie auf das geordnete Rollout. StatefulSets erstellen normalerweise zuerst die Ordnungsnummer 0 und anschließend die Ordnungsnummer 1, daher kann der zweite Pod etwas später erscheinen:

kubectl apply -f stateful-web.yaml
kubectl rollout status statefulset/stateful-web -n practice-stateful --timeout=90s
kubectl get pods,pvc -n practice-stateful

Die Pod-Namen verwenden stabile Ordnungsnummern: stateful-web-0 und stateful-web-1. Jeder Claim enthält den Vorlagennamen, den Pod-Namen und die Ordnungsnummer.

Sie sollten die Claims web-data-stateful-web-0 und web-data-stateful-web-1 sehen, beide mit dem Status Bound. Bound bedeutet, dass Kubernetes Speicher gefunden oder dynamisch bereitgestellt hat, der die jeweilige Anforderung erfüllt.

Die Rollout-Meldungen machen das geordnete Verhalten sichtbar. Anschließend werden die beiden Claims angezeigt:

Waiting for 2 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...

pod/stateful-web-0   1/1   Running   ...
pod/stateful-web-1   1/1   Running   ...

persistentvolumeclaim/web-data-stateful-web-0   Bound   ...   100Mi   RWO   standard
persistentvolumeclaim/web-data-stateful-web-1   Bound   ...   100Mi   RWO   standard

Die generierten Volume-IDs unterscheiden sich bei jedem Durchlauf. Achten Sie auf die stabilen Claim-Namen, den Status Bound, die angeforderte Kapazität und die StorageClass.

Pod-spezifische persistente Daten schreiben

In diesem Schritt schreiben Sie unterschiedliche Inhalte in den gemounteten Claim jedes Pods.

kubectl exec ... -- sh -c wird verwendet, weil die Ausgabeumleitung (>) innerhalb des Containers ausgeführt werden muss. Ohne sh -c würde Ihre lokale Shell versuchen, die Ausgabe auf der virtuellen Maschine statt innerhalb des Pods umzuleiten.

cd /home/labex/project/stateful-storage
kubectl exec stateful-web-0 -n practice-stateful -- sh -c 'echo persistent-zero > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-1 -n practice-stateful -- sh -c 'echo persistent-one > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl exec stateful-web-1 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pods -n practice-stateful -o custom-columns='POD:.metadata.name,HOSTNAME:.spec.hostname,CLAIM:.spec.volumes[0].persistentVolumeClaim.claimName'

Die Pods verwenden zwar dasselbe Image, binden jedoch unterschiedliche Claims ein. Daher sind ihre Daten nicht austauschbar.

Die beiden cat-Befehle sollten unterschiedliche Zeichenfolgen zurückgeben. Die benutzerdefinierte Tabelle ordnet anschließend jeden stabilen Pod-Namen seinem Claim zu und macht die Speicherbeziehung sichtbar.

persistent-zero
persistent-one
POD              HOSTNAME         CLAIM
stateful-web-0   stateful-web-0   web-data-stateful-web-0
stateful-web-1   stateful-web-1   web-data-stateful-web-1

Die Ordnungsnummer erscheint in allen drei Spalten. Diese wiederholte 0 oder 1 ist die Identität, die einen Pod mit seinem eigenen Speicher verknüpft.

Einen Pod ersetzen und seine Daten wiederherstellen

In diesem Schritt löschen Sie den Pod mit der Ordnungsnummer 0 und lassen das StatefulSet dieselbe Identität erneut erstellen.

Das Löschen eines verwalteten Pods ist nicht dasselbe wie das Verringern der gewünschten Replikatanzahl. Das StatefulSet benötigt weiterhin zwei Pods und erstellt daher einen Ersatz mit dem Namen stateful-web-0. Dabei bindet es den für diese Ordnungsnummer reservierten Claim erneut ein.

cd /home/labex/project/stateful-storage
kubectl delete pod stateful-web-0 -n practice-stateful --wait=true
kubectl wait --for=condition=Ready pod/stateful-web-0 -n practice-stateful --timeout=90s
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pod stateful-web-0 -n practice-stateful -o jsonpath='pod={.metadata.name} claim={.spec.volumes[0].persistentVolumeClaim.claimName}{"\n"}'

Das Pod-Objekt ist neu, aber sein Name und die Zuordnung zu seinem Claim bleiben stabil. Die wiederhergestellte Datei beweist, dass die Daten zum PersistentVolume gehörten und nicht zum Dateisystem des gelöschten Containers.

pod "stateful-web-0" deleted from practice-stateful namespace
pod/stateful-web-0 condition met
persistent-zero
pod=stateful-web-0 claim=web-data-stateful-web-0

Die entscheidende Zeile ist persistent-zero: Sie wurde vor dem Löschen geschrieben und aus dem Ersatz-Pod gelesen.

Wäre die Datei außerhalb des gemounteten Verzeichnisses geschrieben worden, wäre sie zusammen mit dem Dateisystem des alten Containers verschwunden. Die Persistenz hängt vom Mount-Pfad ab, nicht allein vom Namen des StatefulSets.

Zusammenfassung

Sie haben die standardmäßige StorageClass, die dynamische PVC-Bereitstellung, einen Headless-Service, stabile Ordnungsnummern eines StatefulSets und podbezogenen Speicher miteinander verbunden. Beim Ersetzen eines Pods blieben sowohl seine Identität als auch seine Daten erhalten.