Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit range limit support #2049

Merged
merged 8 commits into from
May 30, 2019

Conversation

jbartosik
Copy link
Collaborator

@jbartosik jbartosik commented May 20, 2019

This is built on #1813. I follow it with a change addressing comments from review of the original PR. On top of that I make changes to logic of handling limit range.

VPA now preserves limit/request ratio configured by user. VPA will now preserve the ratio if limit comes from default in limit range. It will also preserve the ratio if it must cap limit to keep it below max defined in the limit range.

This uses limit range that appears first in the list and applies to containers. It should be able to handle multiple limit ranges. It should also handle limit ranges on pods.

safanaj and others added 2 commits May 20, 2019 15:47
To allow admission controller to set limits on containers if needed
because LimitRange in namespace with default and max ratio.
This feature have to be explicitly enabled by passing the flag `--allow-to-adjust-limits`
I'm keeping original CL from kubernetes#1813
and applying changes requested in the review in a separate CL to keep
autoship information clean.
@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 20, 2019
@jbartosik
Copy link
Collaborator Author

@safanaj I'd like support for limit range to VPA. I would appreciate if you find time to check if changes I'm applying on top of your PR make sense.

@jbartosik
Copy link
Collaborator Author

Issue: #1812

@jbartosik jbartosik changed the title Limit range limit support [WIP] Limit range limit support May 20, 2019
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 20, 2019
@jbartosik
Copy link
Collaborator Author

Marking as WIP - I think this is ready for review but I didn't perform any E2E tests yet.

@jbartosik
Copy link
Collaborator Author

/assign @bskiba

@jbartosik
Copy link
Collaborator Author

@kgolab PTAL if you have time.

@bskiba
Copy link
Member

bskiba commented May 22, 2019

@jbartosik There seems to still be a lot of logic for checking the request/limit ratio. Since your changes for VPA to keep the request to limit ratio constant, I think we no longer need it. The only thing we need to observe is the appropriate maxRatio.

@safanaj
Copy link
Contributor

safanaj commented May 23, 2019

@jbartosik Thanks, I think you addressed what I missed in my PR review cycle and does make sense for me.

@jbartosik jbartosik force-pushed the limit-range-limit-support branch 2 times, most recently from 7686ed2 to 29a6991 Compare May 24, 2019 15:56

