Skip to content

Commit 26f11c4

Browse files
committed
QOS changes for Pod Level resources
1 parent 6db4044 commit 26f11c4

File tree

4 files changed

+291
-119
lines changed

4 files changed

+291
-119
lines changed

pkg/apis/core/helper/qos/qos.go

+70-39
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ package qos
2121
import (
2222
"k8s.io/apimachinery/pkg/api/resource"
2323
"k8s.io/apimachinery/pkg/util/sets"
24+
utilfeature "k8s.io/apiserver/pkg/util/feature"
2425
"k8s.io/kubernetes/pkg/apis/core"
26+
"k8s.io/kubernetes/pkg/features"
2527
)
2628

2729
var supportedQoSComputeResources = sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
@@ -39,6 +41,45 @@ func GetPodQOS(pod *core.Pod) core.PodQOSClass {
3941
return ComputePodQOS(pod)
4042
}
4143

44+
// zeroQuantity represents a resource.Quantity with value "0", used as a baseline
45+
// for resource comparisons.
46+
var zeroQuantity = resource.MustParse("0")
47+
48+
// processResourceList adds non-zero quantities for supported QoS compute resources
49+
// quantities from newList to list.
50+
func processResourceList(list, newList core.ResourceList) {
51+
for name, quantity := range newList {
52+
if !isSupportedQoSComputeResource(name) {
53+
continue
54+
}
55+
if quantity.Cmp(zeroQuantity) == 1 {
56+
delta := quantity.DeepCopy()
57+
if _, exists := list[name]; !exists {
58+
list[name] = delta
59+
} else {
60+
delta.Add(list[name])
61+
list[name] = delta
62+
}
63+
}
64+
}
65+
}
66+
67+
// getQOSResources returns a set of resource names from the provided resource list that:
68+
// 1. Are supported QoS compute resources
69+
// 2. Have quantities greater than zero
70+
func getQOSResources(list core.ResourceList) sets.Set[string] {
71+
qosResources := sets.New[string]()
72+
for name, quantity := range list {
73+
if !isSupportedQoSComputeResource(name) {
74+
continue
75+
}
76+
if quantity.Cmp(zeroQuantity) == 1 {
77+
qosResources.Insert(string(name))
78+
}
79+
}
80+
return qosResources
81+
}
82+
4283
// ComputePodQOS evaluates the list of containers to determine a pod's QoS class. This function is more
4384
// expensive than GetPodQOS which should be used for pods having a non-empty .Status.QOSClass.
4485
// A pod is besteffort if none of its containers have specified any requests or limits.
@@ -48,54 +89,44 @@ func GetPodQOS(pod *core.Pod) core.PodQOSClass {
4889
func ComputePodQOS(pod *core.Pod) core.PodQOSClass {
4990
requests := core.ResourceList{}
5091
limits := core.ResourceList{}
51-
zeroQuantity := resource.MustParse("0")
5292
isGuaranteed := true
53-
// note, ephemeral containers are not considered for QoS as they cannot define resources
54-
allContainers := []core.Container{}
55-
allContainers = append(allContainers, pod.Spec.Containers...)
56-
allContainers = append(allContainers, pod.Spec.InitContainers...)
57-
for _, container := range allContainers {
58-
// process requests
59-
for name, quantity := range container.Resources.Requests {
60-
if !isSupportedQoSComputeResource(name) {
61-
continue
62-
}
63-
if quantity.Cmp(zeroQuantity) == 1 {
64-
delta := quantity.DeepCopy()
65-
if _, exists := requests[name]; !exists {
66-
requests[name] = delta
67-
} else {
68-
delta.Add(requests[name])
69-
requests[name] = delta
70-
}
71-
}
93+
// When pod-level resources are specified, we use them to determine QoS class.
94+
if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) &&
95+
pod.Spec.Resources != nil {
96+
if len(pod.Spec.Resources.Requests) > 0 {
97+
// process requests
98+
processResourceList(requests, pod.Spec.Resources.Requests)
7299
}
73-
// process limits
74-
qosLimitsFound := sets.NewString()
75-
for name, quantity := range container.Resources.Limits {
76-
if !isSupportedQoSComputeResource(name) {
77-
continue
78-
}
79-
if quantity.Cmp(zeroQuantity) == 1 {
80-
qosLimitsFound.Insert(string(name))
81-
delta := quantity.DeepCopy()
82-
if _, exists := limits[name]; !exists {
83-
limits[name] = delta
84-
} else {
85-
delta.Add(limits[name])
86-
limits[name] = delta
87-
}
100+
101+
if len(pod.Spec.Resources.Limits) > 0 {
102+
// process limits
103+
processResourceList(limits, pod.Spec.Resources.Limits)
104+
qosLimitResources := getQOSResources(pod.Spec.Resources.Limits)
105+
if !qosLimitResources.HasAll(string(core.ResourceMemory), string(core.ResourceCPU)) {
106+
isGuaranteed = false
88107
}
89108
}
90-
91-
if !qosLimitsFound.HasAll(string(core.ResourceMemory), string(core.ResourceCPU)) {
92-
isGuaranteed = false
109+
} else {
110+
// note, ephemeral containers are not considered for QoS as they cannot define resources
111+
allContainers := []core.Container{}
112+
allContainers = append(allContainers, pod.Spec.Containers...)
113+
allContainers = append(allContainers, pod.Spec.InitContainers...)
114+
for _, container := range allContainers {
115+
// process requests
116+
processResourceList(requests, container.Resources.Requests)
117+
// process limits
118+
processResourceList(limits, container.Resources.Limits)
119+
qosLimitResources := getQOSResources(container.Resources.Limits)
120+
if !qosLimitResources.HasAll(string(core.ResourceMemory), string(core.ResourceCPU)) {
121+
isGuaranteed = false
122+
}
93123
}
94124
}
125+
95126
if len(requests) == 0 && len(limits) == 0 {
96127
return core.PodQOSBestEffort
97128
}
98-
// Check is requests match limits for all resources.
129+
// Check if requests match limits for all resources.
99130
if isGuaranteed {
100131
for name, req := range requests {
101132
if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 {

pkg/apis/core/v1/helper/qos/qos.go

+71-37
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
v1 "k8s.io/api/core/v1"
2121
"k8s.io/apimachinery/pkg/api/resource"
2222
"k8s.io/apimachinery/pkg/util/sets"
23+
utilfeature "k8s.io/apiserver/pkg/util/feature"
2324
"k8s.io/kubernetes/pkg/apis/core"
25+
"k8s.io/kubernetes/pkg/features"
2426
)
2527

2628
var supportedQoSComputeResources = sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
@@ -41,57 +43,89 @@ func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
4143
return ComputePodQOS(pod)
4244
}
4345

46+
// zeroQuantity represents a resource.Quantity with value "0", used as a baseline
47+
// for resource comparisons.
48+
var zeroQuantity = resource.MustParse("0")
49+
50+
// processResourceList adds non-zero quantities for supported QoS compute resources
51+
// quantities from newList to list.
52+
func processResourceList(list, newList v1.ResourceList) {
53+
for name, quantity := range newList {
54+
if !isSupportedQoSComputeResource(name) {
55+
continue
56+
}
57+
if quantity.Cmp(zeroQuantity) == 1 {
58+
delta := quantity.DeepCopy()
59+
if _, exists := list[name]; !exists {
60+
list[name] = delta
61+
} else {
62+
delta.Add(list[name])
63+
list[name] = delta
64+
}
65+
}
66+
}
67+
}
68+
69+
// getQOSResources returns a set of resource names from the provided resource list that:
70+
// 1. Are supported QoS compute resources
71+
// 2. Have quantities greater than zero
72+
func getQOSResources(list v1.ResourceList) sets.Set[string] {
73+
qosResources := sets.New[string]()
74+
for name, quantity := range list {
75+
if !isSupportedQoSComputeResource(name) {
76+
continue
77+
}
78+
if quantity.Cmp(zeroQuantity) == 1 {
79+
qosResources.Insert(string(name))
80+
}
81+
}
82+
return qosResources
83+
}
84+
4485
// ComputePodQOS evaluates the list of containers to determine a pod's QoS class. This function is more
4586
// expensive than GetPodQOS which should be used for pods having a non-empty .Status.QOSClass.
4687
// A pod is besteffort if none of its containers have specified any requests or limits.
4788
// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal.
4889
// A pod is burstable if limits and requests do not match across all containers.
90+
// TODO(ndixita): Refactor ComputePodQOS into smaller functions to make it more
91+
// readable and maintainable.
4992
func ComputePodQOS(pod *v1.Pod) v1.PodQOSClass {
5093
requests := v1.ResourceList{}
5194
limits := v1.ResourceList{}
52-
zeroQuantity := resource.MustParse("0")
5395
isGuaranteed := true
54-
allContainers := []v1.Container{}
55-
allContainers = append(allContainers, pod.Spec.Containers...)
56-
allContainers = append(allContainers, pod.Spec.InitContainers...)
57-
for _, container := range allContainers {
58-
// process requests
59-
for name, quantity := range container.Resources.Requests {
60-
if !isSupportedQoSComputeResource(name) {
61-
continue
62-
}
63-
if quantity.Cmp(zeroQuantity) == 1 {
64-
delta := quantity.DeepCopy()
65-
if _, exists := requests[name]; !exists {
66-
requests[name] = delta
67-
} else {
68-
delta.Add(requests[name])
69-
requests[name] = delta
70-
}
71-
}
96+
// When pod-level resources are specified, we use them to determine QoS class.
97+
if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) &&
98+
pod.Spec.Resources != nil {
99+
if len(pod.Spec.Resources.Requests) > 0 {
100+
// process requests
101+
processResourceList(requests, pod.Spec.Resources.Requests)
72102
}
73-
// process limits
74-
qosLimitsFound := sets.NewString()
75-
for name, quantity := range container.Resources.Limits {
76-
if !isSupportedQoSComputeResource(name) {
77-
continue
78-
}
79-
if quantity.Cmp(zeroQuantity) == 1 {
80-
qosLimitsFound.Insert(string(name))
81-
delta := quantity.DeepCopy()
82-
if _, exists := limits[name]; !exists {
83-
limits[name] = delta
84-
} else {
85-
delta.Add(limits[name])
86-
limits[name] = delta
87-
}
103+
104+
if len(pod.Spec.Resources.Limits) > 0 {
105+
// process limits
106+
processResourceList(limits, pod.Spec.Resources.Limits)
107+
qosLimitResources := getQOSResources(pod.Spec.Resources.Limits)
108+
if !qosLimitResources.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
109+
isGuaranteed = false
88110
}
89111
}
90-
91-
if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
92-
isGuaranteed = false
112+
} else {
113+
// note, ephemeral containers are not considered for QoS as they cannot define resources
114+
allContainers := []v1.Container{}
115+
allContainers = append(allContainers, pod.Spec.Containers...)
116+
allContainers = append(allContainers, pod.Spec.InitContainers...)
117+
for _, container := range allContainers {
118+
// process requests
119+
processResourceList(requests, container.Resources.Requests)
120+
// process limits
121+
processResourceList(limits, container.Resources.Limits)
122+
qosLimitResources := getQOSResources(container.Resources.Limits)
123+
if !qosLimitResources.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
124+
isGuaranteed = false
125+
}
93126
}
94127
}
128+
95129
if len(requests) == 0 && len(limits) == 0 {
96130
return v1.PodQOSBestEffort
97131
}

0 commit comments

Comments
 (0)