Kubernetes ist eigentlich dazu da, eine Infrastruktur in Code zu gießen, sie einfach skalieren zu können und dass sich das System im Problemfall “selbst heilt”. Das funtkioniert in der Regel auch ganz gut, aber besonders die “Selbstheilung” hat so ihre Grenzen. In meinem Fall handelt es sich um gemountete Volumes / PCVs, die nur einmal verwendet werden dürfen. Sprich, immer nur genau ein Pod darf dieses Volume mounten.
Das ist z.b. bei Ceph oder NFS mounts der Fall.
In meinem lokalen Cluster aus mehreren Intel NUCs habe ich nun das Problem, dass wenn eine Node ausfällt/vom Strom getrennt wird, dass die Node zwar sehr schnell als fehlend markiert wird und nach 5 Minuten dann auch die Pods das gleiche Schicksal ereilt. Wenn Kubernetes aber nun versucht, die Pods auf einer anderen Node neu zu starten, dann scheitert es daran, dass Kubernetes an dieser Stelle nicht genau weiß, ob die Pods wirklich weg sind oder nur die Infrastruktur selbst nicht richtig kommuniziert. Daher geht es davon aus, dass die Mounts noch beim alten Pod aktiv sind, bis es die endgültige Meldung bekommt, dass der alte Pod wirklich weg ist. Da die Node aber zu diesemn Zeitpunkt keinen Strom mehr hat, kann sie auch nie melden, dass der Pod nicht mehr läuft. Und so wartet Kubernetes einfach bzw. verharrt in diesem unklaren Zustand.
Man kann nun anfangen, die Pods manuell mit einem Force zu löschen, dann werden sie nach einiger Zeit ihre Resourcen wieder frei geben und der neue Pod kann starten. Um dies auch zu automatisieren, kann man sich aber auch mit einem Cron Job innerhalb des Clusters behelfen. Und das sieht dann so aus:
Service Account und RBAC Rolle anlegen:
apiVersion: v1 kind: ServiceAccount metadata: name: cleanup-bot namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cleanup-bot-role rules: - apiGroups: [""] resources: ["pods", "nodes"] verbs: ["get", "list", "delete"] - apiGroups: ["storage.k8s.io"] resources: ["volumeattachments"] verbs: ["list", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cleanup-bot-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cleanup-bot-role subjects: - kind: ServiceAccount name: cleanup-bot namespace: default
Wenn das eingerichtet ist, kann man den Cronjob so anlegen:
apiVersion: batch/v1 kind: CronJob metadata: name: pod-cleanup-dead-nodes namespace: default spec: schedule: "*/10 * * * *" # alle 10 Minuten jobTemplate: spec: template: spec: serviceAccountName: cleanup-bot containers: - name: cleanup image: bitnami/kubectl:latest command: - /bin/sh - -c - | for node in $(kubectl get nodes --no-headers | awk '$2 == "NotReady" {print $1}'); do echo "Cleaning up pods on $node" kubectl get pods --all-namespaces --field-selector spec.nodeName=$node -o json \ | jq -r '.items[] | select(.metadata.deletionTimestamp != null) | "kubectl delete pod -n \(.metadata.namespace) \(.metadata.name) --grace-period=0 --force"' \ | sh done restartPolicy: OnFailure
Dieser Cronjob schaut alle 10 Minuten nach, ob es Nodes gibt, die im Status “NotReady” sind. Für alle Nodes in diesem Status werden die Pods auf dieser Node ausgelesen und einzeln per kubectl und keiner Grace-Period gelöscht.
In meinem Fall hat dies genau die Lösung gebracht, die ich gesucht habe. Erst ist die Node nicht mehr erreichbar, dann failen die Pods und Kubernetes versucht sie neu auf anderen Nodes zu starten, scheitert aber daran wegen der noch offenen Volume Mounts. Nach max. 10 Minuten kommt der Cronjob und beendet alle Pods hart und löst damit den Volume mount auf, worauf sich nach kurzer Zeit die “neuen” Pods aus ihrem Pending Status befreien und korrekt hochfahren.