Merge 697934c96b into a9292351c3
This commit is contained in:
commit
a2f7380c8b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue