Skip to content

Commit 463442a

Browse files
committed
Update container hugepage limit when creating the container
Unit test for updating container hugepage limit Add warning message about ignoring case. Update error handling about hugepage size requirements Signed-off-by: sewon.oh <[email protected]>
1 parent e2f529a commit 463442a

File tree

6 files changed

+293
-6
lines changed

6 files changed

+293
-6
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, erro
8585
return resource.ParseQuantity(pageSize)
8686
}
8787

88+
// HugePageUnitSizeFromByteSize returns hugepage size has the format.
89+
// `size` must be guaranteed to divisible into the largest units that can be expressed.
90+
// <size><unit-prefix>B (1024 = "1KB", 1048576 = "1MB", etc).
91+
func HugePageUnitSizeFromByteSize(size int64) (string, error) {
92+
// hugePageSizeUnitList is borrowed from opencontainers/runc/libcontainer/cgroups/utils.go
93+
var hugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
94+
idx := 0
95+
len := len(hugePageSizeUnitList) - 1
96+
for size%1024 == 0 && idx < len {
97+
size /= 1024
98+
idx++
99+
}
100+
if size > 1024 && idx < len {
101+
return "", fmt.Errorf("size: %d%s must be guaranteed to divisible into the largest units", size, hugePageSizeUnitList[idx])
102+
}
103+
return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil
104+
}
105+
88106
// IsOvercommitAllowed returns true if the resource is in the default
89107
// namespace and is not hugepages.
90108
func IsOvercommitAllowed(name v1.ResourceName) bool {

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"reflect"
2222
"testing"
2323

24-
"k8s.io/api/core/v1"
24+
v1 "k8s.io/api/core/v1"
2525
apiequality "k8s.io/apimachinery/pkg/api/equality"
2626
"k8s.io/apimachinery/pkg/api/resource"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -1398,3 +1398,47 @@ func TestNodeSelectorRequirementKeyExistsInNodeSelectorTerms(t *testing.T) {
13981398
}
13991399
}
14001400
}
1401+
1402+
func TestHugePageUnitSizeFromByteSize(t *testing.T) {
1403+
tests := []struct {
1404+
size int64
1405+
expected string
1406+
wantErr bool
1407+
}{
1408+
{
1409+
size: 1024,
1410+
expected: "1KB",
1411+
wantErr: false,
1412+
},
1413+
{
1414+
size: 33554432,
1415+
expected: "32MB",
1416+
wantErr: false,
1417+
},
1418+
{
1419+
size: 3221225472,
1420+
expected: "3GB",
1421+
wantErr: false,
1422+
},
1423+
{
1424+
size: 1024 * 1024 * 1023 * 3,
1425+
expected: "3069MB",
1426+
wantErr: true,
1427+
},
1428+
}
1429+
for _, test := range tests {
1430+
size := test.size
1431+
result, err := HugePageUnitSizeFromByteSize(size)
1432+
if err != nil {
1433+
if test.wantErr {
1434+
t.Logf("HugePageUnitSizeFromByteSize() expected error = %v", err)
1435+
} else {
1436+
t.Errorf("HugePageUnitSizeFromByteSize() error = %v, wantErr %v", err, test.wantErr)
1437+
}
1438+
continue
1439+
}
1440+
if test.expected != result {
1441+
t.Errorf("HugePageUnitSizeFromByteSize() expected %v but got %v", test.expected, result)
1442+
}
1443+
}
1444+
}

