move targetCPUPercentile into a flag

This commit is contained in:
Matthias Bertschy 2022-04-07 11:32:57 +02:00
parent b718e74780
commit 690dcd161e
6 changed files with 152 additions and 25 deletions

View File

@ -51,14 +51,16 @@ import (
)
const (
evictionWatchRetryWait = 10 * time.Second
evictionWatchJitterFactor = 0.5
scaleCacheLoopPeriod time.Duration = 7 * time.Second
scaleCacheEntryLifetime time.Duration = time.Hour
scaleCacheEntryFreshnessTime time.Duration = 10 * time.Minute
scaleCacheEntryJitterFactor float64 = 1.
defaultResyncPeriod time.Duration = 10 * time.Minute
defaultRecommenderName = "default"
evictionWatchRetryWait = 10 * time.Second
evictionWatchJitterFactor = 0.5
scaleCacheLoopPeriod = 7 * time.Second
scaleCacheEntryLifetime = time.Hour
scaleCacheEntryFreshnessTime = 10 * time.Minute
scaleCacheEntryJitterFactor float64 = 1.
defaultResyncPeriod = 10 * time.Minute
// DefaultRecommenderName designates the recommender that will handle VPA objects which don't specify
// recommender name explicitly (and so implicitly specify that the default recommender should handle them)
DefaultRecommenderName = "default"
)
// ClusterStateFeeder can update state of ClusterState object.
@ -94,6 +96,7 @@ type ClusterStateFeederFactory struct {
SelectorFetcher target.VpaTargetSelectorFetcher
MemorySaveMode bool
ControllerFetcher controllerfetcher.ControllerFetcher
RecommenderName string
}
// Make creates new ClusterStateFeeder with internal data providers, based on kube client.
@ -109,12 +112,13 @@ func (m ClusterStateFeederFactory) Make() *clusterStateFeeder {
selectorFetcher: m.SelectorFetcher,
memorySaveMode: m.MemorySaveMode,
controllerFetcher: m.ControllerFetcher,
recommenderName: m.RecommenderName,
}
}
// NewClusterStateFeeder creates new ClusterStateFeeder with internal data providers, based on kube client config.
// Deprecated; Use ClusterStateFeederFactory instead.
func NewClusterStateFeeder(config *rest.Config, clusterState *model.ClusterState, memorySave bool, namespace, metricsClientName string) ClusterStateFeeder {
func NewClusterStateFeeder(config *rest.Config, clusterState *model.ClusterState, memorySave bool, namespace, metricsClientName string, recommenderName string) ClusterStateFeeder {
kubeClient := kube_client.NewForConfigOrDie(config)
podLister, oomObserver := NewPodListerAndOOMObserver(kubeClient, namespace)
factory := informers.NewSharedInformerFactoryWithOptions(kubeClient, defaultResyncPeriod, informers.WithNamespace(namespace))
@ -131,6 +135,7 @@ func NewClusterStateFeeder(config *rest.Config, clusterState *model.ClusterState
SelectorFetcher: target.NewVpaTargetSelectorFetcher(config, kubeClient, factory),
MemorySaveMode: memorySave,
ControllerFetcher: controllerFetcher,
RecommenderName: recommenderName,
}.Make()
}
@ -224,6 +229,7 @@ type clusterStateFeeder struct {
selectorFetcher target.VpaTargetSelectorFetcher
memorySaveMode bool
controllerFetcher controllerfetcher.ControllerFetcher
recommenderName string
}
func (feeder *clusterStateFeeder) InitFromHistoryProvider(historyProvider history.HistoryProvider) {
@ -349,10 +355,20 @@ func filterVPAs(feeder *clusterStateFeeder, allVpaCRDs []*vpa_types.VerticalPodA
klog.V(3).Infof("Start selecting the vpaCRDs.")
var vpaCRDs []*vpa_types.VerticalPodAutoscaler
for _, vpaCRD := range allVpaCRDs {
currentRecommenderName := defaultRecommenderName
if !implicitDefaultRecommender(vpaCRD.Spec.Recommenders) && !selectsRecommender(vpaCRD.Spec.Recommenders, &currentRecommenderName) {
klog.V(6).Infof("Ignoring vpaCRD %s in namespace %s as current recommender's name %v doesn't appear among its recommenders", vpaCRD.Name, vpaCRD.Namespace, currentRecommenderName)
continue
if feeder.recommenderName == DefaultRecommenderName {
if !implicitDefaultRecommender(vpaCRD.Spec.Recommenders) && !selectsRecommender(vpaCRD.Spec.Recommenders, &feeder.recommenderName) {
klog.V(6).Infof("Ignoring vpaCRD %s in namespace %s as current recommender's name %v doesn't appear among its recommenders", vpaCRD.Name, vpaCRD.Namespace, feeder.recommenderName)
continue
}
} else {
if implicitDefaultRecommender(vpaCRD.Spec.Recommenders) {
klog.V(6).Infof("Ignoring vpaCRD %s in namespace %s as %v recommender doesn't process CRDs implicitly destined to %v recommender", vpaCRD.Name, vpaCRD.Namespace, feeder.recommenderName, DefaultRecommenderName)
continue
}
if !selectsRecommender(vpaCRD.Spec.Recommenders, &feeder.recommenderName) {
klog.V(6).Infof("Ignoring vpaCRD %s in namespace %s as current recommender's name %v doesn't appear among its recommenders", vpaCRD.Name, vpaCRD.Namespace, feeder.recommenderName)
continue
}
}
vpaCRDs = append(vpaCRDs, vpaCRD)
}

View File

@ -41,7 +41,7 @@ type fakeControllerFetcher struct {
err error
}
func (f *fakeControllerFetcher) FindTopMostWellKnownOrScalable(controller *controllerfetcher.ControllerKeyWithAPIVersion) (*controllerfetcher.ControllerKeyWithAPIVersion, error) {
func (f *fakeControllerFetcher) FindTopMostWellKnownOrScalable(_ *controllerfetcher.ControllerKeyWithAPIVersion) (*controllerfetcher.ControllerKeyWithAPIVersion, error) {
return f.key, f.err
}
@ -52,6 +52,8 @@ func parseLabelSelector(selector string) labels.Selector {
}
var (
recommenderName = "name"
empty = ""
unsupportedConditionTextFromFetcher = "Cannot read targetRef. Reason: targetRef not defined"
unsupportedConditionNoExtraText = "Cannot read targetRef"
unsupportedConditionNoTargetRef = "Cannot read targetRef"
@ -80,6 +82,9 @@ func TestLoadPods(t *testing.T) {
expectedSelector labels.Selector
expectedConfigUnsupported *string
expectedConfigDeprecated *string
expectedVpaFetch bool
recommenderName *string
recommender string
}
testCases := []testCase{
@ -90,6 +95,7 @@ func TestLoadPods(t *testing.T) {
expectedSelector: labels.Nothing(),
expectedConfigUnsupported: &unsupportedConditionTextFromFetcher,
expectedConfigDeprecated: nil,
expectedVpaFetch: true,
},
{
name: "also no selector but no error",
@ -98,6 +104,7 @@ func TestLoadPods(t *testing.T) {
expectedSelector: labels.Nothing(),
expectedConfigUnsupported: &unsupportedConditionNoExtraText,
expectedConfigDeprecated: nil,
expectedVpaFetch: true,
},
{
name: "targetRef selector",
@ -119,6 +126,7 @@ func TestLoadPods(t *testing.T) {
expectedSelector: parseLabelSelector("app = test"),
expectedConfigUnsupported: nil,
expectedConfigDeprecated: nil,
expectedVpaFetch: true,
},
{
name: "no targetRef",
@ -127,6 +135,7 @@ func TestLoadPods(t *testing.T) {
expectedSelector: labels.Nothing(),
expectedConfigUnsupported: nil,
expectedConfigDeprecated: nil,
expectedVpaFetch: true,
},
{
name: "can't decide if top-level-ref",
@ -139,6 +148,7 @@ func TestLoadPods(t *testing.T) {
APIVersion: apiVersion,
},
expectedConfigUnsupported: &unsupportedConditionNoTargetRef,
expectedVpaFetch: true,
},
{
name: "non-top-level targetRef",
@ -159,6 +169,7 @@ func TestLoadPods(t *testing.T) {
ApiVersion: apiVersion,
},
expectedConfigUnsupported: &unsupportedTargetRefHasParent,
expectedVpaFetch: true,
},
{
name: "error checking if top-level-ref",
@ -171,6 +182,7 @@ func TestLoadPods(t *testing.T) {
APIVersion: "taxonomy",
},
expectedConfigUnsupported: &unsupportedConditionMudaMudaMuda,
expectedVpaFetch: true,
findTopMostWellKnownOrScalableError: fmt.Errorf("muda muda muda"),
},
{
@ -192,6 +204,78 @@ func TestLoadPods(t *testing.T) {
ApiVersion: apiVersion,
},
expectedConfigUnsupported: nil,
expectedVpaFetch: true,
},
{
name: "no recommenderName",
selector: parseLabelSelector("app = test"),
fetchSelectorError: nil,
targetRef: &autoscalingv1.CrossVersionObjectReference{
Kind: kind,
Name: name1,
APIVersion: apiVersion,
},
topMostWellKnownOrScalableKey: &controllerfetcher.ControllerKeyWithAPIVersion{
ControllerKey: controllerfetcher.ControllerKey{
Kind: kind,
Name: name1,
Namespace: namespace,
},
ApiVersion: apiVersion,
},
expectedSelector: parseLabelSelector("app = test"),
expectedConfigUnsupported: nil,
expectedConfigDeprecated: nil,
expectedVpaFetch: false,
recommenderName: &empty,
},
{
name: "recommenderName doesn't match recommender",
selector: parseLabelSelector("app = test"),
fetchSelectorError: nil,
targetRef: &autoscalingv1.CrossVersionObjectReference{
Kind: kind,
Name: name1,
APIVersion: apiVersion,
},
topMostWellKnownOrScalableKey: &controllerfetcher.ControllerKeyWithAPIVersion{
ControllerKey: controllerfetcher.ControllerKey{
Kind: kind,
Name: name1,
Namespace: namespace,
},
ApiVersion: apiVersion,
},
expectedSelector: parseLabelSelector("app = test"),
expectedConfigUnsupported: nil,
expectedConfigDeprecated: nil,
expectedVpaFetch: false,
recommenderName: &recommenderName,
recommender: "other",
},
{
name: "recommenderName matches recommender",
selector: parseLabelSelector("app = test"),
fetchSelectorError: nil,
targetRef: &autoscalingv1.CrossVersionObjectReference{
Kind: kind,
Name: name1,
APIVersion: apiVersion,
},
topMostWellKnownOrScalableKey: &controllerfetcher.ControllerKeyWithAPIVersion{
ControllerKey: controllerfetcher.ControllerKey{
Kind: kind,
Name: name1,
Namespace: namespace,
},
ApiVersion: apiVersion,
},
expectedSelector: parseLabelSelector("app = test"),
expectedConfigUnsupported: nil,
expectedConfigDeprecated: nil,
expectedVpaFetch: true,
recommenderName: &recommenderName,
recommender: recommenderName,
},
}
@ -201,7 +285,11 @@ func TestLoadPods(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
vpa := test.VerticalPodAutoscaler().WithName("testVpa").WithContainer("container").WithNamespace("testNamespace").WithTargetRef(tc.targetRef).Get()
vpaBuilder := test.VerticalPodAutoscaler().WithName("testVpa").WithContainer("container").WithNamespace("testNamespace").WithTargetRef(tc.targetRef)
if tc.recommender != "" {
vpaBuilder = vpaBuilder.WithRecommender(tc.recommender)
}
vpa := vpaBuilder.Get()
vpaLister := &test.VerticalPodAutoscalerListerMock{}
vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpa}, nil)
@ -217,8 +305,15 @@ func TestLoadPods(t *testing.T) {
err: tc.findTopMostWellKnownOrScalableError,
},
}
if tc.recommenderName == nil {
clusterStateFeeder.recommenderName = DefaultRecommenderName
} else {
clusterStateFeeder.recommenderName = *tc.recommenderName
}
targetSelectorFetcher.EXPECT().Fetch(vpa).Return(tc.selector, tc.fetchSelectorError)
if tc.expectedVpaFetch {
targetSelectorFetcher.EXPECT().Fetch(vpa).Return(tc.selector, tc.fetchSelectorError)
}
clusterStateFeeder.LoadVPAs()
vpaID := model.VpaID{
@ -226,6 +321,10 @@ func TestLoadPods(t *testing.T) {
VpaName: vpa.Name,
}
if !tc.expectedVpaFetch {
assert.NotContains(t, clusterState.Vpas, vpaID)
return
}
assert.Contains(t, clusterState.Vpas, vpaID)
storedVpa := clusterState.Vpas[vpaID]
if tc.expectedSelector != nil {

View File

@ -26,6 +26,7 @@ var (
safetyMarginFraction = flag.Float64("recommendation-margin-fraction", 0.15, `Fraction of usage added as the safety margin to the recommended request`)
podMinCPUMillicores = flag.Float64("pod-recommendation-min-cpu-millicores", 25, `Minimum CPU recommendation for a pod`)
podMinMemoryMb = flag.Float64("pod-recommendation-min-memory-mb", 250, `Minimum memory recommendation for a pod`)
targetCPUPercentile = flag.Float64("target-cpu-percentile", 0.9, "CPU usage percentile that will be used as a base for CPU target recommendation. Doesn't affect CPU lower bound, CPU upper bound nor memory recommendations.")
)
// PodResourceRecommender computes resource recommendation for a Vpa object.
@ -99,7 +100,6 @@ func FilterControlledResources(estimation model.Resources, controlledResources [
// CreatePodResourceRecommender returns the primary recommender.
func CreatePodResourceRecommender() PodResourceRecommender {
targetCPUPercentile := 0.9
lowerBoundCPUPercentile := 0.5
upperBoundCPUPercentile := 0.95
@ -107,7 +107,7 @@ func CreatePodResourceRecommender() PodResourceRecommender {
lowerBoundMemoryPeaksPercentile := 0.5
upperBoundMemoryPeaksPercentile := 0.95
targetEstimator := NewPercentileEstimator(targetCPUPercentile, targetMemoryPeaksPercentile)
targetEstimator := NewPercentileEstimator(*targetCPUPercentile, targetMemoryPeaksPercentile)
lowerBoundEstimator := NewPercentileEstimator(lowerBoundCPUPercentile, lowerBoundMemoryPeaksPercentile)
upperBoundEstimator := NewPercentileEstimator(upperBoundCPUPercentile, upperBoundMemoryPeaksPercentile)

View File

@ -18,6 +18,7 @@ package main
import (
"flag"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input"
"time"
apiv1 "k8s.io/api/core/v1"
@ -32,10 +33,8 @@ import (
klog "k8s.io/klog/v2"
)
// DefaultRecommenderName denotes the current recommender name as the "default" one.
const DefaultRecommenderName = "default"
var (
recommenderName = flag.String("recommender-name", input.DefaultRecommenderName, "Set the recommender name. Recommender will generate recommendations for VPAs that configure the same recommender name. If the recommender name is left as default it will also generate recommendations that don't explicitly specify recommender. You shouldn't run two recommenders with the same name in a cluster.")
metricsFetcherInterval = flag.Duration("recommender-interval", 1*time.Minute, `How often metrics should be fetched`)
checkpointsGCInterval = flag.Duration("checkpoints-gc-interval", 10*time.Minute, `How often orphaned checkpoints should be garbage collected`)
prometheusAddress = flag.String("prometheus-address", "", `Where to reach for Prometheus metrics`)
@ -71,7 +70,7 @@ var (
func main() {
klog.InitFlags(nil)
kube_flag.InitFlags()
klog.V(1).Infof("Vertical Pod Autoscaler %s Recommender: %v", common.VerticalPodAutoscalerVersion, DefaultRecommenderName)
klog.V(1).Infof("Vertical Pod Autoscaler %s Recommender: %v", common.VerticalPodAutoscalerVersion, recommenderName)
config := common.CreateKubeConfigOrDie(*kubeconfig, float32(*kubeApiQps), int(*kubeApiBurst))
@ -83,7 +82,7 @@ func main() {
metrics_quality.Register()
useCheckpoints := *storage != "prometheus"
recommender := routines.NewRecommender(config, *checkpointsGCInterval, useCheckpoints, *vpaObjectNamespace)
recommender := routines.NewRecommender(config, *checkpointsGCInterval, useCheckpoints, *vpaObjectNamespace, *recommenderName)
promQueryTimeout, err := time.ParseDuration(*queryTimeout)
if err != nil {

View File

@ -254,14 +254,14 @@ func (c RecommenderFactory) Make() Recommender {
// NewRecommender creates a new recommender instance.
// Dependencies are created automatically.
// Deprecated; use RecommenderFactory instead.
func NewRecommender(config *rest.Config, checkpointsGCInterval time.Duration, useCheckpoints bool, namespace string) Recommender {
func NewRecommender(config *rest.Config, checkpointsGCInterval time.Duration, useCheckpoints bool, namespace string, recommenderName string) Recommender {
clusterState := model.NewClusterState(AggregateContainerStateGCInterval)
kubeClient := kube_client.NewForConfigOrDie(config)
factory := informers.NewSharedInformerFactoryWithOptions(kubeClient, defaultResyncPeriod, informers.WithNamespace(namespace))
controllerFetcher := controllerfetcher.NewControllerFetcher(config, kubeClient, factory, scaleCacheEntryFreshnessTime, scaleCacheEntryLifetime, scaleCacheEntryJitterFactor)
return RecommenderFactory{
ClusterState: clusterState,
ClusterStateFeeder: input.NewClusterStateFeeder(config, clusterState, *memorySaver, namespace, "default-metrics-client"),
ClusterStateFeeder: input.NewClusterStateFeeder(config, clusterState, *memorySaver, namespace, "default-metrics-client", recommenderName),
ControllerFetcher: controllerFetcher,
CheckpointWriter: checkpoint.NewCheckpointWriter(clusterState, vpa_clientset.NewForConfigOrDie(config).AutoscalingV1()),
VpaClient: vpa_clientset.NewForConfigOrDie(config).AutoscalingV1(),

View File

@ -40,6 +40,7 @@ type VerticalPodAutoscalerBuilder interface {
WithTargetRef(targetRef *autoscaling.CrossVersionObjectReference) VerticalPodAutoscalerBuilder
WithUpperBound(cpu, memory string) VerticalPodAutoscalerBuilder
WithAnnotations(map[string]string) VerticalPodAutoscalerBuilder
WithRecommender(string2 string) VerticalPodAutoscalerBuilder
AppendCondition(conditionType vpa_types.VerticalPodAutoscalerConditionType,
status core.ConditionStatus, reason, message string, lastTransitionTime time.Time) VerticalPodAutoscalerBuilder
AppendRecommendation(vpa_types.RecommendedContainerResources) VerticalPodAutoscalerBuilder
@ -70,6 +71,7 @@ type verticalPodAutoscalerBuilder struct {
annotations map[string]string
targetRef *autoscaling.CrossVersionObjectReference
appendedRecommendations []vpa_types.RecommendedContainerResources
recommender string
}
func (b *verticalPodAutoscalerBuilder) WithName(vpaName string) VerticalPodAutoscalerBuilder {
@ -153,6 +155,12 @@ func (b *verticalPodAutoscalerBuilder) WithAnnotations(annotations map[string]st
return &c
}
func (b *verticalPodAutoscalerBuilder) WithRecommender(recommender string) VerticalPodAutoscalerBuilder {
c := *b
c.recommender = recommender
return &c
}
func (b *verticalPodAutoscalerBuilder) AppendCondition(conditionType vpa_types.VerticalPodAutoscalerConditionType,
status core.ConditionStatus, reason, message string, lastTransitionTime time.Time) VerticalPodAutoscalerBuilder {
c := *b
@ -175,6 +183,10 @@ func (b *verticalPodAutoscalerBuilder) Get() *vpa_types.VerticalPodAutoscaler {
if b.containerName == "" {
panic("Must call WithContainer() before Get()")
}
var recommenders []*vpa_types.VerticalPodAutoscalerRecommenderSelector
if b.recommender != "" {
recommenders = []*vpa_types.VerticalPodAutoscalerRecommenderSelector{{Name: b.recommender}}
}
resourcePolicy := vpa_types.PodResourcePolicy{ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
ContainerName: b.containerName,
MinAllowed: b.minAllowed,
@ -196,6 +208,7 @@ func (b *verticalPodAutoscalerBuilder) Get() *vpa_types.VerticalPodAutoscaler {
UpdatePolicy: b.updatePolicy,
ResourcePolicy: &resourcePolicy,
TargetRef: b.targetRef,
Recommenders: recommenders,
},
Status: vpa_types.VerticalPodAutoscalerStatus{
Recommendation: recommendation,