for _, lr := range limitRanges {
for _, lri := range lr.Spec.Limits {
if lri.Type == v1.LimitTypeContainer || lri.Type == v1.LimitTypePod && (lri.MaxLimitRequestRatio == nil || lri.Default == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jbartosik , I don't get this. In case a LimitRange has MaxRatio and Default both not nil we don't choose that Limit?
Also, in server code I understand that any limit range have to be respected so the more restrictive (minumum max ratio) have to be respected. In this code I understand that we are not checking for the more restrictive limits max ratio.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake. We care about limit range if it has at least one of Max / Default set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but what about the MaxRatio? the purpose of my initial PR was exactly just to shrink limits in case a max ratio was present in LimitRange definition to prevent that after the vpa-updater kill a pod the pod couldn't be recreated anymore because the vpa-admission-controller is setting the requests too low and the LimitRanger admission controller prevent the pod to be created.
I am sorry but I am a bit lost on this rework. this PR like it is now doesn't address anymore the issue #1812

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@safanaj since your PR we have changed the logic of VPA to keep the ratio between request and limit constant. This means that VPA will never break things - since we keep the original request to limit ratio that was specified for the deployment, we rely on the user to specify it in a way that respects the MaxRatio.

@jbartosik jbartosik force-pushed the limit-range-limit-support branch 3 times, most recently from 4a5d4d1 to 87937e5 Compare May 27, 2019 12:32
jbartosik added 2 commits May 27, 2019 14:43
Keep limit/request user set (even if the limit comes from limit range).
Cap limits to Max configured in the limit range.
@jbartosik jbartosik force-pushed the limit-range-limit-support branch from 87937e5 to f1a537a Compare May 27, 2019 12:44
Copy link
Member

@bskiba bskiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main problem - we are changing the recommendation for request as seen by admission controller (if we proportionally cap to maxlimit) but we do not do this in updater.
We can end up with an infinite updating loop since updater has a different view of recommendation than admission controller.

"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/informers"

v1_listers "k8s.io/client-go/listers/core/v1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: just listers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

)

// LimitsRangeCalculator checks for LimitRange and if container needs limits to be set
type LimitsRangeCalculator interface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a LimitRangeItemLister, with a filter applied. So maybe LimitRangeFetcher would be a more appropriate name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will aggregate all matching limit ranges into a single limit range having the same effect. For example if one limit range specifies default, and two other limit ranges specify two different maxes it would return limit range with default from the first limit range and lower max.

I will send that change for review this week so I thought I'd avoid renaming the class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then since it's LimitRange not LimitsRange, lets call it LimitRangeCalculator

for _, lri := range lr.Spec.Limits {
if lri.Type == v1.LimitTypeContainer && (lri.Max != nil || lri.Default != nil) {
// TODO: handle multiple limit ranges matching a pod.
return &v1.LimitRangeItem{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we make a copy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary, done.

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"

//"fmt"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: please organise import and remove comments

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pod *apiv1.Pod
limitRanges []runtime.Object
expectErr error
expectLimits *apiv1.LimitRangeItem
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: expectedLimits

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

func TestUpdateResourceLimits(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TestGetContainerLimitRangeItem

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return resource.NewMilliQuantity(recommendedMilliLimit.Int64(), recommendedRequest.Format), false
}
return resource.NewMilliQuantity(math.MaxInt64, recommendedRequest.Format), true
return scaleQuantityProportionally( /*scaledQuantity=*/ originalLimit /*scaleBase=*/, originalRequest /*scaleResult=*/, recommendedRequest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commas seem to be in weird places.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

return scaledRequest, maxLimit
}

func proportionallyCapLimitsToMax(recommendedRequests v1.ResourceList, cpuLimit, memLimit, maxCpuLimit, maxMemLimit *resource.Quantity) ContainerResources {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call it proportionallyCapResourcesToMaxLimit, since it also caps request (I was suprised)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic actually changes the recommended request, but this is not included in the updater - updater and admission controller will disagree about the request - we can get into an infinite updating loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed.

scaledCpuRequest, scaledCpuLimit := proportionallyCapLimitToMax(recommendedRequests.Cpu(), cpuLimit, maxCpuLimit)
scaledMemRequest, scaledMemLimit := proportionallyCapLimitToMax(recommendedRequests.Memory(), memLimit, maxMemLimit)
result := newContainerResources()
if scaledCpuRequest != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will those be nil?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limits might be nil (if they're not in the original request and there is no default in the limit range).

v1_listers "k8s.io/client-go/listers/core/v1"
)

// LimitsRangeCalculator checks for LimitRange and if container needs limits to be set
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably nitpicking but I don't see why this interface is called Calculator, Finder would be more appropriate.
I also don't get the second part of the comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded comment (to describe what I intend the class to do). For now this returns first matching limit range item in the list. In future PR I will improve the logic.

f.Start(stopCh)
for _, ok := range f.WaitForCacheSync(stopCh) {
if !ok {
if f.Core().V1().LimitRanges().Informer().HasSynced() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be ! HasSynced() ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

name: "no matching limit ranges",
pod: getPod(),
limitRanges: []runtime.Object{
test.LimitRange().WithName("different-namespace").WithNamespace("different").WithType(apiv1.LimitTypePod).WithMax(test.Resources("2", "2")).Get(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since it's different namespace already maybe it's not worth changing type too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is to test that neither limit range matches. One has correct type (now, it was a pod, I think this was you requested) but wrong namespace. Other has good namespace but wrong type.

@jbartosik jbartosik force-pushed the limit-range-limit-support branch from 3b70e32 to 3aa21fe Compare May 29, 2019 13:42
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 29, 2019
@jbartosik jbartosik force-pushed the limit-range-limit-support branch from 3aa21fe to e4a8efa Compare May 29, 2019 13:45
@jbartosik
Copy link
Collaborator Author

@bskiba PTAL

@jbartosik jbartosik force-pushed the limit-range-limit-support branch from e4a8efa to 2437763 Compare May 29, 2019 13:53
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels May 29, 2019
@jbartosik jbartosik force-pushed the limit-range-limit-support branch from 77d7762 to 5f9eb0e Compare May 29, 2019 14:05
@@ -30,7 +30,7 @@ type RecommendationProcessor interface {
// VPA policy and possibly other internal RecommendationProcessor context.
// Must return a non-nil pointer to RecommendedPodResources or error.
Apply(podRecommendation *vpa_types.RecommendedPodResources,
policy *vpa_types.PodResourcePolicy,
conditions []vpa_types.VerticalPodAutoscalerCondition,
_ *vpa_types.PodResourcePolicy,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was in hurry, made a mistake.

@@ -84,6 +91,36 @@ func (c *cappingRecommendationProcessor) Apply(
return &vpa_types.RecommendedPodResources{ContainerRecommendations: updatedRecommendations}, containerToAnnotationsMap, nil
}

func (c *cappingRecommendationProcessor) scaleInProportionWithLimit(podRecommendation *vpa_types.RecommendedPodResources, pod *apiv1.Pod) ([]vpa_types.RecommendedContainerResources, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually capProportionallyToMaxLimit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
cpuLimit, _ := GetProportionalLimit(container.Resources.Limits.Cpu(), container.Resources.Requests.Cpu(), recommendation.Target.Cpu(), defaultCpu)
memLimit, _ := GetProportionalLimit(container.Resources.Limits.Memory(), container.Resources.Requests.Memory(), recommendation.Target.Memory(), defaultMem)
capped := ProportionallyCapResourcesToMaxLimit(recommendation.Target, cpuLimit, memLimit, maxCpuLimit, maxMemLimit)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If capping has been done, this definitely deserves an annotation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

ContainerName: "container",
Target: apiv1.ResourceList{
apiv1.ResourceCPU: resource.MustParse("1000m"),
apiv1.ResourceMemory: resource.MustParse("1000000000000m"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm not sure if my comment was lost) Why millivalue here? It's not too readable

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to use assert.equal to keep test easy to read. The way assert.equal works representation of quantities must be the same, not just their actual values.

@jbartosik jbartosik force-pushed the limit-range-limit-support branch 3 times, most recently from 9a98d9d to 5094dc0 Compare May 29, 2019 14:55
containerToAnnotationsMap[container.Name] = append(containerToAnnotationsMap[container.Name], "changed CPU limit to fit within limit range")
}
if capped.Requests.Memory().Value() != recommendation.Target.Memory().Value() {
containerToAnnotationsMap[container.Name] = append(containerToAnnotationsMap[container.Name], "changed memoery limit to fit within limit range")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit s/memoery/memory/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -62,7 +65,11 @@ func (c *cappingRecommendationProcessor) Apply(
}
updatedRecommendations := []vpa_types.RecommendedContainerResources{}
containerToAnnotationsMap := ContainerToAnnotationsMap{}
for _, containerRecommendation := range podRecommendation.ContainerRecommendations {
limitAdjustedRecommendation, err := c.capProportionallyToMaxLimit(podRecommendation, pod, containerToAnnotationsMap)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work. We need to apply capping also to upper and lower bound. What we need is to integrate capping to max/min limitrange into getCappedRecommendationForContainer function.
Just as we have ApplyVpaPolicy, we need to have ApplyMaxRange here:

process := func(recommendation apiv1.ResourceList, genAnnotations bool) {

Apologies for not spotting it earlier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to adjust not only target but also bounds. I think this will work correctly (if limit range, limit/request ratio and policy allow assigning a recommendation).

@jbartosik jbartosik force-pushed the limit-range-limit-support branch from 5094dc0 to 1b0165c Compare May 29, 2019 16:09
@bskiba
Copy link
Member

bskiba commented May 30, 2019

/lgtm
/approve

please remove WIP from the title

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label May 30, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bskiba

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label May 30, 2019
@bskiba bskiba changed the title [WIP] Limit range limit support Limit range limit support May 30, 2019
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 30, 2019
@k8s-ci-robot k8s-ci-robot merged commit 63039fb into kubernetes:master May 30, 2019
@jbartosik jbartosik deleted the limit-range-limit-support branch May 30, 2019 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants