Skip to content

Commit 649391a

Browse files
authored
Merge pull request kubernetes#84154 from ohsewon/hugepage_per_container
Implement support for setting hugepages limit on container cgroup sandbox.
2 parents 29b09c7 + 463442a commit 649391a

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)