PVC Reconciliation in CloudNativePG#
Overview#
PVC reconciliation in CloudNativePG is handled by the pkg/reconciler/persistentvolumeclaim package. Its job is to keep each cluster's PersistentVolumeClaim objects aligned with the desired state declared in the Cluster spec—both when PVCs are created for new instances and when existing PVCs need to be updated.
The public entry point is Reconcile(), which orchestrates two phases:
- Missing PVC creation —
reconcileMultipleInstancesMissingPVCs()provisions PVCs for instances that lack them. - Existing PVC update —
reconcileExistingPVCs()brings existing PVCs in sync with the desired spec. Conflict errors from the API server cause a requeue rather than a hard failure .
Reconcile() is called from internal/controller/cluster_controller.go.
reconcileExistingPVCs#
reconcileExistingPVCs() assembles a list of reconciliationUnit functions and runs them against every PVC in the cluster:
reconcilePVCQuantity— added whencluster.ShouldResizeInUseVolumes()returnstrue.reconcileVolumeAttributeClass— added whenStorageConfiguration.PersistentVolumeClaimTemplate != nil.
For each PVC the function reads the cnpg.io/pvcRole label (via GetExpectedObjectCalculator()) to determine which StorageConfiguration (data, WAL, or tablespace) applies, then invokes each unit in order .
reconcilePVCQuantity#
reconcilePVCQuantity() expands PVC storage to match the desired size:
- Reads the desired size via
storageConfiguration.GetSizeOrNil(); returnsErrorInvalidSizeif unset . - Compares the current request with the desired value using
AsDec().Cmp(). If current > desired, it logs a warning and skips (Kubernetes does not support shrinking PVCs) . - On increase, patches the PVC spec via
client.MergeFrom.
ShouldResizeInUseVolumes() defaults to true when ResizeInUseVolumes is nil, so in-place expansion is enabled unless explicitly disabled .
reconcileVolumeAttributeClass#
reconcileVolumeAttributeClass() synchronizes Spec.VolumeAttributesClassName on the PVC to match the value declared in the PersistentVolumeClaimTemplate.
Pointer equality gotcha. VolumeAttributesClassName is typed *string in both the Kubernetes API and in StorageConfiguration. The comparison at line 111 uses == directly on the two *string values:
if expectedVolumeAttributesClassName == pvc.Spec.VolumeAttributesClassName {
return nil
}
Go's == on pointers is pointer identity, not value equality. The comparison is safe for the nil case (both nil → equal → no-op) but can produce false-negatives when both sides point to strings with the same content but different allocations. In practice this means an unnecessary Patch would be issued rather than a missed update — a correctness issue only if the call is expensive or triggers unintended side effects. Callers constructing expectedVolumeAttributesClassName from fresh string literals (e.g., in tests or future refactors) should use ptr.Equal or dereference-and-compare to get value equality .
The tests in reconciler_test.go exercise this through the explicit cases of matching value, differing value, and nil-to-value/value-to-nil transitions .
PVC Role and the Calculator Pattern#
Each PVC carries the label cnpg.io/pvcRole (defined in pkg/utils/labels_annotations.go) with one of three values: PG_DATA, PG_WAL, or PG_TABLESPACE .
GetExpectedObjectCalculator() reads this label and returns the matching ExpectedObjectCalculator implementation (NewPgDataCalculator, NewPgWalCalculator, or NewPgTablespaceCalculator). Each calculator knows how to retrieve its StorageConfiguration from the Cluster spec, allowing reconcileExistingPVCs to apply the right desired state to each PVC without nested conditionals.
Metadata Reconciliation#
Separately from spec reconciliation, ReconcileMetadata() keeps PVC labels and annotations in sync (inherited labels/annotations, instance-role labels, operator version annotation). ReconcileSerialAnnotation() ensures the cnpg.io/instanceSerial annotation stays correct relative to the pod that mounts the PVC.
Key Files#
| File | Purpose |
|---|---|
reconciler.go | Public Reconcile() entry point |
existing.go | reconcileExistingPVCs, reconcilePVCQuantity, reconcileVolumeAttributeClass |
calculator.go | ExpectedObjectCalculator interface + role-based factory |
metadata.go | Label/annotation reconciliation |
instance.go | Missing-PVC creation per instance |
reconciler_test.go | Unit tests covering all reconciliation units |