Documentation
¶
Overview ¶
Package stsresize converges a StatefulSet whose declared volume size grew.
The problem ¶
A StatefulSet's volumeClaimTemplates are immutable. Raising the size an env declares for a StatefulSet's volume therefore makes every apply of the new render fail:
The StatefulSet "clickhouse" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden
(newer API servers say `spec.volumeClaimTemplates: Invalid value: ...: field is immutable`). The template is not even what decides the size of a running volume: it is read once, when the StatefulSet controller creates a claim, and never again. Growing a live volume means growing its PersistentVolumeClaim, which IS mutable, grow-only, when the claim's StorageClass allows expansion.
Without a converging step a declared size can never change after the first deploy: either the template is left at the old value and live and declared disagree for ever, or the apply wedges. Under a reconciler the wedge is total — Flux dry-runs every object of a Kustomization before applying any of them, so one rejected StatefulSet stops the whole unit from converging.
What this package does ¶
The production procedure, made a function:
- Decide whether the ONLY difference the API server refuses is a growth of a volumeClaimTemplate's storage request. Anything else — a shrink, a changed storage class, access mode, selector or serviceName, an added or removed template — is refused with a reason, and nothing is written.
- Grow every existing claim of each grown template that is smaller than the declared size, after checking its StorageClass has allowVolumeExpansion. Claims already at or above the size are left alone (an operator may have expanded one ahead of the declaration; that is the declaration catching up, not drift). Claims of ordinals the StatefulSet has scaled away from are grown too, so a later scale-up does not bring back a small volume.
- Delete the StatefulSet object with propagationPolicy=Orphan and wait for it to be gone. Its pods, claims and ControllerRevisions keep running and lose only their owner reference.
The caller then re-applies the declared StatefulSet. The new object adopts the orphaned pods (they match its selector) and its revisions; when the pod template is unchanged the pods are not restarted, and when it changed the normal rolling update follows.
Every step is idempotent, so a converge interrupted at any point is finished by running it again: claims already grown are skipped, and a StatefulSet already orphan-deleted is simply created by the re-apply.
Who calls it ¶
Whatever applies a render: forge's direct apply runs it when an apply fails on a StatefulSet's immutable spec (internal/cluster), and a reconciler's converger runs Converge for each StatefulSet of a revision before handing the revision to Flux. The decision is pure (Decide); the cluster access is the Cluster interface, so each applier supplies its own client.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrShrink is a declared volume size below the live template's. A // PersistentVolumeClaim cannot shrink, so the only honest outcome is to // refuse: lowering the template alone would describe volumes that do not // exist. ErrShrink = errors.New("stsresize: a volume cannot shrink") // ErrNotExpandable is a claim that must grow but whose StorageClass does // not set allowVolumeExpansion (or which has no class at all). ErrNotExpandable = errors.New("stsresize: the claim's storage class does not allow volume expansion") // ErrTemplateChanged is a volumeClaimTemplate change other than a larger // storage request: a template added, removed or renamed, or a changed // storage class, access mode, selector, volume mode, label or annotation. // Those describe different volumes, not bigger ones, and recreating the // StatefulSet would leave every existing claim contradicting the template. ErrTemplateChanged = errors.New("stsresize: a volumeClaimTemplate changed in a way other than a larger storage request") // ErrOtherImmutable is a change to another immutable StatefulSet field // (selector, serviceName, podManagementPolicy) alongside the volume // growth, as judged by the API server's own dry run. Orphan-recreating the // object then would create a StatefulSet that does not adopt the running // pods, so it is refused. ErrOtherImmutable = errors.New("stsresize: the StatefulSet changes an immutable field other than its volume sizes") )
Refusals. Each wraps a detail naming the template or claim; match with errors.Is. Nothing has been written to the cluster when one is returned.
Functions ¶
This section is empty.
Types ¶
type ClaimResize ¶
type ClaimResize struct {
Name string
Template string
StorageClass string
From, To resource.Quantity
}
ClaimResize is one PersistentVolumeClaim to grow to To.
type Cluster ¶
type Cluster interface {
// StatefulSet returns the live object, or nil and no error when absent.
StatefulSet(ctx context.Context, namespace, name string) (*appsv1.StatefulSet, error)
// PersistentVolumeClaims lists the claims in namespace.
PersistentVolumeClaims(ctx context.Context, namespace string) ([]corev1.PersistentVolumeClaim, error)
// StorageClasses lists the cluster's StorageClasses.
StorageClasses(ctx context.Context) ([]storagev1.StorageClass, error)
// DryRunApply server-side applies sts with dryRun=All, under the same
// field manager the real apply uses, and returns the server's verdict.
DryRunApply(ctx context.Context, sts *appsv1.StatefulSet) error
// ResizeClaim sets a claim's spec.resources.requests.storage.
ResizeClaim(ctx context.Context, namespace, name string, size resource.Quantity) error
// OrphanStatefulSet deletes the StatefulSet with propagationPolicy=Orphan
// and returns once it is gone. Absent is success.
OrphanStatefulSet(ctx context.Context, namespace, name string) error
}
Cluster is the cluster access Converge needs. Declared here, by the one consumer, so each applier supplies its own client (forge's direct apply shells out to kubectl; a reconciler's converger holds a controller-runtime client).
type Plan ¶
type Plan struct {
Namespace string
Name string
// Grown lists each volumeClaimTemplate whose storage request grows.
Grown []TemplateGrowth
// Resize lists each existing claim that must be grown, in name order.
Resize []ClaimResize
// Recreate reports that the StatefulSet object must be orphan-deleted and
// re-applied, because its templates differ from the declared ones.
Recreate bool
}
Plan is what converging one StatefulSet takes. The zero Plan (Recreate false) means there is nothing to converge: the live templates already declare the declared sizes.
func Converge ¶
Converge makes the live cluster ready to accept desired: it plans with Decide, proves with a server-side dry run that the volume templates are the only immutable difference, grows the claims, and orphan-deletes the StatefulSet. The caller applies desired afterwards.
desired must carry its namespace. The returned Plan says what was done; when its Recreate is false nothing was written. On a refusal nothing was written either. An error after the claims were grown leaves them grown, which is correct on its own, and a re-run finishes the rest.
func Decide ¶
func Decide(desired, live *appsv1.StatefulSet, claims []corev1.PersistentVolumeClaim, classes []storagev1.StorageClass) (Plan, error)
Decide computes the plan for converging live to desired, given the live claims in the StatefulSet's namespace and the cluster's StorageClasses. It is pure.
It judges the volumeClaimTemplates only. Whether another immutable field also changed is the API server's call, which Converge asks it with a dry run; a caller using Decide alone must make that check itself before acting on a Recreate plan.
A nil live StatefulSet yields the zero Plan: creating one has no immutable field to collide with.
type TemplateGrowth ¶
TemplateGrowth is one volumeClaimTemplate's storage request, live and declared.