This commit is contained in:
Omer Aplatony 2025-09-18 04:45:44 -07:00 committed by GitHub
commit a2f7380c8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 588 additions and 83 deletions

View File

@ -372,6 +372,22 @@ spec:
- Auto
- "Off"
type: string
oomBumpUpRatio:
anyOf:
- type: integer
- type: string
description: oomBumpUpRatio is the ratio to increase memory
when OOM is detected.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
oomMinBumpUp:
anyOf:
- type: integer
- type: string
description: oomMinBumpUp is the minimum increase in memory
when OOM is detected.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
type: array
type: object

View File

@ -48,6 +48,8 @@ _Appears in:_
| `maxAllowed` _[ResourceList](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#resourcelist-v1-core)_ | Specifies the maximum amount of resources that will be recommended<br />for the container. The default is no maximum. | | |
| `controlledResources` _[ResourceName](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#resourcename-v1-core)_ | Specifies the type of recommendations that will be computed<br />(and possibly applied) by VPA.<br />If not specified, the default of [ResourceCPU, ResourceMemory] will be used. | | |
| `controlledValues` _[ContainerControlledValues](#containercontrolledvalues)_ | Specifies which resource values should be controlled.<br />The default is "RequestsAndLimits". | | Enum: [RequestsAndLimits RequestsOnly] <br /> |
| `oomBumpUpRatio` _float_ | OOMBumpUpRatio is the ratio to increase resources when OOM is detected. | | Minimum: 1 <br /> |
| `oomMinBumpUp` _float_ | OOMMinBumpUp is the minimum increase in resources when OOM is detected. | | Minimum: 0 <br /> |
#### ContainerScalingMode

View File

@ -14,7 +14,7 @@ This document is auto-generated from the flag definitions in the VPA admission-c
| `address` | string | ":8944" | The address to expose Prometheus metrics. |
| `alsologtostderr` | | | log to standard error as well as files (no effect when -logtostderr=true) |
| `client-ca-file` | string | "/etc/tls-certs/caCert.pem" | Path to CA PEM file. |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true) |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true)<br>PerVPAConfig=true\|false (ALPHA - default=false) |
| `ignored-vpa-object-namespaces` | string | | A comma-separated list of namespaces to ignore when searching for VPA objects. Leave empty to avoid ignoring any namespaces. These namespaces will not be cleaned by the garbage collector. |
| `kube-api-burst` | float | 100 | QPS burst limit when making requests to Kubernetes apiserver |
| `kube-api-qps` | float | 50 | QPS limit when making requests to Kubernetes apiserver |
@ -68,7 +68,7 @@ This document is auto-generated from the flag definitions in the VPA recommender
| `cpu-integer-post-processor-enabled` | | | Enable the cpu-integer recommendation post processor. The post processor will round up CPU recommendations to a whole CPU for pods which were opted in by setting an appropriate label on VPA object (experimental) |
| `external-metrics-cpu-metric` | string | | ALPHA. Metric to use with external metrics provider for CPU usage. |
| `external-metrics-memory-metric` | string | | ALPHA. Metric to use with external metrics provider for memory usage. |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true) |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true)<br>PerVPAConfig=true\|false (ALPHA - default=false) |
| `history-length` | string | "8d" | How much time back prometheus have to be queried to get historical metrics |
| `history-resolution` | string | "1h" | Resolution at which Prometheus is queried for historical metrics |
| `humanize-memory` | | | DEPRECATED: Convert memory values in recommendations to the highest appropriate SI unit with up to 2 decimal places for better readability. This flag is deprecated and will be removed in a future version. Use --round-memory-bytes instead. |
@ -95,8 +95,8 @@ This document is auto-generated from the flag definitions in the VPA recommender
| `metric-for-pod-labels` | string | "up{job=\"kubernetes-pods\"}" | Which metric to look for pod labels in metrics |
| `min-checkpoints` | int | 10 | Minimum number of checkpoints to write per recommender's main loop. WARNING: this flag is deprecated and doesn't have any effect. It will be removed in a future release. Refer to update-worker-count to influence the minimum number of checkpoints written per loop. |
| `one-output` | severity | | If true, only write logs to their native level (vs also writing to each lower severity level; no effect when -logtostderr=true) |
| `oom-bump-up-ratio` | float | 1.2 | The memory bump up ratio when OOM occurred, default is 1.2. |
| `oom-min-bump-up-bytes` | float | 1.048576e+08 | The minimal increase of memory when OOM occurred in bytes, default is 100 * 1024 * 1024 |
| `oom-bump-up-ratio` | float | 1.2 | Default memory bump up ratio when OOM occurs. This value applies to all VPAs unless overridden in the VPA spec. Default is 1.2. |
| `oom-min-bump-up-bytes` | float | 1.048576e+08 | Default minimal increase of memory (in bytes) when OOM occurs. This value applies to all VPAs unless overridden in the VPA spec. Default is 100 * 1024 * 1024 (100Mi). |
| `password` | string | | The password used in the prometheus server basic auth |
| `pod-label-prefix` | string | "pod_label_" | Which prefix to look for pod labels in metrics |
| `pod-name-label` | string | "kubernetes_pod_name" | Label name to look for pod names |
@ -144,7 +144,7 @@ This document is auto-generated from the flag definitions in the VPA updater cod
| `eviction-rate-burst` | int | 1 | Burst of pods that can be evicted. |
| `eviction-rate-limit` | float | | Number of pods that can be evicted per seconds. A rate limit set to 0 or -1 will disable<br>the rate limiter. (default -1) |
| `eviction-tolerance` | float | 0.5 | Fraction of replica count that can be evicted for update, if more than one pod can be evicted. |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true) |
| `feature-gates` | mapStringBool | | A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:<br>AllAlpha=true\|false (ALPHA - default=false)<br>AllBeta=true\|false (BETA - default=false)<br>InPlaceOrRecreate=true\|false (BETA - default=true)<br>PerVPAConfig=true\|false (ALPHA - default=false) |
| `ignored-vpa-object-namespaces` | string | | A comma-separated list of namespaces to ignore when searching for VPA objects. Leave empty to avoid ignoring any namespaces. These namespaces will not be cleaned by the garbage collector. |
| `in-recommendation-bounds-eviction-lifetime-threshold` | | 12h0m0s | duration Pods that live for at least that long can be evicted even if their request is within the [MinRecommended...MaxRecommended] range |
| `kube-api-burst` | float | 100 | QPS burst limit when making requests to Kubernetes apiserver |

