feat(paperless-ngx): consume-CronJob an den StatefulSet-Pod binden - RWX-Volume wird sonst von einem fremden Knoten eingehaengt #101

Closed
opened 2026-10-08 12:13:02 +02:00 by jlab-bot · 0 comments
Member

Der consume-CronJob hängt das consume-Volume ein, hat aber keine Platzierungsregel. Er landet damit auf einem anderen Knoten als der StatefulSet-Pod, und das erzeugt in cluster-config einen Dauerfehler am Mount.

Symptom, im Cluster gemessen (08.10.2026)

Beide Instanzen mit eingeschalteter SMB-Verbindung melden alle rund zwei Minuten:

MountVolume.SetUp failed for volume "pvc-8b7b112e-…" : rpc error: code = Aborted
desc = no Pending workload pods for volume pvc-8b7b112e-… to be mounted:
map[Running:[paperless-jk-paperless-ngx-sfs-0]
    Succeeded:[paperless-jk-paperless-ngx-consume-cronjob-29855520-4bthr
               paperless-jk-paperless-ngx-consume-cronjob-29856240-cbqgr
               paperless-jk-paperless-ngx-consume-cronjob-29856960-v4fkp]]
betroffen paperless-jk und paperless-ngx (tb) — je 291 Meldungen in 10 Stunden
gemeldet von dem kubelet des Knotens, auf dem der StatefulSet-Pod läuft (jk-worker-1)
begonnen 08.10.2026 00:00:33 — mit dem Mitternachtslauf des CronJobs

Die Kette

