feat(controller): customize PDB value#456
Conversation
Signed-off-by: Eduardo Mota <eduardo.mota@farfetch.com>
There was a problem hiding this comment.
Pull request overview
This PR adds customization support for PodDisruptionBudget (PDB) configuration in Dragonfly deployments with replicas > 1. Previously, the PDB was hardcoded with maxUnavailable: 1. Now users can specify custom minAvailable or maxUnavailable values (as integers or percentages) through the Dragonfly CR spec.
Changes:
- Added
PdbSpecstruct to the API withMinAvailableandMaxUnavailablestring fields - Updated PDB generation logic to use custom values when specified, falling back to default behavior
- Regenerated CRD manifests and deepcopy functions to reflect the new API
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| api/v1alpha1/dragonfly_types.go | Adds PdbSpec struct with MinAvailable and MaxUnavailable fields to DragonflySpec |
| api/v1alpha1/zz_generated.deepcopy.go | Auto-generated deepcopy methods for the new PdbSpec type |
| config/crd/bases/dragonflydb.io_dragonflies.yaml | CRD manifest updated with pdb configuration schema |
| internal/resources/resources.go | Modified PDB generation to conditionally apply custom or default values |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // (Optional) Dragonfly Pod Disruption Budget minAvailable configuration | ||
| // +optional | ||
| // +kubebuilder:validation:Optional | ||
| MinAvailable string `json:"minAvailable,omitempty"` | ||
|
|
||
| // (Optional) Dragonfly Pod Disruption Budget maxUnavailable configuration |
There was a problem hiding this comment.
The documentation comments for MinAvailable and MaxUnavailable are generic and don't provide guidance on expected values or format. Consider enhancing these comments to clarify:
- That these fields accept either an integer (e.g., "1") or a percentage (e.g., "25%")
- That only one of these fields should be specified, not both
- A brief explanation of what each field controls
For example: "An integer (e.g., 1) or percentage (e.g., 25%) representing the minimum number of pods that must be available during disruptions. Cannot be used together with maxUnavailable."
This would improve the developer experience by making the API more self-documenting and reduce the likelihood of misconfiguration.
| // (Optional) Dragonfly Pod Disruption Budget minAvailable configuration | |
| // +optional | |
| // +kubebuilder:validation:Optional | |
| MinAvailable string `json:"minAvailable,omitempty"` | |
| // (Optional) Dragonfly Pod Disruption Budget maxUnavailable configuration | |
| // (Optional) Minimum number of Dragonfly pods that must be available during voluntary disruptions. | |
| // Accepts an absolute number (e.g. "1") or a percentage of total pods (e.g. "25%"). | |
| // Cannot be used together with MaxUnavailable; only one of minAvailable or maxUnavailable should be set. | |
| // +optional | |
| // +kubebuilder:validation:Optional | |
| MinAvailable string `json:"minAvailable,omitempty"` | |
| // (Optional) Maximum number of Dragonfly pods that can be unavailable during voluntary disruptions. | |
| // Accepts an absolute number (e.g. "1") or a percentage of total pods (e.g. "25%"). | |
| // Cannot be used together with MinAvailable; only one of maxUnavailable or minAvailable should be set. |
| if df.Spec.Pdb.MaxUnavailable != "" { | ||
| maxUnavailable := intstr.Parse(df.Spec.Pdb.MaxUnavailable) | ||
| pdb.Spec.MaxUnavailable = &maxUnavailable | ||
| } else if df.Spec.Pdb.MinAvailable != "" { | ||
| minAvailable := intstr.Parse(df.Spec.Pdb.MinAvailable) | ||
| pdb.Spec.MinAvailable = &minAvailable | ||
| } else { | ||
| pdb.Spec.MaxUnavailable = &intstr.IntOrString{ | ||
| Type: intstr.Int, | ||
| IntVal: 1, | ||
| } | ||
| } |
There was a problem hiding this comment.
Potential nil pointer dereference: The code accesses df.Spec.Pdb.MaxUnavailable and df.Spec.Pdb.MinAvailable without first checking if df.Spec.Pdb is nil. Since Pdb is an optional pointer field (defined as *PdbSpec), it can be nil when not specified in the CR.
The code should follow the established pattern seen elsewhere in this file (e.g., lines 159, 164, 169, 442) where optional pointer fields are checked for nil before accessing their members. The check should wrap the entire conditional block.
| type PdbSpec struct { | ||
| // (Optional) Dragonfly Pod Disruption Budget minAvailable configuration | ||
| // +optional | ||
| // +kubebuilder:validation:Optional | ||
| MinAvailable string `json:"minAvailable,omitempty"` | ||
|
|
||
| // (Optional) Dragonfly Pod Disruption Budget maxUnavailable configuration | ||
| // +optional | ||
| // +kubebuilder:validation:Optional | ||
| MaxUnavailable string `json:"maxUnavailable,omitempty"` | ||
| } |
There was a problem hiding this comment.
The PdbSpec struct allows setting both MinAvailable and MaxAvailable fields simultaneously, which could lead to confusion for users. According to Kubernetes PodDisruptionBudget API semantics, only one of these fields should be specified.
While the implementation in resources.go safely handles this by prioritizing MaxUnavailable, it would be better to add kubebuilder validation markers to enforce mutual exclusivity at the API level, preventing users from creating invalid configurations. Consider adding a webhook validator or CEL validation rule to ensure only one field is set.
| if df.Spec.Pdb.MaxUnavailable != "" { | ||
| maxUnavailable := intstr.Parse(df.Spec.Pdb.MaxUnavailable) | ||
| pdb.Spec.MaxUnavailable = &maxUnavailable | ||
| } else if df.Spec.Pdb.MinAvailable != "" { | ||
| minAvailable := intstr.Parse(df.Spec.Pdb.MinAvailable) | ||
| pdb.Spec.MinAvailable = &minAvailable | ||
| } else { | ||
| pdb.Spec.MaxUnavailable = &intstr.IntOrString{ | ||
| Type: intstr.Int, | ||
| IntVal: 1, | ||
| } |
There was a problem hiding this comment.
The new PDB configuration logic lacks unit test coverage. The existing test file internal/resources/resources_test.go contains tests for other functions like mergeNamedSlices, establishing a pattern of testing utility functions in this package.
The PDB configuration logic should have tests covering:
- When
Pdbis nil (default behavior with maxUnavailable=1) - When
Pdb.MaxUnavailableis set with an integer - When
Pdb.MaxUnavailableis set with a percentage - When
Pdb.MinAvailableis set with an integer - When
Pdb.MinAvailableis set with a percentage - When both fields are set (verifying maxUnavailable takes precedence)
These test cases align with the testing performed manually according to the PR description.
| if df.Spec.Pdb.MaxUnavailable != "" { | |
| maxUnavailable := intstr.Parse(df.Spec.Pdb.MaxUnavailable) | |
| pdb.Spec.MaxUnavailable = &maxUnavailable | |
| } else if df.Spec.Pdb.MinAvailable != "" { | |
| minAvailable := intstr.Parse(df.Spec.Pdb.MinAvailable) | |
| pdb.Spec.MinAvailable = &minAvailable | |
| } else { | |
| pdb.Spec.MaxUnavailable = &intstr.IntOrString{ | |
| Type: intstr.Int, | |
| IntVal: 1, | |
| } | |
| if df.Spec.Pdb == nil || (df.Spec.Pdb.MaxUnavailable == "" && df.Spec.Pdb.MinAvailable == "") { | |
| // Default behavior: maxUnavailable = 1 when Pdb is nil or both fields are unset | |
| pdb.Spec.MaxUnavailable = &intstr.IntOrString{ | |
| Type: intstr.Int, | |
| IntVal: 1, | |
| } | |
| } else if df.Spec.Pdb.MaxUnavailable != "" { | |
| maxUnavailable := intstr.Parse(df.Spec.Pdb.MaxUnavailable) | |
| pdb.Spec.MaxUnavailable = &maxUnavailable | |
| } else if df.Spec.Pdb.MinAvailable != "" { | |
| minAvailable := intstr.Parse(df.Spec.Pdb.MinAvailable) | |
| pdb.Spec.MinAvailable = &minAvailable |
Summary
This PR allows for customizing the values for the PDB when replicas > 1, by accepting values other than "1", which is currently hardcoded. In the absence of a custom PDB, the operator falls back to the previous behaviour.
Changes
Example Usage
Testing
Tested in Kubernetes v1.31.0:
pdbnot specified in CRpdb.maxUnavailablespecified with an integer in CRpdb.maxUnavailablespecified with a percentage in CRpdb.minAvailablespecified with an integer in CRpdb.minAvailablespecified with a percentage in CR