Skip to content

Commit 80ee072

Browse files
author
Eric Ernst
committed
pod-overhead: utilize pod overhead for cgroup sizing, eviction handling
Pod and burstable QoS cgroups should take overhead of running a sandbox into account if the PodOverhead feature is enabled. These helper functions are utilized by Kubelet for sizing the pod and burstable QoS cgroups. Pod overhead is added to resource requests, regardless of the initial request values. A particular resource pod overhead is only added to a resource limit if a non-zero limit already existed. This commit updates eviction handling to also take Pod Overhead into account (if the feature is enabled). Signed-off-by: Eric Ernst <[email protected]>
1 parent f137a9c commit 80ee072

File tree

3 files changed

+163
-13
lines changed

3 files changed

+163
-13
lines changed

pkg/api/v1/resource/BUILD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ go_test(
1111
srcs = ["helpers_test.go"],
1212
embed = [":go_default_library"],
1313
deps = [
14+
"//pkg/features:go_default_library",
1415
"//staging/src/k8s.io/api/core/v1:go_default_library",
16+
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
1517
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
18+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
19+
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
1620
"//vendor/github.com/stretchr/testify/assert:go_default_library",
1721
],
1822
)
@@ -22,8 +26,10 @@ go_library(
2226
srcs = ["helpers.go"],
2327
importpath = "k8s.io/kubernetes/pkg/api/v1/resource",
2428
deps = [
29+
"//pkg/features:go_default_library",
2530
"//staging/src/k8s.io/api/core/v1:go_default_library",
2631
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
32+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
2733
],
2834
)
2935

pkg/api/v1/resource/helpers.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import (
2828
)
2929

3030
// addResourceList adds the resources in newList to list
31-
func addResourceList(list, new v1.ResourceList) {
32-
for name, quantity := range new {
31+
func addResourceList(list, newList v1.ResourceList) {
32+
for name, quantity := range newList {
3333
if value, ok := list[name]; !ok {
3434
list[name] = *quantity.Copy()
3535
} else {
@@ -55,7 +55,9 @@ func maxResourceList(list, new v1.ResourceList) {
5555
}
5656

5757
// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
58-
// containers of the pod.
58+
// containers of the pod. If PodOverhead feature is enabled, pod overhead is added to the
59+
// total container resource requests and to the total container limits which have a
60+
// non-zero quantity.
5961
func PodRequestsAndLimits(pod *v1.Pod) (reqs, limits v1.ResourceList) {
6062
reqs, limits = v1.ResourceList{}, v1.ResourceList{}
6163
for _, container := range pod.Spec.Containers {
@@ -67,13 +69,35 @@ func PodRequestsAndLimits(pod *v1.Pod) (reqs, limits v1.ResourceList) {
6769
maxResourceList(reqs, container.Resources.Requests)
6870
maxResourceList(limits, container.Resources.Limits)
6971
}
72+
73+
// if PodOverhead feature is supported, add overhead for running a pod
74+
// to the sum of reqeuests and to non-zero limits:
75+
if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
76+
addResourceList(reqs, pod.Spec.Overhead)
77+
78+
for name, quantity := range pod.Spec.Overhead {
79+
if value, ok := limits[name]; ok && !value.IsZero() {
80+
value.Add(quantity)
81+
limits[name] = value
82+
}
83+
}
84+
}
85+
7086
return
7187
}
7288

7389
// GetResourceRequestQuantity finds and returns the request quantity for a specific resource.
7490
func GetResourceRequestQuantity(pod *v1.Pod, resourceName v1.ResourceName) resource.Quantity {
91+
requestQuantity := resource.Quantity{}
7592

76-
requestQuantity := resource.Quantity{Format: resource.BinarySI}
93+
switch resourceName {
94+
case v1.ResourceCPU:
95+
requestQuantity = resource.Quantity{Format: resource.DecimalSI}
96+
case v1.ResourceMemory, v1.ResourceStorage, v1.ResourceEphemeralStorage:
97+
requestQuantity = resource.Quantity{Format: resource.BinarySI}
98+
default:
99+
requestQuantity = resource.Quantity{Format: resource.DecimalSI}
100+
}
77101

78102
if resourceName == v1.ResourceEphemeralStorage && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
79103
// if the local storage capacity isolation feature gate is disabled, pods request 0 disk
@@ -89,11 +113,19 @@ func GetResourceRequestQuantity(pod *v1.Pod, resourceName v1.ResourceName) resou
89113
for _, container := range pod.Spec.InitContainers {
90114
if rQuantity, ok := container.Resources.Requests[resourceName]; ok {
91115
if requestQuantity.Cmp(rQuantity) < 0 {
92-
requestQuantity = rQuantity
116+
requestQuantity = rQuantity.DeepCopy()
93117
}
94118
}
95119
}
96120

121+
// if PodOverhead feature is supported, add overhead for running a pod
122+
// to the total requests if the resource total is non-zero
123+
if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
124+
if podOverhead, ok := pod.Spec.Overhead[resourceName]; ok && !requestQuantity.IsZero() {
125+
requestQuantity.Add(podOverhead)
126+
}
127+
}
128+
97129
return requestQuantity
98130
}
99131

@@ -107,10 +139,9 @@ func GetResourceRequest(pod *v1.Pod, resource v1.ResourceName) int64 {
107139

108140
if resource == v1.ResourceCPU {
109141
return requestQuantity.MilliValue()
110-
} else {
111-
return requestQuantity.Value()
112142
}
113143

144+
return requestQuantity.Value()
114145
}
115146

116147
// ExtractResourceValueByContainerName extracts the value of a resource

pkg/api/v1/resource/helpers_test.go

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import (
2222
"github.com/stretchr/testify/assert"
2323

2424
"k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/equality"
2526
"k8s.io/apimachinery/pkg/api/resource"
27+
utilfeature "k8s.io/apiserver/pkg/util/feature"
28+
featuregatetesting "k8s.io/component-base/featuregate/testing"
29+
"k8s.io/kubernetes/pkg/features"
2630
)
2731

2832
func TestResourceHelpers(t *testing.T) {
@@ -64,27 +68,53 @@ func TestDefaultResourceHelpers(t *testing.T) {
6468
}
6569

6670
func TestGetResourceRequest(t *testing.T) {
71+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)()
72+
6773
cases := []struct {
6874
pod *v1.Pod
69-
res v1.ResourceName
75+
cName string
76+
resourceName v1.ResourceName
7077
expectedValue int64
71-
expectedError error
7278
}{
7379
{
7480
pod: getPod("foo", podResources{cpuRequest: "9"}),
75-
res: v1.ResourceCPU,
81+
resourceName: v1.ResourceCPU,
7682
expectedValue: 9000,
7783
},
7884
{
7985
pod: getPod("foo", podResources{memoryRequest: "90Mi"}),
80-
res: v1.ResourceMemory,
86+
resourceName: v1.ResourceMemory,
8187
expectedValue: 94371840,
8288
},
89+
{
90+
cName: "just-overhead for cpu",
91+
pod: getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}),
92+
resourceName: v1.ResourceCPU,
93+
expectedValue: 0,
94+
},
95+
{
96+
cName: "just-overhead for memory",
97+
pod: getPod("foo", podResources{memoryOverhead: "5"}),
98+
resourceName: v1.ResourceMemory,
99+
expectedValue: 0,
100+
},
101+
{
102+
cName: "cpu overhead and req",
103+
pod: getPod("foo", podResources{cpuRequest: "2", cpuOverhead: "5", memoryOverhead: "5"}),
104+
resourceName: v1.ResourceCPU,
105+
expectedValue: 7000,
106+
},
107+
{
108+
cName: "mem overhead and req",
109+
pod: getPod("foo", podResources{cpuRequest: "2", memoryRequest: "1024", cpuOverhead: "5", memoryOverhead: "5"}),
110+
resourceName: v1.ResourceMemory,
111+
expectedValue: 1029,
112+
},
83113
}
84114
as := assert.New(t)
85115
for idx, tc := range cases {
86-
actual := GetResourceRequest(tc.pod, tc.res)
87-
as.Equal(actual, tc.expectedValue, "expected test case [%d] to return %q; got %q instead", idx, tc.expectedValue, actual)
116+
actual := GetResourceRequest(tc.pod, tc.resourceName)
117+
as.Equal(actual, tc.expectedValue, "expected test case [%d] %v: to return %q; got %q instead", idx, tc.cName, tc.expectedValue, actual)
88118
}
89119
}
90120

@@ -242,6 +272,78 @@ func TestExtractResourceValue(t *testing.T) {
242272
}
243273
}
244274

275+
func TestPodRequestsAndLimits(t *testing.T) {
276+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)()
277+
278+
cases := []struct {
279+
pod *v1.Pod
280+
cName string
281+
expectedRequests v1.ResourceList
282+
expectedLimits v1.ResourceList
283+
}{
284+
{
285+
cName: "just-limit-no-overhead",
286+
pod: getPod("foo", podResources{cpuLimit: "9"}),
287+
expectedRequests: v1.ResourceList{},
288+
expectedLimits: v1.ResourceList{
289+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
290+
},
291+
},
292+
{
293+
cName: "just-overhead",
294+
pod: getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}),
295+
expectedRequests: v1.ResourceList{
296+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"),
297+
v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
298+
},
299+
expectedLimits: v1.ResourceList{},
300+
},
301+
{
302+
cName: "req-and-overhead",
303+
pod: getPod("foo", podResources{cpuRequest: "1", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}),
304+
expectedRequests: v1.ResourceList{
305+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"),
306+
v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
307+
},
308+
expectedLimits: v1.ResourceList{},
309+
},
310+
{
311+
cName: "all-req-lim-and-overhead",
312+
pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", memoryLimit: "12", cpuOverhead: "5", memoryOverhead: "5"}),
313+
expectedRequests: v1.ResourceList{
314+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"),
315+
v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
316+
},
317+
expectedLimits: v1.ResourceList{
318+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"),
319+
v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"),
320+
},
321+
},
322+
{
323+
cName: "req-some-lim-and-overhead",
324+
pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}),
325+
expectedRequests: v1.ResourceList{
326+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"),
327+
v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
328+
},
329+
expectedLimits: v1.ResourceList{
330+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"),
331+
},
332+
},
333+
}
334+
for idx, tc := range cases {
335+
resRequests, resLimits := PodRequestsAndLimits(tc.pod)
336+
337+
if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) {
338+
t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedRequests, resRequests)
339+
}
340+
341+
if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) {
342+
t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedLimits, resLimits)
343+
}
344+
}
345+
}
346+
245347
type podResources struct {
246348
cpuRequest, cpuLimit, memoryRequest, memoryLimit, cpuOverhead, memoryOverhead string
247349
}
@@ -251,6 +353,9 @@ func getPod(cname string, resources podResources) *v1.Pod {
251353
Limits: make(v1.ResourceList),
252354
Requests: make(v1.ResourceList),
253355
}
356+
357+
overhead := make(v1.ResourceList)
358+
254359
if resources.cpuLimit != "" {
255360
r.Limits[v1.ResourceCPU] = resource.MustParse(resources.cpuLimit)
256361
}
@@ -263,6 +368,13 @@ func getPod(cname string, resources podResources) *v1.Pod {
263368
if resources.memoryRequest != "" {
264369
r.Requests[v1.ResourceMemory] = resource.MustParse(resources.memoryRequest)
265370
}
371+
if resources.cpuOverhead != "" {
372+
overhead[v1.ResourceCPU] = resource.MustParse(resources.cpuOverhead)
373+
}
374+
if resources.memoryOverhead != "" {
375+
overhead[v1.ResourceMemory] = resource.MustParse(resources.memoryOverhead)
376+
}
377+
266378
return &v1.Pod{
267379
Spec: v1.PodSpec{
268380
Containers: []v1.Container{
@@ -277,6 +389,7 @@ func getPod(cname string, resources podResources) *v1.Pod {
277389
Resources: r,
278390
},
279391
},
392+
Overhead: overhead,
280393
},
281394
}
282395
}

0 commit comments

Comments
 (0)