pkg/kubelet/cm/cgroup_manager_linux.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import (
2424
"strings"
2525
"time"
2626

27-
units "github.com/docker/go-units"
2827
libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups"
2928
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
3029
cgroupsystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd"
3130
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
3231
"k8s.io/klog"
32+
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
3333

3434
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3535
"k8s.io/apimachinery/pkg/util/sets"
@@ -387,7 +387,11 @@ func (m *cgroupManagerImpl) toResources(resourceConfig *ResourceConfig) *libcont
387387
// for each page size enumerated, set that value
388388
pageSizes := sets.NewString()
389389
for pageSize, limit := range resourceConfig.HugePageLimit {
390-
sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, libcontainercgroups.HugePageSizeUnitList)
390+
sizeString, err := v1helper.HugePageUnitSizeFromByteSize(pageSize)
391+
if err != nil {
392+
klog.Warningf("pageSize is invalid: %v", err)
393+
continue
394+
}
391395
resources.HugetlbLimit = append(resources.HugetlbLimit, &libcontainerconfigs.HugepageLimit{
392396
Pagesize: sizeString,
393397
Limit: uint64(limit),

pkg/kubelet/kuberuntime/BUILD

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,14 @@ go_library(
7373
"//vendor/k8s.io/klog:go_default_library",
7474
] + select({
7575
"@io_bazel_rules_go//go/platform:android": [
76+
"//pkg/apis/core/v1/helper:go_default_library",
7677
"//pkg/kubelet/qos:go_default_library",
78+
"//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library",
7779
],
7880
"@io_bazel_rules_go//go/platform:linux": [
81+
"//pkg/apis/core/v1/helper:go_default_library",
7982
"//pkg/kubelet/qos:go_default_library",
83+
"//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library",
8084
],
8185
"@io_bazel_rules_go//go/platform:windows": [
8286
"//pkg/kubelet/apis:go_default_library",
@@ -130,7 +134,17 @@ go_test(
130134
"//vendor/github.com/stretchr/testify/assert:go_default_library",
131135
"//vendor/github.com/stretchr/testify/require:go_default_library",
132136
"//vendor/k8s.io/utils/pointer:go_default_library",
133-
],
137+
] + select({
138+
"@io_bazel_rules_go//go/platform:android": [
139+
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
140+
"//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library",
141+
],
142+
"@io_bazel_rules_go//go/platform:linux": [
143+
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
144+
"//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library",
145+
],
146+
"//conditions:default": [],
147+
}),
134148
)
135149

136150
filegroup(

pkg/kubelet/kuberuntime/kuberuntime_container_linux.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ package kuberuntime
2121
import (
2222
"time"
2323

24-
"k8s.io/api/core/v1"
24+
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
25+
v1 "k8s.io/api/core/v1"
2526
utilfeature "k8s.io/apiserver/pkg/util/feature"
2627
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
28+
"k8s.io/klog"
29+
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
2730
kubefeatures "k8s.io/kubernetes/pkg/features"
2831
"k8s.io/kubernetes/pkg/kubelet/qos"
2932
)
@@ -78,5 +81,48 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.C
7881
lc.Resources.CpuPeriod = cpuPeriod
7982
}
8083

84+
lc.Resources.HugepageLimits = GetHugepageLimitsFromResources(container.Resources)
85+
8186
return lc
8287
}
88+
89+
// GetHugepageLimitsFromResources returns limits of each hugepages from resources.
90+
func GetHugepageLimitsFromResources(resources v1.ResourceRequirements) []*runtimeapi.HugepageLimit {
91+
var hugepageLimits []*runtimeapi.HugepageLimit
92+
93+
// For each page size, limit to 0.
94+
for _, pageSize := range cgroupfs.HugePageSizes {
95+
hugepageLimits = append(hugepageLimits, &runtimeapi.HugepageLimit{
96+
PageSize: pageSize,
97+
Limit: uint64(0),
98+
})
99+
}
100+
101+
requiredHugepageLimits := map[string]uint64{}
102+
for resourceObj, amountObj := range resources.Limits {
103+
if !v1helper.IsHugePageResourceName(resourceObj) {
104+
continue
105+
}
106+
107+
pageSize, err := v1helper.HugePageSizeFromResourceName(resourceObj)
108+
if err != nil {
109+
klog.Warningf("Failed to get hugepage size from resource name: %v", err)
110+
continue
111+
}
112+
113+
sizeString, err := v1helper.HugePageUnitSizeFromByteSize(pageSize.Value())
114+
if err != nil {
115+
klog.Warningf("pageSize is invalid: %v", err)
116+
continue
117+
}
118+
requiredHugepageLimits[sizeString] = uint64(amountObj.Value())
119+
}
120+
121+
for _, hugepageLimit := range hugepageLimits {
122+
if limit, exists := requiredHugepageLimits[hugepageLimit.PageSize]; exists {
123+
hugepageLimit.Limit = limit
124+
}
125+
}
126+
127+
return hugepageLimits
128+
}

pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ limitations under the License.
1919
package kuberuntime
2020

2121
import (
22+
"reflect"
2223
"testing"
2324

25+
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
2426
"github.com/stretchr/testify/assert"
25-
"k8s.io/api/core/v1"
27+
v1 "k8s.io/api/core/v1"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2729
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
30+
31+
"k8s.io/apimachinery/pkg/api/resource"
2832
)
2933

3034
func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerIndex int) *runtimeapi.ContainerConfig {
@@ -135,3 +139,160 @@ func TestGenerateContainerConfig(t *testing.T) {
135139
_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{})
136140
assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username")
137141
}
142+
143+
func TestGetHugepageLimitsFromResources(t *testing.T) {
144+
var baseHugepage []*runtimeapi.HugepageLimit
145+
146+
// For each page size, limit to 0.
147+
for _, pageSize := range cgroupfs.HugePageSizes {
148+
baseHugepage = append(baseHugepage, &runtimeapi.HugepageLimit{
149+
PageSize: pageSize,
150+
Limit: uint64(0),
151+
})
152+
}
153+
154+
tests := []struct {
155+
name string
156+
resources v1.ResourceRequirements
157+
expected []*runtimeapi.HugepageLimit
158+
}{
159+
{
160+
name: "Success2MB",
161+
resources: v1.ResourceRequirements{
162+
Limits: v1.ResourceList{
163+
"hugepages-2Mi": resource.MustParse("2Mi"),
164+
},
165+
},
166+
expected: []*runtimeapi.HugepageLimit{
167+
{
168+
PageSize: "2MB",
169+
Limit: 2097152,
170+
},
171+
},
172+
},
173+
{
174+
name: "Success1GB",
175+
resources: v1.ResourceRequirements{
176+
Limits: v1.ResourceList{
177+
"hugepages-1Gi": resource.MustParse("2Gi"),
178+
},
179+
},
180+
expected: []*runtimeapi.HugepageLimit{
181+
{
182+
PageSize: "1GB",
183+
Limit: 2147483648,
184+
},
185+
},
186+
},
187+
{
188+
name: "Skip2MB",
189+
resources: v1.ResourceRequirements{
190+
Limits: v1.ResourceList{
191+
"hugepages-2MB": resource.MustParse("2Mi"),
192+
},
193+
},
194+
expected: []*runtimeapi.HugepageLimit{
195+
{
196+
PageSize: "2MB",
197+
Limit: 0,
198+
},
199+
},
200+
},
201+
{
202+
name: "Skip1GB",
203+
resources: v1.ResourceRequirements{
204+
Limits: v1.ResourceList{
205+
"hugepages-1GB": resource.MustParse("2Gi"),
206+
},
207+
},
208+
expected: []*runtimeapi.HugepageLimit{
209+
{
210+
PageSize: "1GB",
211+
Limit: 0,
212+
},
213+
},
214+
},
215+
{
216+
name: "Success2MBand1GB",
217+
resources: v1.ResourceRequirements{
218+
Limits: v1.ResourceList{
219+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
220+
"hugepages-2Mi": resource.MustParse("2Mi"),
221+
"hugepages-1Gi": resource.MustParse("2Gi"),
222+
},
223+
},
224+
expected: []*runtimeapi.HugepageLimit{
225+
{
226+
PageSize: "2MB",
227+
Limit: 2097152,
228+
},
229+
{
230+
PageSize: "1GB",
231+
Limit: 2147483648,
232+
},
233+
},
234+
},
235+
{
236+
name: "Skip2MBand1GB",
237+
resources: v1.ResourceRequirements{
238+
Limits: v1.ResourceList{
239+
v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
240+
"hugepages-2MB": resource.MustParse("2Mi"),
241+
"hugepages-1GB": resource.MustParse("2Gi"),
242+
},
243+
},
244+
expected: []*runtimeapi.HugepageLimit{
245+
{
246+
PageSize: "2MB",
247+
Limit: 0,
248+
},
249+
{
250+
PageSize: "1GB",
251+
Limit: 0,
252+
},
253+
},
254+
},
255+
}
256+
257+
for _, test := range tests {
258+
// Validate if machine supports hugepage size that used in test case.
259+
machineHugepageSupport := true
260+
for _, hugepageLimit := range test.expected {
261+
hugepageSupport := false
262+
for _, pageSize := range cgroupfs.HugePageSizes {
263+
if pageSize == hugepageLimit.PageSize {
264+
hugepageSupport = true
265+
break
266+
}
267+
}
268+
269+
if !hugepageSupport {
270+
machineHugepageSupport = false
271+
break
272+
}
273+
}
274+
275+
// Case of machine can't support hugepage size
276+
if !machineHugepageSupport {
277+
continue
278+
}
279+
280+
expectedHugepages := baseHugepage
281+
for _, hugepage := range test.expected {
282+
for _, expectedHugepage := range expectedHugepages {
283+
if expectedHugepage.PageSize == hugepage.PageSize {
284+
expectedHugepage.Limit = hugepage.Limit
285+
}
286+
}
287+
}
288+
289+
results := GetHugepageLimitsFromResources(test.resources)
290+
if !reflect.DeepEqual(expectedHugepages, results) {
291+
t.Errorf("%s test failed. Expected %v but got %v", test.name, expectedHugepages, results)
292+
}
293+
294+
for _, hugepage := range baseHugepage {
295+
hugepage.Limit = uint64(0)
296+
}
297+
}
298+
}

0 commit comments

Comments
 (0)