Skip to content

Commit

Permalink
Capping to pod limit range
Browse files Browse the repository at this point in the history
  • Loading branch information
jbartosik committed May 31, 2019
1 parent 9ce20d6 commit 7095cc4
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 1 deletion.
74 changes: 73 additions & 1 deletion vertical-pod-autoscaler/pkg/utils/vpa/capping.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ func (c *cappingRecommendationProcessor) Apply(
}
updatedRecommendations := []vpa_types.RecommendedContainerResources{}
containerToAnnotationsMap := ContainerToAnnotationsMap{}
for _, containerRecommendation := range podRecommendation.ContainerRecommendations {
limitAdjustedRecommendation, err := c.capProportionallyToPodLimitRange(podRecommendation.ContainerRecommendations, pod)
if err != nil {
return nil, nil, err
}
for _, containerRecommendation := range limitAdjustedRecommendation {
container := getContainer(containerRecommendation.ContainerName, pod)

if container == nil {
Expand Down Expand Up @@ -300,3 +304,71 @@ func getBoundaryRecommendation(recommendation apiv1.ResourceList, container apiv
apiv1.ResourceMemory: *memMaxRequest,
}
}

func applyPodLimitRange(resources []vpa_types.RecommendedContainerResources,
pod *apiv1.Pod, limitRange apiv1.LimitRangeItem, resourceName apiv1.ResourceName,
get func(vpa_types.RecommendedContainerResources) *apiv1.ResourceList) []vpa_types.RecommendedContainerResources {
minLimit := limitRange.Min[resourceName]
maxLimit := limitRange.Max[resourceName]
defaultLimit := limitRange.Default[resourceName]

var sumLimit resource.Quantity
for i, item := range pod.Spec.Containers {
if i >= len(resources) {
continue
}
limit := item.Resources.Limits[resourceName]
request := item.Resources.Requests[resourceName]
recommendation := (*get(resources[i]))[resourceName]
containerLimit, _ := getProportionalResourceLimit(resourceName, &limit, &request, &recommendation, &defaultLimit)
if containerLimit != nil {
sumLimit.Add(*containerLimit)
}
}
if minLimit.Cmp(sumLimit) <= 0 && (maxLimit.IsZero() || maxLimit.Cmp(sumLimit) >= 0) {
return resources
}
var targetTotalLimit resource.Quantity
if minLimit.Cmp(sumLimit) > 0 {
targetTotalLimit = minLimit
}
if !maxLimit.IsZero() && maxLimit.Cmp(sumLimit) < 0 {
targetTotalLimit = maxLimit
}
for i := range pod.Spec.Containers {
limit := (*get(resources[i]))[resourceName]
cappedContainerRequest, _ := scaleQuantityProportionally(&limit, &sumLimit, &targetTotalLimit)
(*get(resources[i]))[resourceName] = *cappedContainerRequest
}
return resources
}

func (c *cappingRecommendationProcessor) capProportionallyToPodLimitRange(
containerRecommendations []vpa_types.RecommendedContainerResources, pod *apiv1.Pod) ([]vpa_types.RecommendedContainerResources, error) {
podLimitRange, err := c.limitsRangeCalculator.GetPodLimitRangeItem(pod.Namespace)
if err != nil {
return nil, fmt.Errorf("error obtaining limit range: %s", err)
}
if podLimitRange == nil {
return containerRecommendations, nil
}
getLower := func(r vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &r.LowerBound
}
getTarget := func(r vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &r.Target
}
getUpper := func(r vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &r.UpperBound
}
containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceCPU, getLower)
containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceMemory, getLower)

containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceCPU, getTarget)
containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceMemory, getTarget)

containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceCPU, getUpper)
containerRecommendations = applyPodLimitRange(containerRecommendations, pod, *podLimitRange, apiv1.ResourceMemory, getUpper)

return containerRecommendations, nil
}
217 changes: 217 additions & 0 deletions vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,220 @@ func TestApplyCapsToLimitRange(t *testing.T) {
assert.ElementsMatch(t, []string{"cpu capped to fit Max in container LimitRange", "memory capped to fit Min in container LimitRange"}, annotations["container"])
assert.Equal(t, expectedRecommendation, *processedRecommendation)
}

func TestApplyPodLimitRange(t *testing.T) {
tests := []struct {
name string
resources []vpa_types.RecommendedContainerResources
pod apiv1.Pod
limitRange apiv1.LimitRangeItem
resourceName apiv1.ResourceName
get func(vpa_types.RecommendedContainerResources) *apiv1.ResourceList
expect []vpa_types.RecommendedContainerResources
}{
{
name: "cap target cpu to max",
resources: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
{
ContainerName: "container2",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
pod: apiv1.Pod{
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
},
},
},
limitRange: apiv1.LimitRangeItem{
Max: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
resourceName: apiv1.ResourceCPU,
get: func(rcr vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &rcr.Target
},
expect: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: *resource.NewMilliQuantity(500, resource.DecimalSI),
},
},
{
ContainerName: "container2",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: *resource.NewMilliQuantity(500, resource.DecimalSI),
},
},
},
},
{
name: "cap lower bound cpu to max",
resources: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
LowerBound: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
{
ContainerName: "container2",
LowerBound: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
pod: apiv1.Pod{
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
},
},
},
},
limitRange: apiv1.LimitRangeItem{
Max: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1"),
},
},
resourceName: apiv1.ResourceCPU,
get: func(rcr vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &rcr.LowerBound
},
expect: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
LowerBound: apiv1.ResourceList{
apiv1.ResourceCPU: *resource.NewMilliQuantity(500, resource.DecimalSI),
},
},
{
ContainerName: "container2",
LowerBound: apiv1.ResourceList{
apiv1.ResourceCPU: *resource.NewMilliQuantity(500, resource.DecimalSI),
},
},
},
},
{
name: "cap upper bound mem to min",
resources: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
UpperBound: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1G"),
},
},
{
ContainerName: "container2",
UpperBound: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1G"),
},
},
},
pod: apiv1.Pod{
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1"),
},
},
},
{
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1"),
},
Limits: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("1"),
},
},
},
},
},
},
limitRange: apiv1.LimitRangeItem{
Min: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("4G"),
},
},
resourceName: apiv1.ResourceMemory,
get: func(rcr vpa_types.RecommendedContainerResources) *apiv1.ResourceList {
return &rcr.UpperBound
},
expect: []vpa_types.RecommendedContainerResources{
{
ContainerName: "container1",
UpperBound: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("2000000000000m"),
},
},
{
ContainerName: "container2",
UpperBound: apiv1.ResourceList{
apiv1.ResourceMemory: resource.MustParse("2000000000000m"),
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := applyPodLimitRange(tc.resources, &tc.pod, tc.limitRange, tc.resourceName, tc.get)
assert.Equal(t, tc.expect, got)
})
}
}

0 comments on commit 7095cc4

Please sign in to comment.