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 30, 2019
1 parent 19a8846 commit b1d075b
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 0 deletions.
72 changes: 72 additions & 0 deletions vertical-pod-autoscaler/pkg/utils/vpa/capping.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func (c *cappingRecommendationProcessor) Apply(
if err != nil {
return nil, nil, err
}
limitAdjustedRecommendation, err = c.capProportionallyToPodLimitRange(limitAdjustedRecommendation, pod)
if err != nil {
return nil, nil, err
}
for _, containerRecommendation := range limitAdjustedRecommendation {
container := getContainer(containerRecommendation.ContainerName, pod)

Expand Down Expand Up @@ -278,3 +282,71 @@ func getContainer(containerName string, pod *apiv1.Pod) *apiv1.Container {
}
return nil
}

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, _ := GetProportionalLimit(&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(
resources []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 resources, 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
}
resources = applyPodLimitRange(resources, pod, *podLimitRange, apiv1.ResourceCPU, getLower)
resources = applyPodLimitRange(resources, pod, *podLimitRange, apiv1.ResourceMemory, getLower)

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

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

return resources, 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 @@ -294,3 +294,220 @@ func TestApplyCapsToLimitRange(t *testing.T) {
assert.Equal(t, map[string][]string{"container": {"changed CPU limit to fit within limit range", "changed memory limit to fit within limit range"}}, annotations)
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 b1d075b

Please sign in to comment.