From 596b1f93fcac23e60bd297dd3d11ba6d37ae1e54 Mon Sep 17 00:00:00 2001 From: Chithresh Azad Date: Wed, 2 Sep 2026 14:02:09 -0700 Subject: [PATCH] Add TerminatedPodGcThreshold to control plane component config Adds support for kubeControllerManagerConfig.podGcControllerConfig. terminatedPodGcThreshold, wiring it through the create path (CloudFormation via the vendored goformation structs) and the update path (eksctl utils update-control-plane-component-config, via the EKS SDK). The kube-controller-manager converters are restructured so the pod GC and horizontal pod autoscaler sub-configs are independent: either or both may be set, and setting one no longer drops the other. Signed-off-by: Chithresh Azad --- .../eksctl.io/v1alpha5/assets/schema.json | 23 +++++- pkg/apis/eksctl.io/v1alpha5/types.go | 12 +++ .../v1alpha5/zz_generated.deepcopy.go | 78 ++++++++++++------- pkg/cfn/builder/cluster.go | 25 ++++-- .../builder/cluster_component_config_test.go | 36 +++++++++ .../update_control_plane_component_config.go | 25 ++++-- ...rol_plane_component_config_request_test.go | 36 +++++++++ ...eks-cluster_kubecontrollermanagerconfig.go | 5 ++ .../aws-eks-cluster_podgccontrollerconfig.go | 36 +++++++++ .../usage/control-plane-component-config.md | 11 ++- 10 files changed, 243 insertions(+), 44 deletions(-) create mode 100644 pkg/goformation/cloudformation/eks/aws-eks-cluster_podgccontrollerconfig.go diff --git a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json index c6e41046c6..d7bb7aa919 100755 --- a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json +++ b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json @@ -1656,10 +1656,16 @@ "$ref": "#/definitions/HorizontalPodAutoscalerControllerConfig", "description": "specifies the horizontal pod autoscaler controller configuration.", "x-intellij-html-description": "specifies the horizontal pod autoscaler controller configuration." + }, + "podGCControllerConfig": { + "$ref": "#/definitions/PodGCControllerConfig", + "description": "specifies the pod garbage collector controller configuration.", + "x-intellij-html-description": "specifies the pod garbage collector controller configuration." } }, "preferredOrder": [ - "horizontalPodAutoscalerControllerConfig" + "horizontalPodAutoscalerControllerConfig", + "podGCControllerConfig" ], "additionalProperties": false, "description": "holds the kube-controller-manager configuration.", @@ -2981,6 +2987,21 @@ "description": "specifies placement group information", "x-intellij-html-description": "specifies placement group information" }, + "PodGCControllerConfig": { + "properties": { + "terminatedPodGCThreshold": { + "type": "integer", + "description": "specifies the number of terminated pods that can exist before the pod garbage collector starts deleting terminated pods.", + "x-intellij-html-description": "specifies the number of terminated pods that can exist before the pod garbage collector starts deleting terminated pods." + } + }, + "preferredOrder": [ + "terminatedPodGCThreshold" + ], + "additionalProperties": false, + "description": "holds the pod garbage collector controller configuration.", + "x-intellij-html-description": "holds the pod garbage collector controller configuration." + }, "PodIdentityAssociation": { "properties": { "createServiceAccount": { diff --git a/pkg/apis/eksctl.io/v1alpha5/types.go b/pkg/apis/eksctl.io/v1alpha5/types.go index 56f8105156..279bc1f80b 100644 --- a/pkg/apis/eksctl.io/v1alpha5/types.go +++ b/pkg/apis/eksctl.io/v1alpha5/types.go @@ -1222,6 +1222,10 @@ type KubeControllerManagerConfig struct { // controller configuration. // +optional HorizontalPodAutoscalerControllerConfig *HorizontalPodAutoscalerControllerConfig `json:"horizontalPodAutoscalerControllerConfig,omitempty"` + + // PodGCControllerConfig specifies the pod garbage collector controller configuration. + // +optional + PodGCControllerConfig *PodGCControllerConfig `json:"podGCControllerConfig,omitempty"` } // HorizontalPodAutoscalerControllerConfig holds the horizontal pod autoscaler controller configuration. @@ -1232,6 +1236,14 @@ type HorizontalPodAutoscalerControllerConfig struct { HorizontalPodAutoscalerSyncPeriod *string `json:"horizontalPodAutoscalerSyncPeriod,omitempty"` } +// PodGCControllerConfig holds the pod garbage collector controller configuration. +type PodGCControllerConfig struct { + // TerminatedPodGCThreshold specifies the number of terminated pods that can exist + // before the pod garbage collector starts deleting terminated pods. + // +optional + TerminatedPodGCThreshold *int `json:"terminatedPodGCThreshold,omitempty"` +} + // OutpostInfo describes the Outpost info. type OutpostInfo interface { // IsControlPlaneOnOutposts returns true if the control plane is on Outposts. diff --git a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go index 02793615c1..6059f176a3 100644 --- a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go +++ b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go @@ -1654,6 +1654,11 @@ func (in *KubeControllerManagerConfig) DeepCopyInto(out *KubeControllerManagerCo *out = new(HorizontalPodAutoscalerControllerConfig) (*in).DeepCopyInto(*out) } + if in.PodGCControllerConfig != nil { + in, out := &in.PodGCControllerConfig, &out.PodGCControllerConfig + *out = new(PodGCControllerConfig) + (*in).DeepCopyInto(*out) + } return } @@ -2519,6 +2524,27 @@ func (in *Placement) DeepCopy() *Placement { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodGCControllerConfig) DeepCopyInto(out *PodGCControllerConfig) { + *out = *in + if in.TerminatedPodGCThreshold != nil { + in, out := &in.TerminatedPodGCThreshold, &out.TerminatedPodGCThreshold + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodGCControllerConfig. +func (in *PodGCControllerConfig) DeepCopy() *PodGCControllerConfig { + if in == nil { + return nil + } + out := new(PodGCControllerConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodIdentityAssociation) DeepCopyInto(out *PodIdentityAssociation) { *out = *in @@ -2718,6 +2744,32 @@ func (in *RemoteNodesIAM) DeepCopy() *RemoteNodesIAM { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceWeight) DeepCopyInto(out *ResourceWeight) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceWeight. +func (in *ResourceWeight) DeepCopy() *ResourceWeight { + if in == nil { + return nil + } + out := new(ResourceWeight) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RollbackConfig) DeepCopyInto(out *RollbackConfig) { *out = *in @@ -2786,32 +2838,6 @@ func (in *ScalingConfig) DeepCopy() *ScalingConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceWeight) DeepCopyInto(out *ResourceWeight) { - *out = *in - if in.Name != nil { - in, out := &in.Name, &out.Name - *out = new(string) - **out = **in - } - if in.Weight != nil { - in, out := &in.Weight, &out.Weight - *out = new(int) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceWeight. -func (in *ResourceWeight) DeepCopy() *ResourceWeight { - if in == nil { - return nil - } - out := new(ResourceWeight) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScoringStrategy) DeepCopyInto(out *ScoringStrategy) { *out = *in diff --git a/pkg/cfn/builder/cluster.go b/pkg/cfn/builder/cluster.go index 48b19b1f4b..914864f98d 100644 --- a/pkg/cfn/builder/cluster.go +++ b/pkg/cfn/builder/cluster.go @@ -706,16 +706,25 @@ func makeKubeSchedulerConfig(config *api.KubeSchedulerConfig) *gfneks.Cluster_Ku // CloudFormation representation. It returns nil if no values are set, so that the property // is omitted from the template entirely. func makeKubeControllerManagerConfig(config *api.KubeControllerManagerConfig) *gfneks.Cluster_KubeControllerManagerConfig { - if config == nil || config.HorizontalPodAutoscalerControllerConfig == nil { - return nil - } - hpaConfig := config.HorizontalPodAutoscalerControllerConfig - if hpaConfig.HorizontalPodAutoscalerSyncPeriod == nil { + if config == nil { return nil } - return &gfneks.Cluster_KubeControllerManagerConfig{ - HorizontalPodAutoscalerControllerConfig: &gfneks.Cluster_HorizontalPodAutoscalerControllerConfig{ + result := &gfneks.Cluster_KubeControllerManagerConfig{} + set := false + if hpaConfig := config.HorizontalPodAutoscalerControllerConfig; hpaConfig != nil && hpaConfig.HorizontalPodAutoscalerSyncPeriod != nil { + result.HorizontalPodAutoscalerControllerConfig = &gfneks.Cluster_HorizontalPodAutoscalerControllerConfig{ HorizontalPodAutoscalerSyncPeriod: gfnt.NewString(*hpaConfig.HorizontalPodAutoscalerSyncPeriod), - }, + } + set = true + } + if podGCConfig := config.PodGCControllerConfig; podGCConfig != nil && podGCConfig.TerminatedPodGCThreshold != nil { + result.PodGcControllerConfig = &gfneks.Cluster_PodGcControllerConfig{ + TerminatedPodGcThreshold: gfnt.NewInteger(*podGCConfig.TerminatedPodGCThreshold), + } + set = true + } + if !set { + return nil } + return result } diff --git a/pkg/cfn/builder/cluster_component_config_test.go b/pkg/cfn/builder/cluster_component_config_test.go index b3766c4a3f..192876dbb4 100644 --- a/pkg/cfn/builder/cluster_component_config_test.go +++ b/pkg/cfn/builder/cluster_component_config_test.go @@ -282,6 +282,42 @@ var _ = Describe("control plane component config template properties", func() { }, }, }), + Entry("empty pod GC config is omitted", entry{ + config: &api.KubeControllerManagerConfig{ + PodGCControllerConfig: &api.PodGCControllerConfig{}, + }, + expected: nil, + }), + Entry("terminated pod GC threshold set", entry{ + config: &api.KubeControllerManagerConfig{ + PodGCControllerConfig: &api.PodGCControllerConfig{ + TerminatedPodGCThreshold: aws.Int(12000), + }, + }, + expected: &gfneks.Cluster_KubeControllerManagerConfig{ + PodGcControllerConfig: &gfneks.Cluster_PodGcControllerConfig{ + TerminatedPodGcThreshold: gfnt.NewInteger(12000), + }, + }, + }), + Entry("both sync period and terminated pod GC threshold set", entry{ + config: &api.KubeControllerManagerConfig{ + HorizontalPodAutoscalerControllerConfig: &api.HorizontalPodAutoscalerControllerConfig{ + HorizontalPodAutoscalerSyncPeriod: aws.String("15s"), + }, + PodGCControllerConfig: &api.PodGCControllerConfig{ + TerminatedPodGCThreshold: aws.Int(12000), + }, + }, + expected: &gfneks.Cluster_KubeControllerManagerConfig{ + HorizontalPodAutoscalerControllerConfig: &gfneks.Cluster_HorizontalPodAutoscalerControllerConfig{ + HorizontalPodAutoscalerSyncPeriod: gfnt.NewString("15s"), + }, + PodGcControllerConfig: &gfneks.Cluster_PodGcControllerConfig{ + TerminatedPodGcThreshold: gfnt.NewInteger(12000), + }, + }, + }), ) }) }) diff --git a/pkg/ctl/utils/update_control_plane_component_config.go b/pkg/ctl/utils/update_control_plane_component_config.go index f2cedf7d74..b0d9acd5e8 100644 --- a/pkg/ctl/utils/update_control_plane_component_config.go +++ b/pkg/ctl/utils/update_control_plane_component_config.go @@ -151,16 +151,25 @@ func makeKubeSchedulerConfigRequest(config *api.KubeSchedulerConfig) *ekstypes.K // component is omitted from the request entirely and left unchanged. // It mirrors makeKubeControllerManagerConfig in pkg/cfn/builder/cluster.go. func makeKubeControllerManagerConfigRequest(config *api.KubeControllerManagerConfig) *ekstypes.KubeControllerManagerConfigRequest { - if config == nil || config.HorizontalPodAutoscalerControllerConfig == nil { - return nil - } - hpaConfig := config.HorizontalPodAutoscalerControllerConfig - if hpaConfig.HorizontalPodAutoscalerSyncPeriod == nil { + if config == nil { return nil } - return &ekstypes.KubeControllerManagerConfigRequest{ - HorizontalPodAutoscalerControllerConfig: &ekstypes.HorizontalPodAutoscalerControllerConfigRequest{ + result := &ekstypes.KubeControllerManagerConfigRequest{} + set := false + if hpaConfig := config.HorizontalPodAutoscalerControllerConfig; hpaConfig != nil && hpaConfig.HorizontalPodAutoscalerSyncPeriod != nil { + result.HorizontalPodAutoscalerControllerConfig = &ekstypes.HorizontalPodAutoscalerControllerConfigRequest{ HorizontalPodAutoscalerSyncPeriod: hpaConfig.HorizontalPodAutoscalerSyncPeriod, - }, + } + set = true + } + if podGCConfig := config.PodGCControllerConfig; podGCConfig != nil && podGCConfig.TerminatedPodGCThreshold != nil { + result.PodGcControllerConfig = &ekstypes.PodGcControllerConfigRequest{ + TerminatedPodGcThreshold: aws.Int32(int32(*podGCConfig.TerminatedPodGCThreshold)), + } + set = true + } + if !set { + return nil } + return result } diff --git a/pkg/ctl/utils/update_control_plane_component_config_request_test.go b/pkg/ctl/utils/update_control_plane_component_config_request_test.go index 8a022e7b60..2af76f298c 100644 --- a/pkg/ctl/utils/update_control_plane_component_config_request_test.go +++ b/pkg/ctl/utils/update_control_plane_component_config_request_test.go @@ -250,6 +250,42 @@ var _ = Describe("control plane component config update requests", func() { }, }, }), + Entry("empty pod GC config is not sent", entry{ + config: &api.KubeControllerManagerConfig{ + PodGCControllerConfig: &api.PodGCControllerConfig{}, + }, + expected: nil, + }), + Entry("terminated pod GC threshold set", entry{ + config: &api.KubeControllerManagerConfig{ + PodGCControllerConfig: &api.PodGCControllerConfig{ + TerminatedPodGCThreshold: aws.Int(12000), + }, + }, + expected: &ekstypes.KubeControllerManagerConfigRequest{ + PodGcControllerConfig: &ekstypes.PodGcControllerConfigRequest{ + TerminatedPodGcThreshold: aws.Int32(12000), + }, + }, + }), + Entry("both sync period and terminated pod GC threshold set", entry{ + config: &api.KubeControllerManagerConfig{ + HorizontalPodAutoscalerControllerConfig: &api.HorizontalPodAutoscalerControllerConfig{ + HorizontalPodAutoscalerSyncPeriod: aws.String("15s"), + }, + PodGCControllerConfig: &api.PodGCControllerConfig{ + TerminatedPodGCThreshold: aws.Int(12000), + }, + }, + expected: &ekstypes.KubeControllerManagerConfigRequest{ + HorizontalPodAutoscalerControllerConfig: &ekstypes.HorizontalPodAutoscalerControllerConfigRequest{ + HorizontalPodAutoscalerSyncPeriod: aws.String("15s"), + }, + PodGcControllerConfig: &ekstypes.PodGcControllerConfigRequest{ + TerminatedPodGcThreshold: aws.Int32(12000), + }, + }, + }), ) }) }) diff --git a/pkg/goformation/cloudformation/eks/aws-eks-cluster_kubecontrollermanagerconfig.go b/pkg/goformation/cloudformation/eks/aws-eks-cluster_kubecontrollermanagerconfig.go index feb3c323d7..ed9da6739c 100644 --- a/pkg/goformation/cloudformation/eks/aws-eks-cluster_kubecontrollermanagerconfig.go +++ b/pkg/goformation/cloudformation/eks/aws-eks-cluster_kubecontrollermanagerconfig.go @@ -13,6 +13,11 @@ type Cluster_KubeControllerManagerConfig struct { // See: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-eks-cluster-kubecontrollermanagerconfig.html#cfn-eks-cluster-kubecontrollermanagerconfig-horizontalpodautoscalercontrollerconfig HorizontalPodAutoscalerControllerConfig *Cluster_HorizontalPodAutoscalerControllerConfig `json:"HorizontalPodAutoscalerControllerConfig,omitempty"` + // PodGcControllerConfig AWS CloudFormation Property + // Required: false + // See: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-eks-cluster-kubecontrollermanagerconfig.html#cfn-eks-cluster-kubecontrollermanagerconfig-podgccontrollerconfig + PodGcControllerConfig *Cluster_PodGcControllerConfig `json:"PodGcControllerConfig,omitempty"` + // AWSCloudFormationDeletionPolicy represents a CloudFormation DeletionPolicy AWSCloudFormationDeletionPolicy policies.DeletionPolicy `json:"-"` diff --git a/pkg/goformation/cloudformation/eks/aws-eks-cluster_podgccontrollerconfig.go b/pkg/goformation/cloudformation/eks/aws-eks-cluster_podgccontrollerconfig.go new file mode 100644 index 0000000000..76b1180e13 --- /dev/null +++ b/pkg/goformation/cloudformation/eks/aws-eks-cluster_podgccontrollerconfig.go @@ -0,0 +1,36 @@ +package eks + +import ( + "github.com/weaveworks/eksctl/pkg/goformation/cloudformation/policies" + "github.com/weaveworks/eksctl/pkg/goformation/cloudformation/types" +) + +// Cluster_PodGcControllerConfig AWS CloudFormation Resource (AWS::EKS::Cluster.PodGcControllerConfig) +// See: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-eks-cluster-podgccontrollerconfig.html +type Cluster_PodGcControllerConfig struct { + + // TerminatedPodGcThreshold AWS CloudFormation Property + // Required: false + // See: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-eks-cluster-podgccontrollerconfig.html#cfn-eks-cluster-podgccontrollerconfig-terminatedpodgcthreshold + TerminatedPodGcThreshold *types.Value `json:"TerminatedPodGcThreshold,omitempty"` + + // AWSCloudFormationDeletionPolicy represents a CloudFormation DeletionPolicy + AWSCloudFormationDeletionPolicy policies.DeletionPolicy `json:"-"` + + // AWSCloudFormationUpdateReplacePolicy represents a CloudFormation UpdateReplacePolicy + AWSCloudFormationUpdateReplacePolicy policies.UpdateReplacePolicy `json:"-"` + + // AWSCloudFormationDependsOn stores the logical ID of the resources to be created before this resource + AWSCloudFormationDependsOn []string `json:"-"` + + // AWSCloudFormationMetadata stores structured data associated with this resource + AWSCloudFormationMetadata map[string]interface{} `json:"-"` + + // AWSCloudFormationCondition stores the logical ID of the condition that must be satisfied for this resource to be created + AWSCloudFormationCondition string `json:"-"` +} + +// AWSCloudFormationType returns the AWS CloudFormation resource type +func (r *Cluster_PodGcControllerConfig) AWSCloudFormationType() string { + return "AWS::EKS::Cluster.PodGcControllerConfig" +} diff --git a/userdocs/src/usage/control-plane-component-config.md b/userdocs/src/usage/control-plane-component-config.md index 645ab72804..7d2c3aa747 100644 --- a/userdocs/src/usage/control-plane-component-config.md +++ b/userdocs/src/usage/control-plane-component-config.md @@ -38,6 +38,8 @@ kubeSchedulerConfig: kubeControllerManagerConfig: horizontalPodAutoscalerControllerConfig: horizontalPodAutoscalerSyncPeriod: 15s + podGCControllerConfig: + terminatedPodGCThreshold: 12000 ``` ```shell @@ -98,11 +100,18 @@ look after a pod is placed on them. kubeControllerManagerConfig: horizontalPodAutoscalerControllerConfig: horizontalPodAutoscalerSyncPeriod: 15s + podGCControllerConfig: + terminatedPodGCThreshold: 12000 ``` - `horizontalPodAutoscalerSyncPeriod` — the interval between horizontal pod autoscaler syncs, as a duration string such as `15s`. Shorter intervals make autoscaling more responsive at the cost of more frequent - metric queries. + metric queries. Only configurable on provisioned control plane (PCP) scaling tiers (for example `tier-xl`); + on the Standard tier it is fixed at the default (15s). +- `terminatedPodGCThreshold` — the number of terminated pods that can accumulate before the pod garbage + collector starts deleting them. Lower values reclaim resources sooner; higher values keep more terminated + pods around for inspection. Only configurable on provisioned control plane (PCP) scaling tiers (for example + `tier-xl`); on the Standard tier it is fixed at the default (12500). ## Notes