Downward API hugepages

This commit is contained in:
Derek Carr
2020-11-06 14:22:53 -05:00
parent 26f09b77a8
commit 45bd6cb186
29 changed files with 590 additions and 238 deletions

View File

@@ -12,9 +12,13 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/api/pod",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/features:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)

View File

@@ -21,8 +21,13 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/apis/core/validation"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
)
@@ -283,6 +288,91 @@ func UpdatePodCondition(status *api.PodStatus, condition *api.PodCondition) bool
return !isEqual
}
// usesHugePagesInProjectedVolume returns true if hugepages are used in downward api for volume
func usesHugePagesInProjectedVolume(podSpec *api.PodSpec) bool {
// determine if any container is using hugepages in downward api volume
for _, volumeSource := range podSpec.Volumes {
if volumeSource.DownwardAPI != nil {
for _, item := range volumeSource.DownwardAPI.Items {
if item.ResourceFieldRef != nil {
if strings.HasPrefix(item.ResourceFieldRef.Resource, "requests.hugepages-") || strings.HasPrefix(item.ResourceFieldRef.Resource, "limits.hugepages-") {
return true
}
}
}
}
}
return false
}
// usesHugePagesInProjectedEnv returns true if hugepages are used in downward api for volume
func usesHugePagesInProjectedEnv(item api.Container) bool {
for _, env := range item.Env {
if env.ValueFrom != nil {
if env.ValueFrom.ResourceFieldRef != nil {
if strings.HasPrefix(env.ValueFrom.ResourceFieldRef.Resource, "requests.hugepages-") || strings.HasPrefix(env.ValueFrom.ResourceFieldRef.Resource, "limits.hugepages-") {
return true
}
}
}
}
return false
}
// usesMultipleHugePageResources returns true if the pod spec uses more than
// one size of hugepage
func usesMultipleHugePageResources(podSpec *api.PodSpec) bool {
hugePageResources := sets.NewString()
resourceSet := helper.ToPodResourcesSet(podSpec)
for resourceStr := range resourceSet {
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
hugePageResources.Insert(resourceStr)
}
}
return len(hugePageResources) > 1
}
// GetValidationOptionsFromPodSpec returns validation options based on pod specs
func GetValidationOptionsFromPodSpec(podSpec, oldPodSpec *api.PodSpec) apivalidation.PodValidationOptions {
// default pod validation options based on feature gate
opts := validation.PodValidationOptions{
// Allow multiple huge pages on pod create if feature is enabled
AllowMultipleHugePageResources: utilfeature.DefaultFeatureGate.Enabled(features.HugePageStorageMediumSize),
// Allow pod spec to use hugepages in downward API if feature is enabled
AllowDownwardAPIHugePages: utilfeature.DefaultFeatureGate.Enabled(features.DownwardAPIHugePages),
}
// if we are not doing an update operation, just return with default options
if oldPodSpec == nil {
return opts
}
// if old spec used multiple huge page sizes, we must allow it
opts.AllowMultipleHugePageResources = opts.AllowMultipleHugePageResources || usesMultipleHugePageResources(oldPodSpec)
// if old spec used hugepages in downward api, we must allow it
opts.AllowDownwardAPIHugePages = opts.AllowDownwardAPIHugePages || usesHugePagesInProjectedVolume(oldPodSpec)
// determine if any container is using hugepages in env var
if !opts.AllowDownwardAPIHugePages {
VisitContainers(oldPodSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool {
opts.AllowDownwardAPIHugePages = opts.AllowDownwardAPIHugePages || usesHugePagesInProjectedEnv(*c)
return !opts.AllowDownwardAPIHugePages
})
}
return opts
}
// GetValidationOptionsFromPodTemplate will return pod validation options for specified template.
func GetValidationOptionsFromPodTemplate(podTemplate, oldPodTemplate *api.PodTemplateSpec) apivalidation.PodValidationOptions {
var newPodSpec, oldPodSpec *api.PodSpec
// we have to be careful about nil pointers here
// replication controller in particular is prone to passing nil
if podTemplate != nil {
newPodSpec = &podTemplate.Spec
}
if oldPodTemplate != nil {
oldPodSpec = &oldPodTemplate.Spec
}
return GetValidationOptionsFromPodSpec(newPodSpec, oldPodSpec)
}
// DropDisabledTemplateFields removes disabled fields from the pod template metadata and spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a PodTemplateSpec
func DropDisabledTemplateFields(podTemplate, oldPodTemplate *api.PodTemplateSpec) {

View File

@@ -98,6 +98,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/pod:go_default_library",
"//pkg/api/testing/compat:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/v1:go_default_library",

View File

@@ -19,14 +19,14 @@ package testing
import (
"testing"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
podutil "k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/api/testing/compat"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
_ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/apis/core/validation"
)
func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
@@ -159,7 +159,8 @@ func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
}
validator := func(obj runtime.Object) field.ErrorList {
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), &(obj.(*api.Pod).ObjectMeta), field.NewPath("spec"))
opts := podutil.GetValidationOptionsFromPodSpec(&(obj.(*api.Pod).Spec), nil)
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), &(obj.(*api.Pod).ObjectMeta), field.NewPath("spec"), opts)
}
for _, tc := range cases {

View File

@@ -20,8 +20,9 @@ import (
"fmt"
"math"
"strconv"
"strings"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
@@ -193,7 +194,20 @@ func ExtractContainerResourceValue(fs *v1.ResourceFieldSelector, container *v1.C
case "requests.ephemeral-storage":
return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor)
}
// handle extended standard resources with dynamic names
// example: requests.hugepages-<pageSize> or limits.hugepages-<pageSize>
if strings.HasPrefix(fs.Resource, "requests.") {
resourceName := v1.ResourceName(strings.TrimPrefix(fs.Resource, "requests."))
if IsHugePageResourceName(resourceName) {
return convertResourceHugePagesToString(container.Resources.Requests.Name(resourceName, resource.BinarySI), divisor)
}
}
if strings.HasPrefix(fs.Resource, "limits.") {
resourceName := v1.ResourceName(strings.TrimPrefix(fs.Resource, "limits."))
if IsHugePageResourceName(resourceName) {
return convertResourceHugePagesToString(container.Resources.Limits.Name(resourceName, resource.BinarySI), divisor)
}
}
return "", fmt.Errorf("unsupported container resource : %v", fs.Resource)
}
@@ -211,6 +225,13 @@ func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Q
return strconv.FormatInt(m, 10), nil
}
// convertResourceHugePagesToString converts hugepages value to the format of divisor and returns
// ceiling of the value.
func convertResourceHugePagesToString(hugePages *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(hugePages.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
// convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns
// ceiling of the value.
func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) {
@@ -240,6 +261,8 @@ func MergeContainerResourceLimits(container *v1.Container,
if container.Resources.Limits == nil {
container.Resources.Limits = make(v1.ResourceList)
}
// NOTE: we exclude hugepages-* resources because hugepages are never overcommitted.
// This means that the container always has a limit specified.
for _, resource := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory, v1.ResourceEphemeralStorage} {
if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() {
if cap, exists := allocatable[resource]; exists {
@@ -248,3 +271,9 @@ func MergeContainerResourceLimits(container *v1.Container,
}
}
}
// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name v1.ResourceName) bool {
return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
}