Skip to content

Conversation

@baiyutang
Copy link
Contributor

feat: add PodDisruptionBudget support for Karmada control plane components

  • 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

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:

  • All unit tests pass successfully
  • PDB logic has been centralized in a shared package to eliminate code duplication
  • Uses standard Kubernetes labels (app.kubernetes.io/name/instance) for PDB selectors
  • Maintains backward compatibility - PDB fields are optional
  • Follows existing code patterns and uses established constants

**Does this PR introduce a user-fa

…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>
Copilot AI review requested due to automatic review settings October 22, 2025 14:33
@karmada-bot karmada-bot added the kind/feature Categorizes issue or PR as related to a new feature. label Oct 22, 2025
@gemini-code-assist
Copy link

Warning

Gemini encountered an error creating the summary. You can try again by commenting /gemini summary.

@karmada-bot karmada-bot added the kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API label Oct 22, 2025
@karmada-bot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign rainbowmango, zhzhuang-zju for approval. For more information see the Kubernetes Code Review Process.

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@karmada-bot karmada-bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Oct 22, 2025
Copy link
Contributor

Copilot AI left a 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 PodDisruptionBudgetConfig to the CommonSettings API 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
Copy link

Copilot AI Oct 22, 2025

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.

Suggested change
// verifyDeploymentDetails ensures that the specified deployment slices.Contains the
// verifyDeploymentDetails ensures that the specified deployment contains the

Copilot uses AI. Check for mistakes.
Comment on lines +214 to +271
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,
)
Copy link

Copilot AI Oct 22, 2025

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.

Suggested change
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,
)...)

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +136
// 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
}

Copy link

Copilot AI Oct 22, 2025

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.

Suggested change
// 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 uses AI. Check for mistakes.
@codecov-commenter
Copy link

codecov-commenter commented Oct 22, 2025

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 27.15517% with 169 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.50%. Comparing base (776d7ee) to head (69155fd).

Files with missing lines Patch % Lines
operator/pkg/controller/karmada/validating.go 34.64% 74 Missing and 9 partials ⚠️
operator/pkg/controlplane/pdb/pdb.go 0.00% 53 Missing ⚠️
operator/pkg/util/apiclient/idempotency.go 0.00% 12 Missing ⚠️
operator/pkg/controlplane/apiserver/apiserver.go 0.00% 4 Missing and 2 partials ⚠️
operator/pkg/controlplane/controlplane.go 86.36% 2 Missing and 1 partial ⚠️
operator/pkg/controlplane/etcd/etcd.go 0.00% 2 Missing and 1 partial ⚠️
.../pkg/controlplane/metricsadapter/metricsadapter.go 0.00% 2 Missing and 1 partial ⚠️
operator/pkg/controlplane/search/search.go 0.00% 2 Missing and 1 partial ⚠️
operator/pkg/controlplane/webhook/webhook.go 0.00% 2 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
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     
Flag Coverage Δ
unittests 45.50% <27.15%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@RainbowMango
Copy link
Member

@baiyutang I don't see any difference between this new PR with #6722. Are you still trying to split it?

@karmada-bot karmada-bot added the do-not-merge/contains-merge-commits Indicates a PR which contains merge commits. label Oct 23, 2025
@karmada-bot
Copy link
Collaborator

Adding label do-not-merge/contains-merge-commits because PR contains merge commits, which are not allowed in this repository.
Use git rebase to reapply your commits on top of the target branch. Detailed instructions for doing so can be found here.

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.

@baiyutang baiyutang closed this Oct 23, 2025
@baiyutang baiyutang deleted the feat/pdb-control-plane branch October 23, 2025 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/contains-merge-commits Indicates a PR which contains merge commits. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/feature Categorizes issue or PR as related to a new feature. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants