Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml

Large diffs are not rendered by default.

240 changes: 240 additions & 0 deletions operator/config/crds/operator.karmada.io_karmadas.yaml

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)

// +genclient
Expand Down Expand Up @@ -700,6 +701,11 @@ type CommonSettings struct {
// +kubebuilder:default="system-node-critical"
// +optional
PriorityClassName string `json:"priorityClassName,omitempty"`

// PodDisruptionBudgetConfig specifies the PodDisruptionBudget configuration
// for this component's pods. If not set, no PDB will be created.
// +optional
PodDisruptionBudgetConfig *PodDisruptionBudgetConfig `json:"podDisruptionBudgetConfig,omitempty"`
}

// Image allows to customize the image used for components.
Expand Down Expand Up @@ -793,6 +799,22 @@ type LocalSecretReference struct {
Name string `json:"name,omitempty"`
}

// PodDisruptionBudgetConfig defines a subset of PodDisruptionBudgetSpec fields
// that users can configure for their control plane components.
type PodDisruptionBudgetConfig struct {
// MinAvailable specifies the minimum number or percentage of pods
// that must remain available after evictions.
// Mutually exclusive with MaxUnavailable.
// +optional
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`

// MaxUnavailable specifies the maximum number or percentage of pods
// that can be unavailable after evictions.
// Mutually exclusive with MinAvailable.
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
}

// +kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

Expand Down
32 changes: 32 additions & 0 deletions operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions operator/pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ const (
// KarmadaMetricsAdapterComponent defines the name of the karmada-metrics-adapter component
KarmadaMetricsAdapterComponent = "KarmadaMetricsAdapter"

// KarmadaControllerManager defines the name of the karmada-controller-manager component
KarmadaControllerManager = "karmada-controller-manager"
// KarmadaScheduler defines the name of the karmada-scheduler component
KarmadaScheduler = "karmada-scheduler"
// KarmadaWebhook defines the name of the karmada-webhook component
KarmadaWebhook = "karmada-webhook"
// KarmadaSearch defines the name of the karmada-search component
KarmadaSearch = "karmada-search"
// KarmadaDescheduler defines the name of the karmada-descheduler component
KarmadaDescheduler = "karmada-descheduler"
// KarmadaMetricsAdapter defines the name of the karmada-metrics-adapter component
KarmadaMetricsAdapter = "karmada-metrics-adapter"
// KarmadaAggregatedAPIServer defines the name of the karmada-aggregated-apiserver component
KarmadaAggregatedAPIServer = "karmada-aggregated-apiserver"

// KarmadaOperatorLabelKeyName defines a label key used by all resources created by karmada operator
KarmadaOperatorLabelKeyName = "app.kubernetes.io/managed-by"

Expand Down
63 changes: 63 additions & 0 deletions operator/pkg/controller/karmada/validating.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog/v2"

Expand Down Expand Up @@ -97,6 +98,42 @@ func validateETCD(etcd *operatorv1alpha1.Etcd, karmadaName string, fldPath *fiel
return errs
}

// 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
}

func validate(karmada *operatorv1alpha1.Karmada) error {
var errs field.ErrorList

Expand All @@ -107,6 +144,32 @@ func validate(karmada *operatorv1alpha1.Karmada) error {

errs = append(errs, validateKarmadaAPIServer(components.KarmadaAPIServer, karmada.Spec.HostCluster, fldPath.Child("karmadaAPIServer"))...)
errs = append(errs, validateETCD(components.Etcd, karmada.Name, fldPath.Child("etcd"))...)

// Validate PDB configurations for all components
if components.KarmadaAPIServer != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaAPIServer.CommonSettings, fldPath.Child("karmadaAPIServer"))...)
}
if components.KarmadaControllerManager != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaControllerManager.CommonSettings, fldPath.Child("karmadaControllerManager"))...)
}
if components.KarmadaScheduler != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaScheduler.CommonSettings, fldPath.Child("karmadaScheduler"))...)
}
if components.KarmadaWebhook != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaWebhook.CommonSettings, fldPath.Child("karmadaWebhook"))...)
}
if components.KarmadaDescheduler != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaDescheduler.CommonSettings, fldPath.Child("karmadaDescheduler"))...)
}
if components.KarmadaSearch != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaSearch.CommonSettings, fldPath.Child("karmadaSearch"))...)
}
if components.KarmadaMetricsAdapter != nil {
errs = append(errs, validateCommonSettings(&components.KarmadaMetricsAdapter.CommonSettings, fldPath.Child("karmadaMetricsAdapter"))...)
}
if components.Etcd != nil && components.Etcd.Local != nil {
errs = append(errs, validateCommonSettings(&components.Etcd.Local.CommonSettings, fldPath.Child("etcd").Child("local"))...)
}
}

if len(errs) > 0 {
Expand Down
102 changes: 102 additions & 0 deletions operator/pkg/controller/karmada/validating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"

operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
"github.com/karmada-io/karmada/operator/pkg/util"
Expand Down Expand Up @@ -208,3 +211,102 @@ func Test_validate(t *testing.T) {
})
}
}

func TestValidateCommonSettings(t *testing.T) {
tests := []struct {
name string
commonSettings *operatorv1alpha1.CommonSettings
expectedErrs int
}{
{
name: "nil common settings",
commonSettings: nil,
expectedErrs: 0,
},
{
name: "nil PDB config",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
},
expectedErrs: 0,
},
{
name: "valid minAvailable config",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MinAvailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 2},
},
},
expectedErrs: 0,
},
{
name: "valid maxUnavailable config",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 1},
},
},
expectedErrs: 0,
},
{
name: "valid percentage minAvailable config",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MinAvailable: &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
},
},
expectedErrs: 0,
},
{
name: "both minAvailable and maxUnavailable set",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MinAvailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 2},
MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 1},
},
},
expectedErrs: 1,
},
{
name: "neither minAvailable nor maxUnavailable set",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{},
},
expectedErrs: 1,
},
{
name: "minAvailable greater than replicas",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MinAvailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 4},
},
},
expectedErrs: 1,
},
{
name: "minAvailable equal to replicas",
commonSettings: &operatorv1alpha1.CommonSettings{
Replicas: ptr.To[int32](3),
PodDisruptionBudgetConfig: &operatorv1alpha1.PodDisruptionBudgetConfig{
MinAvailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 3},
},
},
expectedErrs: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := validateCommonSettings(tt.commonSettings, field.NewPath("test"))
if len(errs) != tt.expectedErrs {
t.Errorf("expected %d errors, got %d: %v", tt.expectedErrs, len(errs), errs)
}
})
}
}
14 changes: 14 additions & 0 deletions operator/pkg/controlplane/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import (
clientsetscheme "k8s.io/client-go/kubernetes/scheme"

operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
"github.com/karmada-io/karmada/operator/pkg/constants"
"github.com/karmada-io/karmada/operator/pkg/controlplane/etcd"
"github.com/karmada-io/karmada/operator/pkg/controlplane/pdb"
"github.com/karmada-io/karmada/operator/pkg/util"
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
"github.com/karmada-io/karmada/operator/pkg/util/patcher"
Expand Down Expand Up @@ -87,6 +89,12 @@ func installKarmadaAPIServer(client clientset.Interface, cfg *operatorv1alpha1.K
if err := apiclient.CreateOrUpdateDeployment(client, apiserverDeployment); err != nil {
return fmt.Errorf("error when creating deployment for %s, err: %w", apiserverDeployment.Name, err)
}

// Ensure PDB for the apiserver component if configured
if err := pdb.EnsurePodDisruptionBudget(constants.KarmadaAPIServer, name, namespace, &cfg.CommonSettings, client); err != nil {
return fmt.Errorf("failed to ensure PDB for apiserver component, err: %w", err)
}

return nil
}

Expand Down Expand Up @@ -158,6 +166,12 @@ func installKarmadaAggregatedAPIServer(client clientset.Interface, cfg *operator
if err := apiclient.CreateOrUpdateDeployment(client, aggregatedAPIServerDeployment); err != nil {
return fmt.Errorf("error when creating deployment for %s, err: %w", aggregatedAPIServerDeployment.Name, err)
}

// Ensure PDB for the aggregated apiserver component if configured
if err := pdb.EnsurePodDisruptionBudget(constants.KarmadaAggregatedAPIServer, name, namespace, &cfg.CommonSettings, client); err != nil {
return fmt.Errorf("failed to ensure PDB for aggregated apiserver component, err: %w", err)
}

return nil
}

Expand Down
Loading
Loading