feat(rustfs): let the StatefulSet own its drives, ahead of a resize #437

Merged
binjovi-bot merged 1 commit from feat/rustfs-capacity into trunk 2026-09-07 22:57:03 +00:00
Owner

RustFS is provisioned at 4 x 55Gi -- about 110 GiB usable at 2+2 -- and the MinIO data it is to replace is 144 GiB, of which zot alone is 136 GiB. It has to grow before anything moves.

volumeClaimTemplates are immutable, so a resize is a delete-and-recreate of the StatefulSet. That has a trap: the old claims survive the delete, the recreated pods bind them at the old size, and the resize silently does nothing. Nothing fails; the store just stays small.

This commit makes the recreate mean what it says, without yet performing it.

change why
persistentVolumeClaimRetentionPolicy.whenDeleted: Delete the controller puts an ownerReference on each claim, so deleting the StatefulSet garbage-collects the drives and the recreate provisions fresh ones
whenScaled: Retain a scale-down must never destroy a drive of an erasure set
force: true on the Kustomization Flux recreates on an immutable-field conflict instead of failing

force: true is scoped: this tree contains nothing but RustFS, so the blast radius of a forced recreate is exactly the object being resized. The same flag is deliberately absent from larger trees.

Why this is a separate release from the resize

The retention policy is a mutable field and it has to be live before the resize lands. If both arrive together, the delete the resize triggers is evaluated against the policy in effect when the StatefulSet was created -- Retain -- and the old drives stay behind. So: this release first, then the resize.

Guard

tests/rustfs.sh gains a "a resize must actually resize" block pinning all three. Negative controls, each applied alone:

retention policy weakened to Retain            detected
scale-down allowed to destroy a drive          detected
force removed from the Kustomization           detected

The force assertion is anchored to the YAML line (^ force: true$). The first version matched the comment above it, which names the flag, and read NOT DETECTED.

bash tests/check.sh exits 0. Server-side dry run applies clean.

https://claude.ai/code/session_01KZoQin34jeyt6nDGqvJA76

RustFS is provisioned at 4 x 55Gi -- about 110 GiB usable at 2+2 -- and the MinIO data it is to replace is 144 GiB, of which zot alone is 136 GiB. It has to grow before anything moves. `volumeClaimTemplates` are immutable, so a resize is a delete-and-recreate of the StatefulSet. That has a trap: the old claims survive the delete, the recreated pods bind them at the **old** size, and the resize silently does nothing. Nothing fails; the store just stays small. This commit makes the recreate mean what it says, **without yet performing it**. | change | why | |---|---| | `persistentVolumeClaimRetentionPolicy.whenDeleted: Delete` | the controller puts an ownerReference on each claim, so deleting the StatefulSet garbage-collects the drives and the recreate provisions fresh ones | | `whenScaled: Retain` | a scale-down must never destroy a drive of an erasure set | | `force: true` on the Kustomization | Flux recreates on an immutable-field conflict instead of failing | `force: true` is scoped: this tree contains nothing but RustFS, so the blast radius of a forced recreate is exactly the object being resized. The same flag is deliberately absent from larger trees. ## Why this is a separate release from the resize The retention policy is a mutable field and it has to be **live** before the resize lands. If both arrive together, the delete the resize triggers is evaluated against the policy in effect when the StatefulSet was created -- Retain -- and the old drives stay behind. So: this release first, then the resize. ## Guard `tests/rustfs.sh` gains a "a resize must actually resize" block pinning all three. Negative controls, each applied alone: ``` retention policy weakened to Retain detected scale-down allowed to destroy a drive detected force removed from the Kustomization detected ``` The `force` assertion is anchored to the YAML line (`^ force: true$`). The first version matched the comment above it, which names the flag, and read NOT DETECTED. `bash tests/check.sh` exits 0. Server-side dry run applies clean. https://claude.ai/code/session_01KZoQin34jeyt6nDGqvJA76
feat(rustfs): let the StatefulSet own its drives, ahead of a resize
All checks were successful
binjovi/ci Binjovi completed the frozen plan
aee5dd164a
RustFS is provisioned at 4 x 55Gi -- about 110 GiB usable at 2+2 -- and the
MinIO data it is to replace is 144 GiB, of which zot alone is 136 GiB. It has to
grow before anything moves. volumeClaimTemplates are immutable, so a resize is a
delete-and-recreate of the StatefulSet, and that has a trap: the old claims
survive the delete, the recreated pods bind them at the OLD size, and the resize
silently does nothing.

This commit makes the recreate mean what it says, without yet performing it:

  * persistentVolumeClaimRetentionPolicy.whenDeleted: Delete -- the controller
    puts an ownerReference on each claim, so deleting the StatefulSet
    garbage-collects the drives and the recreate provisions fresh ones.
    whenScaled stays Retain: a scale-down must never destroy a drive of an
    erasure set.
  * force: true on the rustfs Kustomization -- Flux recreates on an
    immutable-field conflict instead of failing. This tree contains nothing but
    RustFS, so the blast radius is exactly the object being resized; the same
    flag is deliberately absent from larger trees.

The retention policy is a mutable field and it must be LIVE before the resize
lands, or the delete it triggers still leaves the old drives behind. That is why
the resize is the next release and not this one.

tests/rustfs.sh pins both, and fails if either is removed alone -- they only
work together. The force assertion is anchored to the YAML line because the
comment explaining the flag names it too.

Claude-Session: https://claude.ai/code/session_01KZoQin34jeyt6nDGqvJA76
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/seanfarm!437
No description provided.