Skip to content

Commit d7f488b

Browse files
committed
API changes for Pod Level Resources
1. Add Resources struct to PodSpec struct in both external and internal API packages 2. Adding feature gate and logic for dropping disabled fields for Pod Level Resources KEP: enhancements/keps/sig-node/2837-pod-level-resource-spec
1 parent 210deea commit d7f488b

File tree

6 files changed

+213
-0
lines changed

6 files changed

+213
-0
lines changed

pkg/api/pod/util.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ func dropDisabledFields(
621621
}
622622
}
623623

624+
dropDisabledPodLevelResources(podSpec, oldPodSpec)
624625
dropDisabledProcMountField(podSpec, oldPodSpec)
625626

626627
dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec)
@@ -674,6 +675,14 @@ func dropDisabledFields(
674675
dropSELinuxChangePolicy(podSpec, oldPodSpec)
675676
}
676677

678+
func dropDisabledPodLevelResources(podSpec, oldPodSpec *api.PodSpec) {
679+
// If the feature is disabled and not in use, drop Resources at the pod-level
680+
// from PodSpec.
681+
if !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) && !podLevelResourcesInUse(oldPodSpec) {
682+
podSpec.Resources = nil
683+
}
684+
}
685+
677686
func dropPodLifecycleSleepAction(podSpec, oldPodSpec *api.PodSpec) {
678687
if utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepAction) || podLifecycleSleepActionInUse(oldPodSpec) {
679688
return
@@ -1050,6 +1059,28 @@ func supplementalGroupsPolicyInUse(podSpec *api.PodSpec) bool {
10501059
return false
10511060
}
10521061

1062+
// podLevelResourcesInUse returns true if pod-spec is non-nil and Resources field at
1063+
// pod-level has non-empty Requests or Limits.
1064+
func podLevelResourcesInUse(podSpec *api.PodSpec) bool {
1065+
if podSpec == nil {
1066+
return false
1067+
}
1068+
1069+
if podSpec.Resources == nil {
1070+
return false
1071+
}
1072+
1073+
if len(podSpec.Resources.Requests) > 0 {
1074+
return true
1075+
}
1076+
1077+
if len(podSpec.Resources.Limits) > 0 {
1078+
return true
1079+
}
1080+
1081+
return false
1082+
}
1083+
10531084
// inPlacePodVerticalScalingInUse returns true if pod spec is non-nil and ResizePolicy is set
10541085
func inPlacePodVerticalScalingInUse(podSpec *api.PodSpec) bool {
10551086
if podSpec == nil {

pkg/api/pod/util_test.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2703,6 +2703,149 @@ func TestDropInPlacePodVerticalScaling(t *testing.T) {
27032703
}
27042704
}
27052705

2706+
func TestDropPodLevelResources(t *testing.T) {
2707+
containers := []api.Container{
2708+
{
2709+
Name: "c1",
2710+
Image: "image",
2711+
Resources: api.ResourceRequirements{
2712+
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
2713+
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
2714+
},
2715+
},
2716+
}
2717+
podWithPodLevelResources := func() *api.Pod {
2718+
return &api.Pod{
2719+
Spec: api.PodSpec{
2720+
Resources: &api.ResourceRequirements{
2721+
Requests: api.ResourceList{
2722+
api.ResourceCPU: resource.MustParse("100m"),
2723+
api.ResourceMemory: resource.MustParse("50Gi"),
2724+
},
2725+
Limits: api.ResourceList{
2726+
api.ResourceCPU: resource.MustParse("100m"),
2727+
api.ResourceMemory: resource.MustParse("50Gi"),
2728+
},
2729+
},
2730+
Containers: containers,
2731+
},
2732+
}
2733+
}
2734+
2735+
podWithoutPodLevelResources := func() *api.Pod {
2736+
return &api.Pod{
2737+
Spec: api.PodSpec{
2738+
Containers: containers,
2739+
},
2740+
}
2741+
}
2742+
2743+
podInfo := []struct {
2744+
description string
2745+
hasPodLevelResources bool
2746+
pod func() *api.Pod
2747+
}{
2748+
{
2749+
description: "has pod-level resources",
2750+
hasPodLevelResources: true,
2751+
pod: podWithPodLevelResources,
2752+
},
2753+
{
2754+
description: "does not have pod-level resources",
2755+
hasPodLevelResources: false,
2756+
pod: podWithoutPodLevelResources,
2757+
},
2758+
{
2759+
description: "is nil",
2760+
hasPodLevelResources: false,
2761+
pod: func() *api.Pod { return nil },
2762+
},
2763+
{
2764+
description: "is empty struct",
2765+
hasPodLevelResources: false,
2766+
// refactor to generalize and use podWithPodLevelResources()
2767+
pod: func() *api.Pod {
2768+
return &api.Pod{
2769+
Spec: api.PodSpec{
2770+
Resources: &api.ResourceRequirements{},
2771+
Containers: containers,
2772+
},
2773+
}
2774+
},
2775+
},
2776+
{
2777+
description: "is empty Requests list",
2778+
hasPodLevelResources: false,
2779+
pod: func() *api.Pod {
2780+
return &api.Pod{
2781+
Spec: api.PodSpec{Resources: &api.ResourceRequirements{
2782+
Requests: api.ResourceList{},
2783+
}}}
2784+
},
2785+
},
2786+
{
2787+
description: "is empty Limits list",
2788+
hasPodLevelResources: false,
2789+
pod: func() *api.Pod {
2790+
return &api.Pod{
2791+
Spec: api.PodSpec{Resources: &api.ResourceRequirements{
2792+
Limits: api.ResourceList{},
2793+
}}}
2794+
},
2795+
},
2796+
}
2797+
2798+
for _, enabled := range []bool{true, false} {
2799+
for _, oldPodInfo := range podInfo {
2800+
for _, newPodInfo := range podInfo {
2801+
oldPodHasPodLevelResources, oldPod := oldPodInfo.hasPodLevelResources, oldPodInfo.pod()
2802+
newPodHasPodLevelResources, newPod := newPodInfo.hasPodLevelResources, newPodInfo.pod()
2803+
if newPod == nil {
2804+
continue
2805+
}
2806+
2807+
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
2808+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, enabled)
2809+
2810+
var oldPodSpec *api.PodSpec
2811+
if oldPod != nil {
2812+
oldPodSpec = &oldPod.Spec
2813+
}
2814+
2815+
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
2816+
2817+
// old pod should never be changed
2818+
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
2819+
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
2820+
}
2821+
2822+
switch {
2823+
case enabled || oldPodHasPodLevelResources:
2824+
// new pod shouldn't change if feature enabled or if old pod has
2825+
// any pod level resources
2826+
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
2827+
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
2828+
}
2829+
case newPodHasPodLevelResources:
2830+
// new pod should be changed
2831+
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
2832+
t.Errorf("new pod was not changed")
2833+
}
2834+
// new pod should not have any pod-level resources
2835+
if !reflect.DeepEqual(newPod, podWithoutPodLevelResources()) {
2836+
t.Errorf("new pod has pod-level resources: %v", cmp.Diff(newPod, podWithoutPodLevelResources()))
2837+
}
2838+
default:
2839+
if newPod.Spec.Resources != nil {
2840+
t.Errorf("expected nil, got: %v", newPod.Spec.Resources)
2841+
}
2842+
}
2843+
})
2844+
}
2845+
}
2846+
}
2847+
}
2848+
27062849
func TestDropSidecarContainers(t *testing.T) {
27072850
containerRestartPolicyAlways := api.ContainerRestartPolicyAlways
27082851

pkg/apis/core/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,6 +3609,20 @@ type PodSpec struct {
36093609
// +featureGate=DynamicResourceAllocation
36103610
// +optional
36113611
ResourceClaims []PodResourceClaim
3612+
// Resources is the total amount of CPU and Memory resources required by all
3613+
// containers in the pod. It supports specifying Requests and Limits for
3614+
// "cpu" and "memory" resource names only. ResourceClaims are not supported.
3615+
//
3616+
// This field enables fine-grained control over resource allocation for the
3617+
// entire pod, allowing resource sharing among containers in a pod.
3618+
// TODO: For beta graduation, expand this comment with a detailed explanation.
3619+
//
3620+
// This is an alpha field and requires enabling the PodLevelResources feature
3621+
// gate.
3622+
//
3623+
// +featureGate=PodLevelResources
3624+
// +optional
3625+
Resources *ResourceRequirements
36123626
}
36133627

36143628
// PodResourceClaim references exactly one ResourceClaim through a ClaimSource.

pkg/features/kube_features.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,13 @@ const (
835835
// Enables external service account JWT signing and key management.
836836
// If enabled, it allows passing --service-account-signing-endpoint flag to configure external signer.
837837
ExternalServiceAccountTokenSigner featuregate.Feature = "ExternalServiceAccountTokenSigner"
838+
839+
// owner: @ndixita
840+
// key: https://kep.k8s.io/2837
841+
// alpha: 1.32
842+
//
843+
// Enables specifying resources at pod-level.
844+
PodLevelResources featuregate.Feature = "PodLevelResources"
838845
)
839846

840847
func init() {

pkg/features/versioned_kube_features.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
567567
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.35
568568
},
569569

570+
PodLevelResources: {
571+
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
572+
},
573+
570574
PodLifecycleSleepAction: {
571575
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
572576
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},

staging/src/k8s.io/api/core/v1/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4080,6 +4080,20 @@ type PodSpec struct {
40804080
// +featureGate=DynamicResourceAllocation
40814081
// +optional
40824082
ResourceClaims []PodResourceClaim `json:"resourceClaims,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,39,rep,name=resourceClaims"`
4083+
// Resources is the total amount of CPU and Memory resources required by all
4084+
// containers in the pod. It supports specifying Requests and Limits for
4085+
// "cpu" and "memory" resource names only. ResourceClaims are not supported.
4086+
//
4087+
// This field enables fine-grained control over resource allocation for the
4088+
// entire pod, allowing resource sharing among containers in a pod.
4089+
// TODO: For beta graduation, expand this comment with a detailed explanation.
4090+
//
4091+
// This is an alpha field and requires enabling the PodLevelResources feature
4092+
// gate.
4093+
//
4094+
// +featureGate=PodLevelResources
4095+
// +optional
4096+
Resources *ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,40,opt,name=resources"`
40834097
}
40844098

40854099
// PodResourceClaim references exactly one ResourceClaim, either directly

0 commit comments

Comments
 (0)