Skip to content

Commit

Permalink
Add a command line option to limit which resources can be applied
Browse files Browse the repository at this point in the history
  • Loading branch information
domenicbozzuto committed Feb 22, 2024
1 parent 25a8a96 commit a56945e
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 10 deletions.
13 changes: 12 additions & 1 deletion vertical-pod-autoscaler/pkg/admission-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"net/http"
"os"
"strings"
"time"

apiv1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -69,6 +70,7 @@ var (
registerWebhook = flag.Bool("register-webhook", true, "If set to true, admission webhook object will be created on start up to register with the API server.")
registerByURL = flag.Bool("register-by-url", false, "If set to true, admission webhook will be registered by URL (webhookAddress:webhookPort) instead of by service name")
vpaObjectNamespace = flag.String("vpa-object-namespace", apiv1.NamespaceAll, "Namespace to search for VPA objects. Empty means all namespaces will be used.")
allowedResources = flag.String("allowed-resources", "", "Comma-separated allow list of resources that can be applied by this controller. Empty means any recommendation can be applied")
)

func main() {
Expand Down Expand Up @@ -96,7 +98,16 @@ func main() {
klog.Errorf("Failed to create limitRangeCalculator, falling back to not checking limits. Error message: %s", err)
limitRangeCalculator = limitrange.NewNoopLimitsCalculator()
}
recommendationProvider := recommendation.NewProvider(limitRangeCalculator, vpa_api_util.NewCappingRecommendationProcessor(limitRangeCalculator))

var allowedResourcesNames []apiv1.ResourceName
if *allowedResources != "" {
for _, resourceName := range strings.Split(*allowedResources, ",") {
if resourceName != "" {
allowedResourcesNames = append(allowedResourcesNames, apiv1.ResourceName(resourceName))
}
}
}
recommendationProvider := recommendation.NewProvider(limitRangeCalculator, vpa_api_util.NewCappingRecommendationProcessor(limitRangeCalculator), allowedResourcesNames)
vpaMatcher := vpa.NewMatcher(vpaLister, targetSelectorFetcher)

hostname, err := os.Hostname()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package recommendation

import (
"fmt"

core "k8s.io/api/core/v1"
"k8s.io/klog/v2"

Expand All @@ -35,24 +34,27 @@ type Provider interface {
type recommendationProvider struct {
limitsRangeCalculator limitrange.LimitRangeCalculator
recommendationProcessor vpa_api_util.RecommendationProcessor
allowedResources []core.ResourceName
}

// NewProvider constructs the recommendation provider that can be used to determine recommendations for pods.
func NewProvider(calculator limitrange.LimitRangeCalculator,
recommendationProcessor vpa_api_util.RecommendationProcessor) Provider {
recommendationProcessor vpa_api_util.RecommendationProcessor,
allowedResources []core.ResourceName) Provider {
return &recommendationProvider{
limitsRangeCalculator: calculator,
recommendationProcessor: recommendationProcessor,
allowedResources: allowedResources,
}
}

// GetContainersResources returns the recommended resources for each container in the given pod in the same order they are specified in the pod.Spec.
// If addAll is set to true, containers w/o a recommendation are also added to the list, otherwise they're skipped (default behaviour).
func GetContainersResources(pod *core.Pod, vpaResourcePolicy *vpa_types.PodResourcePolicy, podRecommendation vpa_types.RecommendedPodResources, limitRange *core.LimitRangeItem,
addAll bool, annotations vpa_api_util.ContainerToAnnotationsMap) []vpa_api_util.ContainerResources {
addAll bool, annotations vpa_api_util.ContainerToAnnotationsMap, allowedResources []core.ResourceName) []vpa_api_util.ContainerResources {
resources := make([]vpa_api_util.ContainerResources, len(pod.Spec.Containers))
for i, container := range pod.Spec.Containers {
recommendation := vpa_api_util.GetRecommendationForContainer(container.Name, &podRecommendation)
recommendation := filterAllowedContainerResources(vpa_api_util.GetRecommendationForContainer(container.Name, &podRecommendation), allowedResources)
if recommendation == nil {
if !addAll {
klog.V(2).Infof("no matching recommendation found for container %s, skipping", container.Name)
Expand Down Expand Up @@ -109,7 +111,7 @@ func (p *recommendationProvider) GetContainersResourcesForPod(pod *core.Pod, vpa
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, false, annotations)
containerResources := GetContainersResources(pod, resourcePolicy, *recommendedPodResources, containerLimitRange, false, annotations, p.allowedResources)

// Ensure that we are not propagating empty resource key if any.
for _, resource := range containerResources {
Expand All @@ -120,3 +122,35 @@ func (p *recommendationProvider) GetContainersResourcesForPod(pod *core.Pod, vpa

return containerResources, annotations, nil
}

func filterAllowedContainerResources(recommendation *vpa_types.RecommendedContainerResources, allowedResources []core.ResourceName) *vpa_types.RecommendedContainerResources {
if recommendation != nil || len(allowedResources) > 0 {
recommendation.Target = filterResourceList(recommendation.Target, allowedResources)
recommendation.LowerBound = filterResourceList(recommendation.LowerBound, allowedResources)
recommendation.UpperBound = filterResourceList(recommendation.UpperBound, allowedResources)
recommendation.UncappedTarget = filterResourceList(recommendation.UncappedTarget, allowedResources)
}
return recommendation
}

func filterResourceList(resourceList core.ResourceList, allowedResources []core.ResourceName) core.ResourceList {
if len(allowedResources) == 0 {
return resourceList
}
filteredResourceList := core.ResourceList{}
for resourceName, resourceValue := range resourceList {
if containsResource(allowedResources, resourceName) {
filteredResourceList[resourceName] = resourceValue
}
}
return filteredResourceList
}

func containsResource(resourceNames []core.ResourceName, target core.ResourceName) bool {
for _, resourceName := range resourceNames {
if target == resourceName {
return true
}
}
return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func TestUpdateResourceRequests(t *testing.T) {
limitRange *apiv1.LimitRangeItem
limitRangeCalcErr error
annotations vpa_api_util.ContainerToAnnotationsMap
allowedResources []apiv1.ResourceName
}{
{
name: "uninitialized pod",
Expand Down Expand Up @@ -291,6 +292,24 @@ func TestUpdateResourceRequests(t *testing.T) {
},
},
},
{
name: "memory recommendation filtered out",
pod: initialized,
vpa: vpa,
expectedAction: true,
expectedMem: resource.MustParse("200Mi"),
expectedCPU: resource.MustParse("2"),
allowedResources: []apiv1.ResourceName{apiv1.ResourceCPU},
},
{
name: "cpu recommendation filtered out",
pod: initialized,
vpa: vpa,
expectedAction: true,
expectedMem: resource.MustParse("200Mi"),
expectedCPU: resource.MustParse("2"),
allowedResources: []apiv1.ResourceName{apiv1.ResourceMemory},
},
}

for _, tc := range testCases {
Expand All @@ -301,6 +320,7 @@ func TestUpdateResourceRequests(t *testing.T) {
containerLimitRange: tc.limitRange,
containerErr: tc.limitRangeCalcErr,
},
allowedResources: tc.allowedResources,
}

resources, annotations, err := recommendationProvider.GetContainersResourcesForPod(tc.pod, tc.vpa)
Expand All @@ -314,11 +334,21 @@ func TestUpdateResourceRequests(t *testing.T) {
_, foundEmpty := resources[0].Requests[""]
assert.Equal(t, foundEmpty, false, "empty resourceKey have not been purged")

cpuRequest := resources[0].Requests[apiv1.ResourceCPU]
assert.Equal(t, tc.expectedCPU.Value(), cpuRequest.Value(), "cpu request doesn't match")
if len(tc.allowedResources) == 0 || containsResource(tc.allowedResources, apiv1.ResourceCPU) {
cpuRequest := resources[0].Requests[apiv1.ResourceCPU]
assert.Equal(t, tc.expectedCPU.Value(), cpuRequest.Value(), "cpu request doesn't match")
} else {
_, ok := resources[0].Requests[apiv1.ResourceCPU]
assert.False(t, ok)
}

memoryRequest := resources[0].Requests[apiv1.ResourceMemory]
assert.Equal(t, tc.expectedMem.Value(), memoryRequest.Value(), "memory request doesn't match")
if len(tc.allowedResources) == 0 || containsResource(tc.allowedResources, apiv1.ResourceMemory) {
memoryRequest := resources[0].Requests[apiv1.ResourceMemory]
assert.Equal(t, tc.expectedMem.Value(), memoryRequest.Value(), "memory request doesn't match")
} else {
_, ok := resources[0].Requests[apiv1.ResourceMemory]
assert.False(t, ok)
}

cpuLimit, cpuLimitPresent := resources[0].Limits[apiv1.ResourceCPU]
if tc.expectedCPULimit == nil {
Expand Down

0 comments on commit a56945e

Please sign in to comment.