View File

@ -873,26 +873,149 @@ var _ = AdmissionControllerE2eDescribe("Admission-controller", ginkgo.Label("FG:
err := InstallRawVPA(f, validVPA)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Valid VPA object rejected")
ginkgo.By("Setting up invalid VPA object")
// The invalid object differs by name and minAllowed - there is an invalid "requests" field.
invalidVPA := []byte(`{
"kind": "VerticalPodAutoscaler",
"apiVersion": "autoscaling.k8s.io/v1",
"metadata": {"name": "hamster-vpa-invalid"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name":"hamster"
},
"resourcePolicy": {
"containerPolicies": [{"containerName": "*", "minAllowed":{"requests":{"cpu":"50m"}}}]
}
}
}`)
err2 := InstallRawVPA(f, invalidVPA)
gomega.Expect(err2).To(gomega.HaveOccurred(), "Invalid VPA object accepted")
gomega.Expect(err2.Error()).To(gomega.MatchRegexp(`.*admission webhook .*vpa.* denied the request: .*`))
ginkgo.By("Setting up invalid VPA objects")
testCases := []struct {
name string
vpaJSON string
expectedErr string
}{
{
name: "Invalid oomBumpUpRatio (negative value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": -1,
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio: Invalid value: -1: spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio in body should be greater than or equal to 1",
},
{
name: "Invalid oomBumpUpRatio (string value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": "12",
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "json: cannot unmarshal string into Go struct field ContainerResourcePolicy.spec.resourcePolicy.containerPolicies.oomBumpUpRatio of type float64",
},
{
name: "Invalid oomBumpUpRatio (less than 1)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": 0.5,
"oomMinBumpUp": 104857600
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio: Invalid value: 0.5: spec.resourcePolicy.containerPolicies[0].oomBumpUpRatio in body should be greater than or equal to 1",
},
{
name: "Invalid oomMinBumpUp (negative value)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "oom-test-vpa"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "oom-test"
},
"updatePolicy": {
"updateMode": "Auto"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"oomBumpUpRatio": 2,
"oomMinBumpUp": -1
}]
}
}
}`,
expectedErr: "spec.resourcePolicy.containerPolicies[0].oomMinBumpUp: Invalid value: -1: spec.resourcePolicy.containerPolicies[0].oomMinBumpUp in body should be greater than or equal to 0",
},
{
name: "Invalid minAllowed (invalid requests field)",
vpaJSON: `{
"apiVersion": "autoscaling.k8s.io/v1",
"kind": "VerticalPodAutoscaler",
"metadata": {"name": "hamster-vpa-invalid"},
"spec": {
"targetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "hamster"
},
"resourcePolicy": {
"containerPolicies": [{
"containerName": "*",
"minAllowed": {
"requests": {
"cpu": "50m"
}
}
}]
}
}
}`,
expectedErr: "admission webhook .*vpa.* denied the request:",
},
}
for _, tc := range testCases {
ginkgo.By(fmt.Sprintf("Testing %s", tc.name))
err := InstallRawVPA(f, []byte(tc.vpaJSON))
gomega.Expect(err).To(gomega.HaveOccurred(), "Invalid VPA object accepted")
gomega.Expect(err.Error()).To(gomega.MatchRegexp(tc.expectedErr))
}
})
ginkgo.It("reloads the webhook leaf and CA certificate", func(ctx ginkgo.SpecContext) {

View File

@ -37,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework"
framework_deployment "k8s.io/kubernetes/test/e2e/framework/deployment"
@ -612,6 +613,20 @@ func WaitForPodsUpdatedWithoutEviction(f *framework.Framework, initialPods *apiv
return err
}
// checkPerVPAConfigTestsEnabled checks if the PerVPAConfig feature gate is enabled
// in the VPA recommender.
func checkPerVPAConfigTestsEnabled(f *framework.Framework) {
ginkgo.By("Checking PerVPAConfig feature gate is enabled for recommender")
deploy, err := f.ClientSet.AppsV1().Deployments(VpaNamespace).Get(context.TODO(), "vpa-recommender", metav1.GetOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(deploy.Spec.Template.Spec.Containers).To(gomega.HaveLen(1))
vpaRecommenderPod := deploy.Spec.Template.Spec.Containers[0]
gomega.Expect(vpaRecommenderPod.Name).To(gomega.Equal("recommender"))
if !anyContainsSubstring(vpaRecommenderPod.Args, fmt.Sprintf("%s=true", string(features.PerVPAConfig))) {
ginkgo.Skip("Skipping suite: PerVPAConfig feature gate is not enabled for the VPA recommender")
}
}
func anyContainsSubstring(arr []string, substr string) bool {
for _, s := range arr {
if strings.Contains(s, substr) {

View File

@ -24,6 +24,7 @@ import (
autoscaling "k8s.io/api/autoscaling/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
@ -411,6 +412,64 @@ var _ = RecommenderE2eDescribe("VPA CRD object", func() {
})
})
var _ = RecommenderE2eDescribe("OOM with custom config", ginkgo.Label("FG:PerVPAConfig"), func() {
const replicas = 3
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
var (
vpaCRD *vpa_types.VerticalPodAutoscaler
vpaClientSet vpa_clientset.Interface
)
ginkgo.BeforeEach(func() {
checkPerVPAConfigTestsEnabled(f)
ns := f.Namespace.Name
vpaClientSet = getVpaClientSet(f)
ginkgo.By("Setting up a hamster deployment")
runOomingReplicationController(
f.ClientSet,
ns,
"hamster",
replicas)
ginkgo.By("Setting up a VPA CRD")
targetRef := &autoscaling.CrossVersionObjectReference{
APIVersion: "v1",
Kind: "Deployment",
Name: "hamster",
}
containerName := GetHamsterContainerNameByIndex(0)
vpaCRD = test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(targetRef).
WithContainer(containerName).
WithOOMBumpUpRatio(resource.NewQuantity(2, resource.DecimalSI)).
Get()
InstallVPA(f, vpaCRD)
})
ginkgo.It("have memory requests growing with OOMs more than the default", func() {
listOptions := metav1.ListOptions{
LabelSelector: "name=hamster",
FieldSelector: getPodSelectorExcludingDonePodsOrDie(),
}
err := waitForResourceRequestInRangeInPods(
f, oomTestTimeout, listOptions, apiv1.ResourceMemory,
ParseQuantityOrDie("1024Mi"), ParseQuantityOrDie("1024Mi"))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
ginkgo.By("Waiting for recommendation to be filled")
vpa, err := WaitForRecommendationPresent(vpaClientSet, vpaCRD)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(vpa.Status.Recommendation.ContainerRecommendations).Should(gomega.HaveLen(1))
currentMemory := vpa.Status.Recommendation.ContainerRecommendations[0].Target.Memory().Value()
oomReplicationControllerRequestLimit := int64(1024 * 1024 * 1024) // from runOomingReplicationController
defaultBumpMemory := float64(oomReplicationControllerRequestLimit) * 1.2 // DefaultOOMBumpUpRatio
customBumpMemory := float64(oomReplicationControllerRequestLimit) * 2.0 // Custom ratio from VPA config
gomega.Expect(currentMemory).Should(gomega.BeNumerically(">", int64(defaultBumpMemory)),
fmt.Sprintf("Memory recommendation should be at bigger than default bump up ratio (2x). Got: %d, Expected: >= %d", currentMemory, int64(customBumpMemory)))
})
})
func deleteRecommender(c clientset.Interface) error {
namespace := "kube-system"
listOptions := metav1.ListOptions{}

View File

@ -137,6 +137,28 @@ func ValidateVPA(vpa *vpa_types.VerticalPodAutoscaler, isCreate bool) error {
if policy.ContainerName == "" {
return fmt.Errorf("containerPolicies.ContainerName is required")
}
// check that perVPA is on if being used
if err := validatePerVPAFeatureFlag(&policy); err != nil {
return err
}
// Validate OOMBumpUpRatio
if policy.OOMBumpUpRatio != nil {
ratio := float64(policy.OOMBumpUpRatio.MilliValue()) / 1000.0
if ratio < 1.0 {
return fmt.Errorf("OOMBumpUpRatio must be greater than or equal to 1.0, got %v", ratio)
}
}
// Validate OOMMinBumpUp
if policy.OOMMinBumpUp != nil {
minBump := policy.OOMMinBumpUp.Value()
if minBump < 0 {
return fmt.Errorf("OOMMinBumpUp must be greater than or equal to 0, got %v bytes", minBump)
}
}
mode := policy.Mode
if mode != nil {
if _, found := possibleScalingModes[*mode]; !found {
@ -201,3 +223,12 @@ func validateMemoryResolution(val apires.Quantity) error {
}
return nil
}
func validatePerVPAFeatureFlag(policy *vpa_types.ContainerResourcePolicy) error {
featureFlagOn := features.Enabled(features.PerVPAConfig)
perVPA := policy.OOMBumpUpRatio != nil || policy.OOMMinBumpUp != nil
if !featureFlagOn && perVPA {
return fmt.Errorf("OOMBumpUpRatio and OOMMinBumpUp are not supported when feature flag %s is disabled", features.PerVPAConfig)
}
return nil
}

View File

@ -51,6 +51,7 @@ func TestValidateVPA(t *testing.T) {
isCreate bool
expectError error
inPlaceOrRecreateFeatureGateDisabled bool
PerVPAConfigDisabled bool
}{
{
name: "empty update",
@ -319,10 +320,64 @@ func TestValidateVPA(t *testing.T) {
},
},
},
{
name: "per-vpa config active and used",
vpa: vpa_types.VerticalPodAutoscaler{
Spec: vpa_types.VerticalPodAutoscalerSpec{
UpdatePolicy: &vpa_types.PodUpdatePolicy{
UpdateMode: &validUpdateMode,
},
ResourcePolicy: &vpa_types.PodResourcePolicy{
ContainerPolicies: []vpa_types.ContainerResourcePolicy{
{
ContainerName: "loot box",
Mode: &validScalingMode,
MinAllowed: apiv1.ResourceList{
cpu: resource.MustParse("10"),
},
MaxAllowed: apiv1.ResourceList{
cpu: resource.MustParse("100"),
},
OOMBumpUpRatio: resource.NewQuantity(2, resource.DecimalSI),
},
},
},
},
},
PerVPAConfigDisabled: false,
},
{
name: "per-vpa config disabled and used",
vpa: vpa_types.VerticalPodAutoscaler{
Spec: vpa_types.VerticalPodAutoscalerSpec{
UpdatePolicy: &vpa_types.PodUpdatePolicy{
UpdateMode: &validUpdateMode,
},
ResourcePolicy: &vpa_types.PodResourcePolicy{
ContainerPolicies: []vpa_types.ContainerResourcePolicy{
{
ContainerName: "loot box",
Mode: &validScalingMode,
MinAllowed: apiv1.ResourceList{
cpu: resource.MustParse("10"),
},
MaxAllowed: apiv1.ResourceList{
cpu: resource.MustParse("100"),
},
OOMMinBumpUp: resource.NewQuantity(2, resource.DecimalSI),
},
},
},
},
},
PerVPAConfigDisabled: true,
expectError: fmt.Errorf("OOMBumpUpRatio and OOMMinBumpUp are not supported when feature flag PerVPAConfig is disabled"),
},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("test case: %s", tc.name), func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, features.MutableFeatureGate, features.InPlaceOrRecreate, !tc.inPlaceOrRecreateFeatureGateDisabled)
featuregatetesting.SetFeatureGateDuringTest(t, features.MutableFeatureGate, features.PerVPAConfig, !tc.PerVPAConfigDisabled)
err := ValidateVPA(&tc.vpa, tc.isCreate)
if tc.expectError == nil {
assert.NoError(t, err)

View File

@ -19,6 +19,7 @@ package v1
import (
autoscaling "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -224,6 +225,14 @@ type ContainerResourcePolicy struct {
// The default is "RequestsAndLimits".
// +optional
ControlledValues *ContainerControlledValues `json:"controlledValues,omitempty" protobuf:"bytes,6,rep,name=controlledValues"`
// oomBumpUpRatio is the ratio to increase memory when OOM is detected.
// +optional
OOMBumpUpRatio *resource.Quantity `json:"oomBumpUpRatio,omitempty" protobuf:"bytes,7,opt,name=oomBumpUpRatio"`
// oomMinBumpUp is the minimum increase in memory when OOM is detected.
// +optional
OOMMinBumpUp *resource.Quantity `json:"oomMinBumpUp,omitempty" protobuf:"bytes,8,opt,name=oomMinBumpUp"`
}
const (

View File

@ -63,6 +63,16 @@ func (in *ContainerResourcePolicy) DeepCopyInto(out *ContainerResourcePolicy) {
*out = new(ContainerControlledValues)
**out = **in
}
if in.OOMBumpUpRatio != nil {
in, out := &in.OOMBumpUpRatio, &out.OOMBumpUpRatio
x := (*in).DeepCopy()
*out = &x
}
if in.OOMMinBumpUp != nil {
in, out := &in.OOMMinBumpUp, &out.OOMMinBumpUp
x := (*in).DeepCopy()
*out = &x
}
return
}

View File

@ -19,6 +19,7 @@ limitations under the License.
package fake
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
@ -55,9 +56,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset {
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
var opts metav1.ListOptions
if watchActcion, ok := action.(testing.WatchActionImpl); ok {
opts = watchActcion.ListOptions
}
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
watch, err := o.Watch(gvr, ns, opts)
if err != nil {
return false, nil, err
}

View File

@ -50,9 +50,7 @@ func (c *AutoscalingV1Client) VerticalPodAutoscalerCheckpoints(namespace string)
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AutoscalingV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*AutoscalingV1Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@ -89,7 +85,7 @@ func New(c rest.Interface) *AutoscalingV1Client {
return &AutoscalingV1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
func setConfigDefaults(config *rest.Config) {
gv := autoscalingk8siov1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error {
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@ -50,9 +50,7 @@ func (c *AutoscalingV1beta1Client) VerticalPodAutoscalerCheckpoints(namespace st
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AutoscalingV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*AutoscalingV1beta1Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@ -89,7 +85,7 @@ func New(c rest.Interface) *AutoscalingV1beta1Client {
return &AutoscalingV1beta1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
func setConfigDefaults(config *rest.Config) {
gv := autoscalingk8siov1beta1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error {
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@ -50,9 +50,7 @@ func (c *AutoscalingV1beta2Client) VerticalPodAutoscalerCheckpoints(namespace st
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AutoscalingV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*AutoscalingV1beta2Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@ -89,7 +85,7 @@ func New(c rest.Interface) *AutoscalingV1beta2Client {
return &AutoscalingV1beta2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
func setConfigDefaults(config *rest.Config) {
gv := autoscalingk8siov1beta2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error {
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@ -50,9 +50,7 @@ func (c *PocV1alpha1Client) VerticalPodAutoscalerCheckpoints(namespace string) V
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*PocV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*PocV1alpha1Client, error) {
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*PocV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
@ -89,7 +85,7 @@ func New(c rest.Interface) *PocV1alpha1Client {
return &PocV1alpha1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
func setConfigDefaults(config *rest.Config) {
gv := pocautoscalingk8siov1alpha1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error {
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerInformer(client versioned.Interface, namesp
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).List(context.TODO(), options)
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).List(context.Background(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalers(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1.VerticalPodAutoscaler{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerCheckpointInformer(client versioned.Interfa
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).List(context.TODO(), options)
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).List(context.Background(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().VerticalPodAutoscalerCheckpoints(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1.VerticalPodAutoscalerCheckpoint{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerInformer(client versioned.Interface, namesp
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).List(context.TODO(), options)
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalers(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1beta1.VerticalPodAutoscaler{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerCheckpointInformer(client versioned.Interfa
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).List(context.TODO(), options)
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta1().VerticalPodAutoscalerCheckpoints(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1beta1.VerticalPodAutoscalerCheckpoint{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerInformer(client versioned.Interface, namesp
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).List(context.TODO(), options)
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalers(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1beta2.VerticalPodAutoscaler{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerCheckpointInformer(client versioned.Interfa
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).List(context.TODO(), options)
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.TODO(), options)
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1beta2().VerticalPodAutoscalerCheckpoints(namespace).Watch(ctx, options)
},
},
&apisautoscalingk8siov1beta2.VerticalPodAutoscalerCheckpoint{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerInformer(client versioned.Interface, namesp
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).List(context.TODO(), options)
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).Watch(context.TODO(), options)
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalers(namespace).Watch(ctx, options)
},
},
&apispocautoscalingk8siov1alpha1.VerticalPodAutoscaler{},

View File

@ -62,13 +62,25 @@ func NewFilteredVerticalPodAutoscalerCheckpointInformer(client versioned.Interfa
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).List(context.TODO(), options)
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.TODO(), options)
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PocV1alpha1().VerticalPodAutoscalerCheckpoints(namespace).Watch(ctx, options)
},
},
&apispocautoscalingk8siov1alpha1.VerticalPodAutoscalerCheckpoint{},

View File

@ -48,6 +48,15 @@ const (
// InPlaceOrRecreate enables the InPlaceOrRecreate update mode to be used.
// Requires KEP-1287 InPlacePodVerticalScaling feature-gate to be enabled on the cluster.
InPlaceOrRecreate featuregate.Feature = "InPlaceOrRecreate"
// alpha: v1.5.0
// components: recommender, updater
// PerVPAConfig enables the ability to specify component-specific configuration
// parameters at the individual VPA object level. This allows for different
// optimization strategies to be applied to different workloads within the
// same cluster.
PerVPAConfig featuregate.Feature = "PerVPAConfig"
)
// MutableFeatureGate is a mutable, versioned, global FeatureGate.

View File

@ -31,4 +31,7 @@ var defaultVersionedFeatureGates = map[featuregate.Feature]featuregate.Versioned
{Version: version.MustParse("1.4"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.5"), Default: true, PreRelease: featuregate.Beta},
},
PerVPAConfig: {
{Version: version.MustParse("1.5"), Default: false, PreRelease: featuregate.Alpha},
},
}

View File

@ -103,8 +103,8 @@ var (
memoryAggregationIntervalCount = flag.Int64("memory-aggregation-interval-count", model.DefaultMemoryAggregationIntervalCount, `The number of consecutive memory-aggregation-intervals which make up the MemoryAggregationWindowLength which in turn is the period for memory usage aggregation by VPA. In other words, MemoryAggregationWindowLength = memory-aggregation-interval * memory-aggregation-interval-count.`)
memoryHistogramDecayHalfLife = flag.Duration("memory-histogram-decay-half-life", model.DefaultMemoryHistogramDecayHalfLife, `The amount of time it takes a historical memory usage sample to lose half of its weight. In other words, a fresh usage sample is twice as 'important' as one with age equal to the half life period.`)
cpuHistogramDecayHalfLife = flag.Duration("cpu-histogram-decay-half-life", model.DefaultCPUHistogramDecayHalfLife, `The amount of time it takes a historical CPU usage sample to lose half of its weight.`)
oomBumpUpRatio = flag.Float64("oom-bump-up-ratio", model.DefaultOOMBumpUpRatio, `The memory bump up ratio when OOM occurred, default is 1.2.`)
oomMinBumpUp = flag.Float64("oom-min-bump-up-bytes", model.DefaultOOMMinBumpUp, `The minimal increase of memory when OOM occurred in bytes, default is 100 * 1024 * 1024`)
oomBumpUpRatio = flag.Float64("oom-bump-up-ratio", model.DefaultOOMBumpUpRatio, `Default memory bump up ratio when OOM occurs. This value applies to all VPAs unless overridden in the VPA spec. Default is 1.2.`)
oomMinBumpUp = flag.Float64("oom-min-bump-up-bytes", model.DefaultOOMMinBumpUp, `Default minimal increase of memory (in bytes) when OOM occurs. This value applies to all VPAs unless overridden in the VPA spec. Default is 100 * 1024 * 1024 (100Mi).`)
)
// Post processors flags

View File

@ -41,9 +41,12 @@ import (
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/util"
)
@ -82,6 +85,10 @@ type ContainerStateAggregator interface {
// GetUpdateMode returns the update mode of VPA controlling this aggregator,
// nil if aggregator is not autoscaled.
GetUpdateMode() *vpa_types.UpdateMode
// GetOOMBumpUpRatio returns the OOM bump up ratio for this container
GetOOMBumpUpRatio() float64
// GetOOMMinBumpUp returns the minimum OOM bump up value for this container
GetOOMMinBumpUp() float64
}
// AggregateContainerState holds input signals aggregated from a set of containers.
@ -110,6 +117,8 @@ type AggregateContainerState struct {
IsUnderVPA bool
UpdateMode *vpa_types.UpdateMode
ScalingMode *vpa_types.ContainerScalingMode
OOMBumpUpRatio float64
OOMMinBumpUp float64
ControlledResources *[]ResourceName
mutex sync.RWMutex
@ -155,6 +164,16 @@ func (a *AggregateContainerState) GetControlledResources() []ResourceName {
return DefaultControlledResources
}
// GetOOMBumpUpRatio returns the ratio by which to increase the memory recommendation in case of OOM
func (a *AggregateContainerState) GetOOMBumpUpRatio() float64 {
return a.OOMBumpUpRatio
}
// GetOOMMinBumpUp returns the minimum absolute increase in memory recommendation in case of OOM
func (a *AggregateContainerState) GetOOMMinBumpUp() float64 {
return a.OOMMinBumpUp
}
// MarkNotAutoscaled registers that this container state is not controlled by
// a VPA object.
func (a *AggregateContainerState) MarkNotAutoscaled() {
@ -187,6 +206,8 @@ func NewAggregateContainerState() *AggregateContainerState {
AggregateCPUUsage: util.NewDecayingHistogram(config.CPUHistogramOptions, config.CPUHistogramDecayHalfLife),
AggregateMemoryPeaks: util.NewDecayingHistogram(config.MemoryHistogramOptions, config.MemoryHistogramDecayHalfLife),
CreationTime: time.Now(),
OOMBumpUpRatio: config.OOMBumpUpRatio,
OOMMinBumpUp: config.OOMMinBumpUp,
}
}
@ -282,6 +303,13 @@ func (a *AggregateContainerState) isEmpty() bool {
return a.TotalSamplesCount == 0
}
func (a *AggregateContainerState) convertQuantityToFloat64(quantity *resource.Quantity) float64 {
if quantity == nil {
return 0.0
}
return float64(quantity.MilliValue()) / 1000.0
}
// UpdateFromPolicy updates container state scaling mode and controlled resources based on resource
// policy of the VPA object.
func (a *AggregateContainerState) UpdateFromPolicy(resourcePolicy *vpa_types.ContainerResourcePolicy) {
@ -295,6 +323,25 @@ func (a *AggregateContainerState) UpdateFromPolicy(resourcePolicy *vpa_types.Con
if resourcePolicy != nil && resourcePolicy.ControlledResources != nil {
a.ControlledResources = ResourceNamesApiToModel(*resourcePolicy.ControlledResources)
}
// Per VPA components - feature flag "PerVPAConfig" must be enabled
if resourcePolicy != nil {
if resourcePolicy.OOMBumpUpRatio != nil {
if features.Enabled(features.PerVPAConfig) {
a.OOMBumpUpRatio = a.convertQuantityToFloat64(resourcePolicy.OOMBumpUpRatio)
} else {
klog.InfoS("OOMBumpUpRatio is set but PerVPAConfig feature gate is disabled, falling back to default value")
}
}
if resourcePolicy.OOMMinBumpUp != nil {
if features.Enabled(features.PerVPAConfig) {
a.OOMMinBumpUp = a.convertQuantityToFloat64(resourcePolicy.OOMMinBumpUp)
} else {
klog.InfoS("OOMMinBumpUp is set but PerVPAConfig feature gate is disabled, falling back to default value")
}
}
}
}
// AggregateStateByContainerName takes a set of AggregateContainerStates and merge them
@ -363,3 +410,17 @@ func (p *ContainerStateAggregatorProxy) GetScalingMode() *vpa_types.ContainerSca
aggregator := p.cluster.findOrCreateAggregateContainerState(p.containerID)
return aggregator.GetScalingMode()
}
// GetOOMMinBumpUp returns the minimum amount to bump up resources when OOM is detected.
// This implementation returns 0 to satisfy the interface requirement.
func (p *ContainerStateAggregatorProxy) GetOOMMinBumpUp() float64 {
aggregator := p.cluster.findOrCreateAggregateContainerState(p.containerID)
return aggregator.GetOOMMinBumpUp()
}
// GetOOMBumpUpRatio returns the ratio to increase resources when OOM is detected.
// This implementation returns 0 to satisfy the interface requirement.
func (p *ContainerStateAggregatorProxy) GetOOMBumpUpRatio() float64 {
aggregator := p.cluster.findOrCreateAggregateContainerState(p.containerID)
return aggregator.GetOOMBumpUpRatio()
}

View File

@ -125,6 +125,18 @@ func (container *ContainerState) GetMaxMemoryPeak() ResourceAmount {
return ResourceAmountMax(container.memoryPeak, container.oomPeak)
}
// GetOOMBumpUpRatio returns the ratio to increase resources when OOM is detected.
// It delegates to the aggregator's implementation.
func (container *ContainerState) GetOOMBumpUpRatio() float64 {
return container.aggregator.GetOOMBumpUpRatio()
}
// GetOOMMinBumpUp returns the minimum amount to bump up resources when OOM is detected.
// It delegates to the aggregator's implementation.
func (container *ContainerState) GetOOMMinBumpUp() float64 {
return container.aggregator.GetOOMMinBumpUp()
}
func (container *ContainerState) addMemorySample(sample *ContainerUsageSample, isOOM bool) bool {
ts := sample.MeasureStart
// We always process OOM samples.
@ -183,14 +195,16 @@ func (container *ContainerState) addMemorySample(sample *ContainerUsageSample, i
// RecordOOM adds info regarding OOM event in the model as an artificial memory sample.
func (container *ContainerState) RecordOOM(timestamp time.Time, requestedMemory ResourceAmount) error {
// Discard old OOM
if timestamp.Before(container.WindowEnd.Add(-1 * GetAggregationsConfig().MemoryAggregationInterval)) {
config := GetAggregationsConfig()
// TODO(omerap12): remove MemoryAggregationInterval to per-container configuration as well
if timestamp.Before(container.WindowEnd.Add(-1 * config.MemoryAggregationInterval)) {
return fmt.Errorf("OOM event will be discarded - it is too old (%v)", timestamp)
}
// Get max of the request and the recent usage-based memory peak.
// Omitting oomPeak here to protect against recommendation running too high on subsequent OOMs.
memoryUsed := ResourceAmountMax(requestedMemory, container.memoryPeak)
memoryNeeded := ResourceAmountMax(memoryUsed+MemoryAmountFromBytes(GetAggregationsConfig().OOMMinBumpUp),
ScaleResource(memoryUsed, GetAggregationsConfig().OOMBumpUpRatio))
memoryNeeded := ResourceAmountMax(memoryUsed+MemoryAmountFromBytes(container.GetOOMMinBumpUp()),
ScaleResource(memoryUsed, container.GetOOMBumpUpRatio()))
oomMemorySample := ContainerUsageSample{
MeasureStart: timestamp,

View File

@ -61,6 +61,8 @@ func newContainerTest() ContainerTest {
aggregateContainerState := &AggregateContainerState{
AggregateCPUUsage: mockCPUHistogram,
AggregateMemoryPeaks: mockMemoryHistogram,
OOMBumpUpRatio: 1.2, // Default value, can be adjusted as needed
OOMMinBumpUp: 1.048576e+08, // Default value (100Mi), can be adjusted as needed
}
container := &ContainerState{
Request: TestRequest,

View File

@ -21,6 +21,7 @@ import (
autoscaling "k8s.io/api/autoscaling/v1"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
@ -47,6 +48,8 @@ type VerticalPodAutoscalerBuilder interface {
WithGroupVersion(gv meta.GroupVersion) VerticalPodAutoscalerBuilder
WithEvictionRequirements([]*vpa_types.EvictionRequirement) VerticalPodAutoscalerBuilder
WithMinReplicas(minReplicas *int32) VerticalPodAutoscalerBuilder
WithOOMBumpUpRatio(ratio *resource.Quantity) VerticalPodAutoscalerBuilder
WithOOMMinBumpUp(minBumpUp *resource.Quantity) VerticalPodAutoscalerBuilder
AppendCondition(conditionType vpa_types.VerticalPodAutoscalerConditionType,
status core.ConditionStatus, reason, message string, lastTransitionTime time.Time) VerticalPodAutoscalerBuilder
AppendRecommendation(vpa_types.RecommendedContainerResources) VerticalPodAutoscalerBuilder
@ -87,6 +90,8 @@ type verticalPodAutoscalerBuilder struct {
targetRef *autoscaling.CrossVersionObjectReference
appendedRecommendations []vpa_types.RecommendedContainerResources
recommender string
oomBumpUpRatio *resource.Quantity
oomMinBumpUp *resource.Quantity
}
func (b *verticalPodAutoscalerBuilder) WithName(vpaName string) VerticalPodAutoscalerBuilder {
@ -214,6 +219,18 @@ func (b *verticalPodAutoscalerBuilder) WithMinReplicas(minReplicas *int32) Verti
return &c
}
func (b *verticalPodAutoscalerBuilder) WithOOMBumpUpRatio(ratio *resource.Quantity) VerticalPodAutoscalerBuilder {
c := *b
c.oomBumpUpRatio = ratio
return &c
}
func (b *verticalPodAutoscalerBuilder) WithOOMMinBumpUp(minBumpUp *resource.Quantity) VerticalPodAutoscalerBuilder {
c := *b
c.oomMinBumpUp = minBumpUp
return &c
}
func (b *verticalPodAutoscalerBuilder) AppendCondition(conditionType vpa_types.VerticalPodAutoscalerConditionType,
status core.ConditionStatus, reason, message string, lastTransitionTime time.Time) VerticalPodAutoscalerBuilder {
c := *b
@ -250,6 +267,8 @@ func (b *verticalPodAutoscalerBuilder) Get() *vpa_types.VerticalPodAutoscaler {
MaxAllowed: b.maxAllowed[containerName],
ControlledValues: b.controlledValues[containerName],
Mode: &scalingModeAuto,
OOMBumpUpRatio: b.oomBumpUpRatio,
OOMMinBumpUp: b.oomMinBumpUp,
}
if scalingMode, ok := b.scalingMode[containerName]; ok {
containerResourcePolicy.Mode = scalingMode