smbConnection.enabled: true
  -> templates/paperless-consume.cronjob.yaml wird gerendert, schedule "0 0,12 * * *"
  -> der Pod fuehrt aus:  mv /mnt/smb/*.pdf /mnt/consume
     und haengt dafuer BEIDE Volumes ein, darunter das consume-Volume (RWX, Longhorn)
  -> keine affinity, kein nodeSelector  ->  Scheduler setzt ihn irgendwohin
     (gemessen: jk-worker-2 und jk-worker-3, waehrend der StatefulSet-Pod auf
      jk-worker-1 liegt)
  -> successfulJobsHistoryLimit (Kubernetes-Vorgabe 3) haelt drei erledigte Pods
  -> Longhorns CSI-Treiber verlangt vor dem Einhaengen eines RWX-Volumes einen
     Workload-Pod in PENDING. Vorhanden sind einer in Running und drei in
     Succeeded -> Aborted, und das bei jedem Versuch des kubelets.

Die Gegenprobe steht im Cluster: paperless-cmk hat smbConnection.enabled: false, also keinen CronJob, also nur den StatefulSet-Pod in Longhorns Workload-Liste — und null solche Ereignisse. Gleiche Chart-Fassung, gleiche Volume-Bauart.

Warum das nicht in cluster-config zu lösen ist

templates/paperless-consume.cronjob.yaml nimmt aus den Werten genau einen Schlüssel: smbConnection.cronJob (den Zeitplan). Keine affinity, kein nodeSelector, keine tolerations. Betroffene Fassung: 12.2.0+up3.2.1.

Vorschlag — und ein statischer nodeSelector wäre der falsche Griff

Der StatefulSet-Pod ist selbst nicht festgenagelt: er liegt da, wo der Scheduler ihn hingelegt hat, und wandert bei jedem Neustart. Ein fester nodeSelector am CronJob würde also genau so lange stimmen, bis der StatefulSet-Pod umzieht — und dann still wieder falsch sein.

Das passende Mittel ist eine podAffinity auf den StatefulSet-Pod mit topologyKey: kubernetes.io/hostname. Sie folgt dem Pod, wohin er auch geht. Das Label existiert bereits und ist am laufenden Pod abgelesen: app: <release>-<chart>-sfs.

      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
              topologyKey: kubernetes.io/hostname

Die Abwägung, die dazugehört, und sie ist nicht klein:

  • Mit required wird der CronJob-Pod unschedulebar, wenn der StatefulSet-Pod nicht läuft — etwa bei replicas: 0 oder während eines Neustarts. Er bleibt dann Pending stehen, und alle zwölf Stunden kommt einer dazu. Dagegen helfen startingDeadlineSeconds und concurrencyPolicy: Forbid; ob das in den Chart gehört oder in die Werte, ist Teil der Entscheidung.
  • Mit preferred fällt er im Zweifel auf einen anderen Knoten zurück — und ist damit genau der Fehler, den dieses Issue beschreibt, nur seltener und schwerer zu finden. Ich halte preferred hier für die schlechtere Wahl, weil der stille Rückfall das Fehlerbild erzeugt, das zehn Stunden lang niemandem auffiel.

Falls die Platzierung konfigurierbar werden soll statt fest verdrahtet, wäre die Form smbConnection.cron.affinity (oder ein null-sicherer Helfer wie bei den übrigen Charts) der Ort — mit der podAffinity als Vorgabe, nicht als leeres Feld.

Die Alternative, die das Problem an der Wurzel nimmt

Den mv nicht in einem eigenen Pod laufen lassen, sondern im StatefulSet-Pod — als Sidecar mit Schlafschleife oder über ein exec. Dann wird das consume-Volume nur von einem Knoten eingehängt und bräuchte nicht einmal RWX.

Das ist der sauberere Entwurf und der größere Eingriff: Er ändert, wofür das consume-Volume RWX ist, und RWX lässt sich an einer bestehenden PVC nicht zurücknehmen. Für die drei laufenden Installationen bliebe es also bei RWX, und der Gewinn wäre nur der entfallene Fernzugriff. Deshalb nenne ich es als Alternative und nicht als Vorschlag.

Was nicht hilft

successfulJobsHistoryLimit: 0. Longhorns Prüfung will einen Pod in Pending, nicht die Abwesenheit erledigter — mit nur einem Running-Pod bricht sie genauso ab.

Was der Betreiber heute tun muss

Den StatefulSet-Pod löschen. Der neue ist Pending, genau das, was die Prüfung sehen will, und die Schleife endet:

kubectl -n paperless-jk delete pod paperless-jk-paperless-ngx-sfs-0

Beim nächsten Lauf um 12:00 kommt es wieder. Das ist der Grund, warum der Fix in den Chart gehört und nicht in ein Runbook.

Der consume-CronJob hängt das consume-Volume ein, hat aber keine Platzierungsregel. Er landet damit auf einem anderen Knoten als der StatefulSet-Pod, und das erzeugt in `cluster-config` einen Dauerfehler am Mount. ## Symptom, im Cluster gemessen (08.10.2026) Beide Instanzen mit eingeschalteter SMB-Verbindung melden alle rund zwei Minuten: ``` MountVolume.SetUp failed for volume "pvc-8b7b112e-…" : rpc error: code = Aborted desc = no Pending workload pods for volume pvc-8b7b112e-… to be mounted: map[Running:[paperless-jk-paperless-ngx-sfs-0] Succeeded:[paperless-jk-paperless-ngx-consume-cronjob-29855520-4bthr paperless-jk-paperless-ngx-consume-cronjob-29856240-cbqgr paperless-jk-paperless-ngx-consume-cronjob-29856960-v4fkp]] ``` | | | |---|---| | betroffen | `paperless-jk` und `paperless-ngx` (tb) — **je 291 Meldungen in 10 Stunden** | | gemeldet von | dem kubelet des Knotens, auf dem der **StatefulSet**-Pod läuft (`jk-worker-1`) | | begonnen | 08.10.2026 **00:00:33** — mit dem Mitternachtslauf des CronJobs | ## Die Kette ``` smbConnection.enabled: true -> templates/paperless-consume.cronjob.yaml wird gerendert, schedule "0 0,12 * * *" -> der Pod fuehrt aus: mv /mnt/smb/*.pdf /mnt/consume und haengt dafuer BEIDE Volumes ein, darunter das consume-Volume (RWX, Longhorn) -> keine affinity, kein nodeSelector -> Scheduler setzt ihn irgendwohin (gemessen: jk-worker-2 und jk-worker-3, waehrend der StatefulSet-Pod auf jk-worker-1 liegt) -> successfulJobsHistoryLimit (Kubernetes-Vorgabe 3) haelt drei erledigte Pods -> Longhorns CSI-Treiber verlangt vor dem Einhaengen eines RWX-Volumes einen Workload-Pod in PENDING. Vorhanden sind einer in Running und drei in Succeeded -> Aborted, und das bei jedem Versuch des kubelets. ``` **Die Gegenprobe steht im Cluster:** `paperless-cmk` hat `smbConnection.enabled: false`, also keinen CronJob, also nur den StatefulSet-Pod in Longhorns Workload-Liste — und **null** solche Ereignisse. Gleiche Chart-Fassung, gleiche Volume-Bauart. ## Warum das nicht in `cluster-config` zu lösen ist `templates/paperless-consume.cronjob.yaml` nimmt aus den Werten genau einen Schlüssel: `smbConnection.cronJob` (den Zeitplan). Keine `affinity`, kein `nodeSelector`, keine `tolerations`. Betroffene Fassung: **12.2.0+up3.2.1**. ## Vorschlag — und ein statischer `nodeSelector` wäre der falsche Griff Der StatefulSet-Pod ist selbst **nicht** festgenagelt: er liegt da, wo der Scheduler ihn hingelegt hat, und wandert bei jedem Neustart. Ein fester `nodeSelector` am CronJob würde also genau so lange stimmen, bis der StatefulSet-Pod umzieht — und dann still wieder falsch sein. Das passende Mittel ist eine **podAffinity auf den StatefulSet-Pod** mit `topologyKey: kubernetes.io/hostname`. Sie folgt dem Pod, wohin er auch geht. Das Label existiert bereits und ist am laufenden Pod abgelesen: `app: <release>-<chart>-sfs`. ```yaml affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs topologyKey: kubernetes.io/hostname ``` **Die Abwägung, die dazugehört, und sie ist nicht klein:** - Mit `required` wird der CronJob-Pod **unschedulebar**, wenn der StatefulSet-Pod nicht läuft — etwa bei `replicas: 0` oder während eines Neustarts. Er bleibt dann `Pending` stehen, und alle zwölf Stunden kommt einer dazu. Dagegen helfen `startingDeadlineSeconds` und `concurrencyPolicy: Forbid`; ob das in den Chart gehört oder in die Werte, ist Teil der Entscheidung. - Mit `preferred` fällt er im Zweifel auf einen anderen Knoten zurück — und ist damit genau der Fehler, den dieses Issue beschreibt, nur seltener und schwerer zu finden. **Ich halte `preferred` hier für die schlechtere Wahl**, weil der stille Rückfall das Fehlerbild erzeugt, das zehn Stunden lang niemandem auffiel. Falls die Platzierung konfigurierbar werden soll statt fest verdrahtet, wäre die Form `smbConnection.cron.affinity` (oder ein null-sicherer Helfer wie bei den übrigen Charts) der Ort — mit der podAffinity als Vorgabe, nicht als leeres Feld. ## Die Alternative, die das Problem an der Wurzel nimmt Den `mv` nicht in einem eigenen Pod laufen lassen, sondern **im StatefulSet-Pod** — als Sidecar mit Schlafschleife oder über ein `exec`. Dann wird das consume-Volume nur von einem Knoten eingehängt und bräuchte nicht einmal RWX. Das ist der sauberere Entwurf und der größere Eingriff: Er ändert, wofür das consume-Volume RWX ist, und RWX lässt sich an einer bestehenden PVC nicht zurücknehmen. Für die drei laufenden Installationen bliebe es also bei RWX, und der Gewinn wäre nur der entfallene Fernzugriff. Deshalb nenne ich es als Alternative und nicht als Vorschlag. ## Was nicht hilft `successfulJobsHistoryLimit: 0`. Longhorns Prüfung will einen Pod in **Pending**, nicht die Abwesenheit erledigter — mit nur einem Running-Pod bricht sie genauso ab. ## Was der Betreiber heute tun muss Den StatefulSet-Pod löschen. Der neue ist `Pending`, genau das, was die Prüfung sehen will, und die Schleife endet: ```bash kubectl -n paperless-jk delete pod paperless-jk-paperless-ngx-sfs-0 ``` Beim nächsten Lauf um 12:00 kommt es wieder. Das ist der Grund, warum der Fix in den Chart gehört und nicht in ein Runbook.
jlab-bot added the inhouse-chart label 2026-10-08 12:13:02 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jlab-cluster/helm-charts#101