Merge pull request #676 from tkulczynski/request-limit
Make requests smaller or equal to limits
This commit is contained in:
commit
727cb2fa33
|
|
@ -41,76 +41,20 @@ func NewRecommendationProvider(vpaLister vpa_lister.VerticalPodAutoscalerLister)
|
|||
return &recommendationProvider{vpaLister: vpaLister}
|
||||
}
|
||||
|
||||
// getRecomendedResources overwrites pod resources Request field with recommended values.
|
||||
// getRecomendedResources returns the recommended resources Request for each container in the given pod in the same order they are specified in the pod.Spec.
|
||||
func getRecomendedResources(pod *v1.Pod, podRecommendation vpa_types.RecommendedPodResources, policy vpa_types.PodResourcePolicy) []v1.ResourceList {
|
||||
res := make([]v1.ResourceList, len(pod.Spec.Containers))
|
||||
for i, container := range pod.Spec.Containers {
|
||||
containerRecommendation := getRecommendationForContainer(podRecommendation, container)
|
||||
if containerRecommendation == nil {
|
||||
recommendation, err := vpa_api_util.GetCappedRecommendationForContainer(container, &podRecommendation, &policy)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("%v", err)
|
||||
continue
|
||||
}
|
||||
containerPolicy := getContainerPolicy(container.Name, &policy)
|
||||
applyVPAPolicy(containerRecommendation, containerPolicy)
|
||||
res[i] = make(v1.ResourceList)
|
||||
for resource, recommended := range containerRecommendation.Target {
|
||||
requested, exists := container.Resources.Requests[resource]
|
||||
if exists {
|
||||
// overwriting existing resource spec
|
||||
glog.V(2).Infof("updating resources request for pod %v container %v resource %v old value: %v new value: %v",
|
||||
pod.Name, container.Name, resource, requested, recommended)
|
||||
} else {
|
||||
// adding new resource spec
|
||||
glog.V(2).Infof("updating resources request for pod %v container %v resource %v old value: none new value: %v",
|
||||
pod.Name, container.Name, resource, recommended)
|
||||
}
|
||||
|
||||
res[i][resource] = recommended
|
||||
}
|
||||
res[i] = recommendation
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// applyVPAPolicy updates recommendation if recommended resources exceed limits defined in VPA resources policy
|
||||
func applyVPAPolicy(recommendation *vpa_types.RecommendedContainerResources, policy *vpa_types.ContainerResourcePolicy) {
|
||||
for resourceName, recommended := range recommendation.Target {
|
||||
if policy == nil {
|
||||
continue
|
||||
}
|
||||
min, found := policy.MinAllowed[resourceName]
|
||||
if found && !min.IsZero() && recommended.Value() < min.Value() {
|
||||
glog.Warningf("recommendation outside of policy bounds : min value : %v recommended : %v",
|
||||
min.Value(), recommended)
|
||||
recommendation.Target[resourceName] = min
|
||||
}
|
||||
max, found := policy.MaxAllowed[resourceName]
|
||||
if found && !max.IsZero() && recommended.Value() > max.Value() {
|
||||
glog.Warningf("recommendation outside of policy bounds : max value : %v recommended : %v",
|
||||
max.Value(), recommended)
|
||||
recommendation.Target[resourceName] = max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRecommendationForContainer(recommendation vpa_types.RecommendedPodResources, container v1.Container) *vpa_types.RecommendedContainerResources {
|
||||
for i, containerRec := range recommendation.ContainerRecommendations {
|
||||
if containerRec.Name == container.Name {
|
||||
return &recommendation.ContainerRecommendations[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerPolicy(containerName string, policy *vpa_types.PodResourcePolicy) *vpa_types.ContainerResourcePolicy {
|
||||
if policy != nil {
|
||||
for i, container := range policy.ContainerPolicies {
|
||||
if containerName == container.Name {
|
||||
return &policy.ContainerPolicies[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *recommendationProvider) getMatchingVPA(pod *v1.Pod) *vpa_types.VerticalPodAutoscaler {
|
||||
configs, err := p.vpaLister.VerticalPodAutoscalers(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
|
||||
)
|
||||
|
||||
// GetCappedRecommendationForContainer returns a recommendation extracted for the given container, adjusted to obey policy and limits.
|
||||
func GetCappedRecommendationForContainer(
|
||||
container apiv1.Container,
|
||||
podRecommendation *vpa_types.RecommendedPodResources,
|
||||
policy *vpa_types.PodResourcePolicy) (apiv1.ResourceList, error) {
|
||||
containerRecommendation := getRecommendationForContainer(podRecommendation, container)
|
||||
if containerRecommendation == nil {
|
||||
return nil, fmt.Errorf("no recommendation available for container name %v", container.Name)
|
||||
}
|
||||
// containerPolicy can be nil (user does not have to configure it).
|
||||
containerPolicy := getContainerPolicy(container.Name, policy)
|
||||
applyVPAPolicy(containerRecommendation, containerPolicy)
|
||||
// TODO: If limits and policy are conflicting, set some condition on the VPA.
|
||||
capRecommendationToContainerLimit(containerRecommendation, container)
|
||||
res := make(apiv1.ResourceList)
|
||||
for resource, recommended := range containerRecommendation.Target {
|
||||
res[resource] = recommended
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// capRecommendationToContainerLimit makes sure recommendation is not above current limit for the container.
|
||||
func capRecommendationToContainerLimit(recommendation *vpa_types.RecommendedContainerResources, container apiv1.Container) {
|
||||
// Iterate over limits set in the container. Unset means Infinite limit.
|
||||
for resourceName, limit := range container.Resources.Limits {
|
||||
target, found := recommendation.Target[resourceName]
|
||||
if found && target.MilliValue() > limit.MilliValue() {
|
||||
recommendation.Target[resourceName] = limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applyVPAPolicy updates recommendation if recommended resources are outside of limits defined in VPA resources policy
|
||||
func applyVPAPolicy(recommendation *vpa_types.RecommendedContainerResources, policy *vpa_types.ContainerResourcePolicy) {
|
||||
if policy == nil {
|
||||
return
|
||||
}
|
||||
for resourceName, recommended := range recommendation.Target {
|
||||
min, found := policy.MinAllowed[resourceName]
|
||||
if found && !min.IsZero() && recommended.MilliValue() < min.MilliValue() {
|
||||
recommendation.Target[resourceName] = min
|
||||
}
|
||||
max, found := policy.MaxAllowed[resourceName]
|
||||
if found && !max.IsZero() && recommended.MilliValue() > max.MilliValue() {
|
||||
recommendation.Target[resourceName] = max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRecommendationForContainer(recommendation *vpa_types.RecommendedPodResources, container apiv1.Container) *vpa_types.RecommendedContainerResources {
|
||||
if recommendation != nil {
|
||||
for i, containerRec := range recommendation.ContainerRecommendations {
|
||||
if containerRec.Name == container.Name {
|
||||
recommendationCopy := recommendation.ContainerRecommendations[i]
|
||||
return &recommendationCopy
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerPolicy(containerName string, policy *vpa_types.PodResourcePolicy) *vpa_types.ContainerResourcePolicy {
|
||||
if policy != nil {
|
||||
for i, container := range policy.ContainerPolicies {
|
||||
if containerName == container.Name {
|
||||
return &policy.ContainerPolicies[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
|
||||
)
|
||||
|
||||
func TestRecommendationNotAvailable(t *testing.T) {
|
||||
container := apiv1.Container{Name: "ctr-name"}
|
||||
podRecommendation := vpa_types.RecommendedPodResources{
|
||||
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
|
||||
{
|
||||
Name: "ctr-name-other",
|
||||
Target: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(100, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(50000, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
policy := vpa_types.PodResourcePolicy{}
|
||||
|
||||
res, err := GetCappedRecommendationForContainer(container, &podRecommendation, &policy)
|
||||
assert.Nil(t, res)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestRecommendationChosenForProperContainer(t *testing.T) {
|
||||
container := apiv1.Container{Name: "ctr-name"}
|
||||
podRecommendation := vpa_types.RecommendedPodResources{
|
||||
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
|
||||
{
|
||||
Name: "ctr-name-other",
|
||||
Target: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(100, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(50000, 1),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ctr-name",
|
||||
Target: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(10, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(5000, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
policy := vpa_types.PodResourcePolicy{}
|
||||
|
||||
res, err := GetCappedRecommendationForContainer(container, &podRecommendation, &policy)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, res, apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(10, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(5000, 1),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecommendationCappedToLimit(t *testing.T) {
|
||||
container := apiv1.Container{
|
||||
Name: "ctr-name",
|
||||
Resources: apiv1.ResourceRequirements{
|
||||
Limits: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(3, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(7000, 1),
|
||||
},
|
||||
},
|
||||
}
|
||||
podRecommendation := vpa_types.RecommendedPodResources{
|
||||
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
|
||||
{
|
||||
Name: "ctr-name",
|
||||
Target: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(10, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(5000, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
policy := vpa_types.PodResourcePolicy{}
|
||||
|
||||
res, err := GetCappedRecommendationForContainer(container, &podRecommendation, &policy)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, res, apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(3, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(5000, 1),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecommendationCappedToMinMaxPolicy(t *testing.T) {
|
||||
container := apiv1.Container{Name: "ctr-name"}
|
||||
podRecommendation := vpa_types.RecommendedPodResources{
|
||||
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
|
||||
{
|
||||
Name: "ctr-name",
|
||||
Target: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(10, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(5000, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
policy := vpa_types.PodResourcePolicy{
|
||||
ContainerPolicies: []vpa_types.ContainerResourcePolicy{
|
||||
{
|
||||
Name: "ctr-name",
|
||||
MinAllowed: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(40, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(4000, 1),
|
||||
},
|
||||
MaxAllowed: apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(45, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(4500, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
res, err := GetCappedRecommendationForContainer(container, &podRecommendation, &policy)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, res, apiv1.ResourceList{
|
||||
apiv1.ResourceCPU: *resource.NewScaledQuantity(40, 1),
|
||||
apiv1.ResourceMemory: *resource.NewScaledQuantity(4500, 1),
|
||||
})
|
||||
}
|
||||
|
|
@ -20,12 +20,11 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
|
||||
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
|
||||
|
||||
"github.com/golang/glog"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
"github.com/golang/glog"
|
||||
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
|
||||
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -90,36 +89,25 @@ func (calc *UpdatePriorityCalculator) getUpdatePriority(pod *apiv1.Pod, recommen
|
|||
var priority float64
|
||||
|
||||
for _, podContainer := range pod.Spec.Containers {
|
||||
cr := getContainerRecommendation(podContainer.Name, recommendation)
|
||||
if cr == nil {
|
||||
recommendedRequest, err := vpa_api_util.GetCappedRecommendationForContainer(podContainer, recommendation, calc.resourcesPolicy)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("no recommendation for container %v in pod %v", podContainer.Name, pod.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
containerPolicy := getContainerPolicy(podContainer.Name, calc.resourcesPolicy)
|
||||
|
||||
for resourceName, recommended := range cr.Target {
|
||||
var requested, min, max *resource.Quantity
|
||||
for resourceName, recommended := range recommendedRequest {
|
||||
var requested *resource.Quantity
|
||||
|
||||
if request, ok := podContainer.Resources.Requests[resourceName]; ok {
|
||||
requested = &request
|
||||
}
|
||||
if containerPolicy != nil {
|
||||
if minAllowed, ok := containerPolicy.MinAllowed[resourceName]; ok {
|
||||
min = &minAllowed
|
||||
}
|
||||
if maxAllowed, ok := containerPolicy.MaxAllowed[resourceName]; ok {
|
||||
max = &maxAllowed
|
||||
}
|
||||
}
|
||||
resourceDiff := getPercentageDiff(requested, min, max, &recommended)
|
||||
resourceDiff := getPercentageDiff(requested, &recommended)
|
||||
priority += math.Abs(resourceDiff)
|
||||
}
|
||||
}
|
||||
return priority
|
||||
}
|
||||
|
||||
func getPercentageDiff(request, min, max, recommendation *resource.Quantity) float64 {
|
||||
func getPercentageDiff(request, recommendation *resource.Quantity) float64 {
|
||||
if request == nil {
|
||||
// resource requirement is not currently specified
|
||||
// any recommendation for this resource we will treat as 100% change
|
||||
|
|
@ -128,39 +116,7 @@ func getPercentageDiff(request, min, max, recommendation *resource.Quantity) flo
|
|||
if recommendation == nil || recommendation.IsZero() {
|
||||
return 0
|
||||
}
|
||||
recommended := recommendation.MilliValue()
|
||||
if min != nil && !min.IsZero() && recommendation.MilliValue() < min.MilliValue() {
|
||||
glog.Warningf("recommendation outside of policy bounds : min value : %v recommended : %v",
|
||||
min.MilliValue(), recommended)
|
||||
recommended = min.MilliValue()
|
||||
}
|
||||
if max != nil && !max.IsZero() && recommendation.MilliValue() > max.MilliValue() {
|
||||
glog.Warningf("recommendation outside of policy bounds : max value : %v recommended : %v",
|
||||
max.MilliValue(), recommended)
|
||||
recommended = max.MilliValue()
|
||||
}
|
||||
diff := recommended - request.MilliValue()
|
||||
return float64(diff) / float64(request.MilliValue())
|
||||
}
|
||||
|
||||
func getContainerPolicy(containerName string, policy *vpa_types.PodResourcePolicy) *vpa_types.ContainerResourcePolicy {
|
||||
if policy != nil {
|
||||
for _, container := range policy.ContainerPolicies {
|
||||
if containerName == container.Name {
|
||||
return &container
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerRecommendation(containerName string, recommendation *vpa_types.RecommendedPodResources) *vpa_types.RecommendedContainerResources {
|
||||
for _, container := range recommendation.ContainerRecommendations {
|
||||
if containerName == container.Name {
|
||||
return &container
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return float64(recommendation.MilliValue()-request.MilliValue()) / float64(request.MilliValue())
|
||||
}
|
||||
|
||||
type podPriority struct {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func TestSortPriorityMultiContainers(t *testing.T) {
|
|||
assert.Exactly(t, []*apiv1.Pod{pod2, pod1}, result, "Wrong priority order")
|
||||
}
|
||||
|
||||
func TestSortPriorityResorucesDecrease(t *testing.T) {
|
||||
func TestSortPriorityResourcesDecrease(t *testing.T) {
|
||||
calculator := NewUpdatePriorityCalculator(nil, nil)
|
||||
|
||||
pod1 := test.BuildTestPod("POD1", containerName, "4", "", nil, nil)
|
||||
|
|
|
|||
Loading…
Reference in New Issue