Skip to content

Commit

Permalink
Configurable container limit scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneswuerbach committed May 16, 2020
1 parent 7b7d704 commit 30940f8
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func NewProvider(calculator limitrange.LimitRangeCalculator,
}

// GetContainersResources returns the recommended resources for each container in the given pod in the same order they are specified in the pod.Spec.
func GetContainersResources(pod *core.Pod, podRecommendation vpa_types.RecommendedPodResources, limitRange *core.LimitRangeItem,
func GetContainersResources(pod *core.Pod, vpaResourcePolicy *vpa_types.PodResourcePolicy, podRecommendation vpa_types.RecommendedPodResources, limitRange *core.LimitRangeItem,
annotations vpa_api_util.ContainerToAnnotationsMap) []vpa_api_util.ContainerResources {
resources := make([]vpa_api_util.ContainerResources, len(pod.Spec.Containers))
for i, container := range pod.Spec.Containers {
Expand All @@ -60,17 +60,29 @@ func GetContainersResources(pod *core.Pod, podRecommendation vpa_types.Recommend
if limitRange != nil {
defaultLimit = limitRange.Default
}
proportionalLimits, limitAnnotations := vpa_api_util.GetProportionalLimit(container.Resources.Limits, container.Resources.Requests, recommendation.Target, defaultLimit)
if proportionalLimits != nil {
resources[i].Limits = proportionalLimits
if len(limitAnnotations) > 0 {
annotations[container.Name] = append(annotations[container.Name], limitAnnotations...)
containerLimitPolicy := GetContainerLimitPolicy(container.Name, vpaResourcePolicy)
if containerLimitPolicy == vpa_types.ContainerLimitScalingModeAuto {
proportionalLimits, limitAnnotations := vpa_api_util.GetProportionalLimit(container.Resources.Limits, container.Resources.Requests, recommendation.Target, defaultLimit)
if proportionalLimits != nil {
resources[i].Limits = proportionalLimits
if len(limitAnnotations) > 0 {
annotations[container.Name] = append(annotations[container.Name], limitAnnotations...)
}
}
}
}
return resources
}

// GetContainerLimitPolicy returns container limit scaling policy by container name
func GetContainerLimitPolicy(name string, vpaResourcePolicy *vpa_types.PodResourcePolicy) vpa_types.ContainerLimitScalingMode {
containerPolicy := vpa_api_util.GetContainerResourcePolicy(name, vpaResourcePolicy)
if containerPolicy == nil || containerPolicy.LimitMode == nil {
return vpa_types.ContainerLimitScalingModeAuto
}
return *containerPolicy.LimitMode
}

// GetContainersResourcesForPod returns recommended request for a given pod and associated annotations.
// The returned slice corresponds 1-1 to containers in the Pod.
func (p *recommendationProvider) GetContainersResourcesForPod(pod *core.Pod, vpa *vpa_types.VerticalPodAutoscaler) ([]vpa_api_util.ContainerResources, vpa_api_util.ContainerToAnnotationsMap, error) {
Expand All @@ -95,6 +107,10 @@ func (p *recommendationProvider) GetContainersResourcesForPod(pod *core.Pod, vpa
if err != nil {
return nil, nil, fmt.Errorf("error getting containerLimitRange: %s", err)
}
containerResources := GetContainersResources(pod, *recommendedPodResources, containerLimitRange, annotations)
var resourcePolicy *vpa_types.PodResourcePolicy
if vpa.Spec.UpdatePolicy == nil || vpa.Spec.UpdatePolicy.UpdateMode == nil || *vpa.Spec.UpdatePolicy.UpdateMode != vpa_types.UpdateModeOff {
resourcePolicy = vpa.Spec.ResourcePolicy
}
containerResources := GetContainersResources(pod, resourcePolicy, *recommendedPodResources, containerLimitRange, annotations)
return containerResources, annotations, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ func TestUpdateResourceRequests(t *testing.T) {
vpaWithHighMemory := vpaBuilder.WithTarget("2", "1000Mi").WithMaxAllowed("3", "3Gi").Get()
vpaWithExabyteRecommendation := vpaBuilder.WithTarget("1Ei", "1Ei").WithMaxAllowed("1Ei", "1Ei").Get()

limitScalingOffVPA := vpaBuilder.WithLimitMode(vpa_types.ContainerLimitScalingModeOff).Get()

vpaWithEmptyRecommendation := vpaBuilder.Get()
vpaWithEmptyRecommendation.Status.Recommendation = &vpa_types.RecommendedPodResources{}
vpaWithNilRecommendation := vpaBuilder.Get()
Expand Down Expand Up @@ -210,6 +212,14 @@ func TestUpdateResourceRequests(t *testing.T) {
expectedCPULimit: mustParseResourcePointer("4"),
expectedMemLimit: mustParseResourcePointer("400Mi"),
},
{
name: "disabled limit scaling",
pod: podWithDoubleLimit,
vpa: limitScalingOffVPA,
expectedAction: true,
expectedCPU: resource.MustParse("2"),
expectedMem: resource.MustParse("200Mi"),
},
{
name: "limit over int64",
pod: podWithTenfoldLimit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ func validateVPA(vpa *vpa_types.VerticalPodAutoscaler, isCreate bool) error {
return fmt.Errorf("max resource for %v is lower than min", resource)
}
}
limitMode := policy.LimitMode
if mode != nil && limitMode != nil {
if *mode == vpa_types.ContainerScalingModeOff && *limitMode == vpa_types.ContainerLimitScalingModeAuto {
return fmt.Errorf("Mode can not be Off when LimitScaling is Auto")
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func TestValidateVPA(t *testing.T) {
validUpdateMode := vpa_types.UpdateModeOff
badScalingMode := vpa_types.ContainerScalingMode("bad")
validScalingMode := vpa_types.ContainerScalingModeAuto
scalingModeOff := vpa_types.ContainerScalingModeOff
autoLimitScalingMode := vpa_types.ContainerLimitScalingModeAuto
tests := []struct {
name string
vpa vpa_types.VerticalPodAutoscaler
Expand Down Expand Up @@ -120,6 +122,23 @@ func TestValidateVPA(t *testing.T) {
},
expectError: fmt.Errorf("max resource for cpu is lower than min"),
},
{
name: "scaling off with limit scaling auto",
vpa: vpa_types.VerticalPodAutoscaler{
Spec: vpa_types.VerticalPodAutoscalerSpec{
ResourcePolicy: &vpa_types.PodResourcePolicy{
ContainerPolicies: []vpa_types.ContainerResourcePolicy{
{
ContainerName: "loot box",
Mode: &scalingModeOff,
LimitMode: &autoLimitScalingMode,
},
},
},
},
},
expectError: fmt.Errorf("Mode can not be Off when LimitScaling is Auto"),
},
{
name: "all valid",
vpa: vpa_types.VerticalPodAutoscaler{
Expand Down
17 changes: 17 additions & 0 deletions vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ type ContainerResourcePolicy struct {
// (and possibly applied) by VPA.
// If not specified, the default of [ResourceCPU, ResourceMemory] will be used.
ControlledResources *[]v1.ResourceName `json:"controlledResources,omitempty" patchStrategy:"merge" protobuf:"bytes,5,rep,name=controlledResources"`

// Whether autoscaler limit scaling is enabled for the container. The default is "Auto".
// Enabling this requires the autoscaler to be enabled for the container
// +optional
LimitMode *ContainerLimitScalingMode `json:"limitMode,omitempty" protobuf:"bytes,6,rep,name=limitMode"`
}

const (
Expand All @@ -171,6 +176,18 @@ const (
ContainerScalingModeOff ContainerScalingMode = "Off"
)

// ContainerLimitScalingMode controls whether autoscaler limit scaling
// is enabled for a specific container.
type ContainerLimitScalingMode string

const (
// ContainerLimitScalingModeAuto means limits are scaled automatically.
// Limit is scaled proportionally to the request.
ContainerLimitScalingModeAuto ContainerLimitScalingMode = "Auto"
// ContainerLimitScalingModeOff means limit scaling is disabled for a container.
ContainerLimitScalingModeOff ContainerLimitScalingMode = "Off"
)

// VerticalPodAutoscalerStatus describes the runtime state of the autoscaler.
type VerticalPodAutoscalerStatus struct {
// The most recently computed amount of resources recommended by the
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions vertical-pod-autoscaler/pkg/utils/test/test_vpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type VerticalPodAutoscalerBuilder interface {
WithCreationTimestamp(timestamp time.Time) VerticalPodAutoscalerBuilder
WithMinAllowed(cpu, memory string) VerticalPodAutoscalerBuilder
WithMaxAllowed(cpu, memory string) VerticalPodAutoscalerBuilder
WithLimitMode(mode vpa_types.ContainerLimitScalingMode) VerticalPodAutoscalerBuilder
WithTarget(cpu, memory string) VerticalPodAutoscalerBuilder
WithLowerBound(cpu, memory string) VerticalPodAutoscalerBuilder
WithTargetRef(targetRef *autoscaling.CrossVersionObjectReference) VerticalPodAutoscalerBuilder
Expand Down Expand Up @@ -63,6 +64,7 @@ type verticalPodAutoscalerBuilder struct {
creationTimestamp time.Time
minAllowed core.ResourceList
maxAllowed core.ResourceList
limitMode *vpa_types.ContainerLimitScalingMode
recommendation RecommendationBuilder
conditions []vpa_types.VerticalPodAutoscalerCondition
annotations map[string]string
Expand Down Expand Up @@ -115,6 +117,12 @@ func (b *verticalPodAutoscalerBuilder) WithMaxAllowed(cpu, memory string) Vertic
return &c
}

func (b *verticalPodAutoscalerBuilder) WithLimitMode(mode vpa_types.ContainerLimitScalingMode) VerticalPodAutoscalerBuilder {
c := *b
c.limitMode = &mode
return &c
}

func (b *verticalPodAutoscalerBuilder) WithTarget(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.recommendation = c.recommendation.WithTarget(cpu, memory)
Expand Down Expand Up @@ -171,6 +179,7 @@ func (b *verticalPodAutoscalerBuilder) Get() *vpa_types.VerticalPodAutoscaler {
ContainerName: b.containerName,
MinAllowed: b.minAllowed,
MaxAllowed: b.maxAllowed,
LimitMode: b.limitMode,
}}}

recommendation := b.recommendation.WithContainer(b.containerName).Get()
Expand Down

0 comments on commit 30940f8

Please sign in to comment.