-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: add PDB support for Karmada control plane( new PR) #6867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…nents - Add PodDisruptionBudgetConfig to CommonSettings API - Implement PDB creation/update/deletion logic for all components - Add validation for PDB configuration (mutual exclusivity, replicas check) - Support both minAvailable and maxUnavailable PDB strategies Signed-off-by: baiyutang <irichardwang@gmail.com>
Signed-off-by: baiyutang <irichardwang@gmail.com>
…nents - Add PodDisruptionBudgetConfig to CommonSettings API - Implement PDB creation/update/deletion logic for all components - Add validation for PDB configuration (mutual exclusivity, replicas check) - Support both minAvailable and maxUnavailable PDB strategies Signed-off-by: baiyutang <irichardwang@gmail.com>
Signed-off-by: baiyutang <irichardwang@gmail.com>
Signed-off-by: baiyutang <irichardwang@gmail.com>
Signed-off-by: baiyutang <irichardwang@gmail.com>
|
Warning Gemini encountered an error creating the summary. You can try again by commenting |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces PodDisruptionBudget (PDB) support for Karmada Operator control plane components, enabling declarative high availability configuration for all components during planned disruptions. The implementation mirrors the PDB functionality available in Helm charts and maintains backward compatibility.
Key changes:
- Added
PodDisruptionBudgetConfigto theCommonSettingsAPI with validation for mutual exclusivity and replica constraints - Implemented centralized PDB management logic in a shared package to ensure consistent behavior across components
- Updated all control plane components to create/update/delete PDBs based on configuration
Reviewed Changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| operator/pkg/apis/operator/v1alpha1/type.go | Adds PodDisruptionBudgetConfig struct to CommonSettings API |
| operator/pkg/controlplane/pdb/pdb.go | New shared package implementing PDB create/update/delete logic |
| operator/pkg/controller/karmada/validating.go | Adds validation logic for PDB configuration including mutual exclusivity and replica checks |
| operator/pkg/controlplane/*/*.go | Updates component installation functions to ensure PDB creation |
| operator/pkg/util/apiclient/idempotency.go | Adds CreateOrUpdatePodDisruptionBudget helper function |
| operator/pkg/controlplane/*_test.go | Updates tests to verify PDB creation alongside deployments |
| operator/config/crds/*.yaml | Updates CRD schemas with PDB configuration fields |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| } | ||
|
|
||
| // verifyDeploymentDetails ensures that the specified deployment contains the | ||
| // verifyDeploymentDetails ensures that the specified deployment slices.Contains the |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected 'slices.Contains' to 'contains' in comment. The word 'Contains' appears to be an artifact from refactoring and doesn't make grammatical sense in this context.
| // verifyDeploymentDetails ensures that the specified deployment slices.Contains the | |
| // verifyDeploymentDetails ensures that the specified deployment contains the |
| checkPdb(fldPath.Child("etcd").Child("podDisruptionBudgetConfig"), | ||
| components.Etcd.Local.PodDisruptionBudgetConfig, | ||
| components.Etcd.Local.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaAPIServer != nil { | ||
| checkPdb(fldPath.Child("karmadaAPIServer").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaAPIServer.PodDisruptionBudgetConfig, | ||
| components.KarmadaAPIServer.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaAggregatedAPIServer != nil { | ||
| checkPdb(fldPath.Child("karmadaAggregatedAPIServer").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaAggregatedAPIServer.PodDisruptionBudgetConfig, | ||
| components.KarmadaAggregatedAPIServer.Replicas, | ||
| ) | ||
| } | ||
| if components.KubeControllerManager != nil { | ||
| checkPdb(fldPath.Child("kubeControllerManager").Child("podDisruptionBudgetConfig"), | ||
| components.KubeControllerManager.PodDisruptionBudgetConfig, | ||
| components.KubeControllerManager.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaControllerManager != nil { | ||
| checkPdb(fldPath.Child("karmadaControllerManager").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaControllerManager.PodDisruptionBudgetConfig, | ||
| components.KarmadaControllerManager.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaScheduler != nil { | ||
| checkPdb(fldPath.Child("karmadaScheduler").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaScheduler.PodDisruptionBudgetConfig, | ||
| components.KarmadaScheduler.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaDescheduler != nil { | ||
| checkPdb(fldPath.Child("karmadaDescheduler").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaDescheduler.PodDisruptionBudgetConfig, | ||
| components.KarmadaDescheduler.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaSearch != nil { | ||
| checkPdb(fldPath.Child("karmadaSearch").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaSearch.PodDisruptionBudgetConfig, | ||
| components.KarmadaSearch.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaMetricsAdapter != nil { | ||
| checkPdb(fldPath.Child("karmadaMetricsAdapter").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaMetricsAdapter.PodDisruptionBudgetConfig, | ||
| components.KarmadaMetricsAdapter.Replicas, | ||
| ) | ||
| } | ||
| if components.KarmadaWebhook != nil { | ||
| checkPdb(fldPath.Child("karmadaWebhook").Child("podDisruptionBudgetConfig"), | ||
| components.KarmadaWebhook.PodDisruptionBudgetConfig, | ||
| components.KarmadaWebhook.Replicas, | ||
| ) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error collection - the return value from checkPdb is not being appended to errs. All other calls to checkPdb in this function append their results to errs, but this one does not.
| checkPdb(fldPath.Child("etcd").Child("podDisruptionBudgetConfig"), | |
| components.Etcd.Local.PodDisruptionBudgetConfig, | |
| components.Etcd.Local.Replicas, | |
| ) | |
| } | |
| if components.KarmadaAPIServer != nil { | |
| checkPdb(fldPath.Child("karmadaAPIServer").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaAPIServer.PodDisruptionBudgetConfig, | |
| components.KarmadaAPIServer.Replicas, | |
| ) | |
| } | |
| if components.KarmadaAggregatedAPIServer != nil { | |
| checkPdb(fldPath.Child("karmadaAggregatedAPIServer").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaAggregatedAPIServer.PodDisruptionBudgetConfig, | |
| components.KarmadaAggregatedAPIServer.Replicas, | |
| ) | |
| } | |
| if components.KubeControllerManager != nil { | |
| checkPdb(fldPath.Child("kubeControllerManager").Child("podDisruptionBudgetConfig"), | |
| components.KubeControllerManager.PodDisruptionBudgetConfig, | |
| components.KubeControllerManager.Replicas, | |
| ) | |
| } | |
| if components.KarmadaControllerManager != nil { | |
| checkPdb(fldPath.Child("karmadaControllerManager").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaControllerManager.PodDisruptionBudgetConfig, | |
| components.KarmadaControllerManager.Replicas, | |
| ) | |
| } | |
| if components.KarmadaScheduler != nil { | |
| checkPdb(fldPath.Child("karmadaScheduler").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaScheduler.PodDisruptionBudgetConfig, | |
| components.KarmadaScheduler.Replicas, | |
| ) | |
| } | |
| if components.KarmadaDescheduler != nil { | |
| checkPdb(fldPath.Child("karmadaDescheduler").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaDescheduler.PodDisruptionBudgetConfig, | |
| components.KarmadaDescheduler.Replicas, | |
| ) | |
| } | |
| if components.KarmadaSearch != nil { | |
| checkPdb(fldPath.Child("karmadaSearch").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaSearch.PodDisruptionBudgetConfig, | |
| components.KarmadaSearch.Replicas, | |
| ) | |
| } | |
| if components.KarmadaMetricsAdapter != nil { | |
| checkPdb(fldPath.Child("karmadaMetricsAdapter").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaMetricsAdapter.PodDisruptionBudgetConfig, | |
| components.KarmadaMetricsAdapter.Replicas, | |
| ) | |
| } | |
| if components.KarmadaWebhook != nil { | |
| checkPdb(fldPath.Child("karmadaWebhook").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaWebhook.PodDisruptionBudgetConfig, | |
| components.KarmadaWebhook.Replicas, | |
| ) | |
| errs = append(errs, checkPdb(fldPath.Child("etcd").Child("podDisruptionBudgetConfig"), | |
| components.Etcd.Local.PodDisruptionBudgetConfig, | |
| components.Etcd.Local.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaAPIServer != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaAPIServer").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaAPIServer.PodDisruptionBudgetConfig, | |
| components.KarmadaAPIServer.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaAggregatedAPIServer != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaAggregatedAPIServer").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaAggregatedAPIServer.PodDisruptionBudgetConfig, | |
| components.KarmadaAggregatedAPIServer.Replicas, | |
| )...) | |
| } | |
| if components.KubeControllerManager != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("kubeControllerManager").Child("podDisruptionBudgetConfig"), | |
| components.KubeControllerManager.PodDisruptionBudgetConfig, | |
| components.KubeControllerManager.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaControllerManager != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaControllerManager").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaControllerManager.PodDisruptionBudgetConfig, | |
| components.KarmadaControllerManager.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaScheduler != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaScheduler").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaScheduler.PodDisruptionBudgetConfig, | |
| components.KarmadaScheduler.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaDescheduler != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaDescheduler").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaDescheduler.PodDisruptionBudgetConfig, | |
| components.KarmadaDescheduler.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaSearch != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaSearch").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaSearch.PodDisruptionBudgetConfig, | |
| components.KarmadaSearch.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaMetricsAdapter != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaMetricsAdapter").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaMetricsAdapter.PodDisruptionBudgetConfig, | |
| components.KarmadaMetricsAdapter.Replicas, | |
| )...) | |
| } | |
| if components.KarmadaWebhook != nil { | |
| errs = append(errs, checkPdb(fldPath.Child("karmadaWebhook").Child("podDisruptionBudgetConfig"), | |
| components.KarmadaWebhook.PodDisruptionBudgetConfig, | |
| components.KarmadaWebhook.Replicas, | |
| )...) |
| // validateCommonSettings validates the common settings of a component, including PDB configuration | ||
| func validateCommonSettings(commonSettings *operatorv1alpha1.CommonSettings, fldPath *field.Path) (errs field.ErrorList) { | ||
| if commonSettings == nil { | ||
| return nil | ||
| } | ||
|
|
||
| if commonSettings.PodDisruptionBudgetConfig != nil { | ||
| pdbConfig := commonSettings.PodDisruptionBudgetConfig | ||
| pdbPath := fldPath.Child("podDisruptionBudgetConfig") | ||
|
|
||
| // Check if both minAvailable and maxUnavailable are set (mutually exclusive) | ||
| if pdbConfig.MinAvailable != nil && pdbConfig.MaxUnavailable != nil { | ||
| errs = append(errs, field.Invalid(pdbPath, pdbConfig, "minAvailable and maxUnavailable are mutually exclusive, only one can be set")) | ||
| } | ||
|
|
||
| // Check if at least one of minAvailable or maxUnavailable is set | ||
| if pdbConfig.MinAvailable == nil && pdbConfig.MaxUnavailable == nil { | ||
| errs = append(errs, field.Invalid(pdbPath, pdbConfig, "either minAvailable or maxUnavailable must be set")) | ||
| } | ||
|
|
||
| // Validate minAvailable against replicas if replicas is set | ||
| if pdbConfig.MinAvailable != nil && commonSettings.Replicas != nil { | ||
| replicas := *commonSettings.Replicas | ||
| if pdbConfig.MinAvailable.Type == intstr.Int { | ||
| minAvailableInt := pdbConfig.MinAvailable.IntValue() | ||
| if minAvailableInt > int(replicas) { | ||
| errs = append(errs, field.Invalid(pdbPath.Child("minAvailable"), pdbConfig.MinAvailable, | ||
| fmt.Sprintf("minAvailable (%d) cannot be greater than replicas (%d)", minAvailableInt, replicas))) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return errs | ||
| } | ||
|
|
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validateCommonSettings function is defined but never used in the codebase. This function appears to duplicate the validation logic in checkPdb (lines 157-204) and validatePDBConfigs (lines 206-274). Consider removing this unused function to reduce code duplication.
| // validateCommonSettings validates the common settings of a component, including PDB configuration | |
| func validateCommonSettings(commonSettings *operatorv1alpha1.CommonSettings, fldPath *field.Path) (errs field.ErrorList) { | |
| if commonSettings == nil { | |
| return nil | |
| } | |
| if commonSettings.PodDisruptionBudgetConfig != nil { | |
| pdbConfig := commonSettings.PodDisruptionBudgetConfig | |
| pdbPath := fldPath.Child("podDisruptionBudgetConfig") | |
| // Check if both minAvailable and maxUnavailable are set (mutually exclusive) | |
| if pdbConfig.MinAvailable != nil && pdbConfig.MaxUnavailable != nil { | |
| errs = append(errs, field.Invalid(pdbPath, pdbConfig, "minAvailable and maxUnavailable are mutually exclusive, only one can be set")) | |
| } | |
| // Check if at least one of minAvailable or maxUnavailable is set | |
| if pdbConfig.MinAvailable == nil && pdbConfig.MaxUnavailable == nil { | |
| errs = append(errs, field.Invalid(pdbPath, pdbConfig, "either minAvailable or maxUnavailable must be set")) | |
| } | |
| // Validate minAvailable against replicas if replicas is set | |
| if pdbConfig.MinAvailable != nil && commonSettings.Replicas != nil { | |
| replicas := *commonSettings.Replicas | |
| if pdbConfig.MinAvailable.Type == intstr.Int { | |
| minAvailableInt := pdbConfig.MinAvailable.IntValue() | |
| if minAvailableInt > int(replicas) { | |
| errs = append(errs, field.Invalid(pdbPath.Child("minAvailable"), pdbConfig.MinAvailable, | |
| fmt.Sprintf("minAvailable (%d) cannot be greater than replicas (%d)", minAvailableInt, replicas))) | |
| } | |
| } | |
| } | |
| } | |
| return errs | |
| } |
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #6867 +/- ##
==========================================
- Coverage 45.57% 45.50% -0.07%
==========================================
Files 692 693 +1
Lines 57816 58048 +232
==========================================
+ Hits 26347 26413 +66
- Misses 29816 29966 +150
- Partials 1653 1669 +16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@baiyutang I don't see any difference between this new PR with #6722. Are you still trying to split it? |
|
Adding label Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
feat: add PodDisruptionBudget support for Karmada control plane components
What type of PR is this?
/kind feature
/kind api-change
What this PR does / why we need it:
This PR adds PodDisruptionBudget (PDB) support to the Karmada Operator, enabling users to configure high availability guarantees for all Karmada control plane components (karmada-controller-manager, karmada-scheduler, karmada-apiserver, etc.) during planned disruptions.
The feature mirrors the PDB functionality already available in Helm charts, providing consistent high availability guarantees across different installation methods. Users can now declaratively configure PDB strategies through the Karmada CRD, ensuring that control plane components remain available during node maintenance, cluster upgrades, or other planned operations.
Which issue(s) this PR fixes:
Fixes #(issue number)
#6282
Special notes for your reviewer:
**Does this PR introduce a user-fa