Skip to content

Commit 51e419d

Browse files
committed
feat: add evictAfterOOMThreshold per vpa
Signed-off-by: Omer Aplatony <[email protected]>
1 parent ed74077 commit 51e419d

File tree

14 files changed

+465
-15
lines changed

14 files changed

+465
-15
lines changed

vertical-pod-autoscaler/charts/vertical-pod-autoscaler/crds/vpa-v1-crd-gen.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ spec:
254254
- jsonPath: .metadata.creationTimestamp
255255
name: Age
256256
type: date
257+
- jsonPath: .spec.updatePolicy.minReplicas
258+
name: MinReplicas
259+
priority: 1
260+
type: integer
261+
- jsonPath: .spec.updatePolicy.evictAfterOOMThreshold
262+
name: OOMThreshold
263+
priority: 1
264+
type: string
257265
name: v1
258266
schema:
259267
openAPIV3Schema:
@@ -425,6 +433,14 @@ spec:
425433
If not specified, all fields in the `PodUpdatePolicy` are set to their
426434
default values.
427435
properties:
436+
evictAfterOOMThreshold:
437+
description: |-
438+
EvictAfterOOMThreshold specifies the time to wait after an OOM event before
439+
considering the pod for eviction. Pods that have OOMed in less than this threshold
440+
since start will be evicted.
441+
format: duration
442+
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
443+
type: string
428444
evictionRequirements:
429445
description: |-
430446
EvictionRequirements is a list of EvictionRequirements that need to
@@ -478,6 +494,10 @@ spec:
478494
- Auto
479495
type: string
480496
type: object
497+
x-kubernetes-validations:
498+
- message: evictAfterOOMThreshold must be greater than 0
499+
rule: '!has(self.evictAfterOOMThreshold) || duration(self.evictAfterOOMThreshold)
500+
>= duration(''0s'')'
481501
required:
482502
- targetRef
483503
type: object

vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ spec:
254254
- jsonPath: .metadata.creationTimestamp
255255
name: Age
256256
type: date
257+
- jsonPath: .spec.updatePolicy.minReplicas
258+
name: MinReplicas
259+
priority: 1
260+
type: integer
261+
- jsonPath: .spec.updatePolicy.evictAfterOOMThreshold
262+
name: OOMThreshold
263+
priority: 1
264+
type: string
257265
name: v1
258266
schema:
259267
openAPIV3Schema:
@@ -425,6 +433,14 @@ spec:
425433
If not specified, all fields in the `PodUpdatePolicy` are set to their
426434
default values.
427435
properties:
436+
evictAfterOOMThreshold:
437+
description: |-
438+
EvictAfterOOMThreshold specifies the time to wait after an OOM event before
439+
considering the pod for eviction. Pods that have OOMed in less than this threshold
440+
since start will be evicted.
441+
format: duration
442+
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
443+
type: string
428444
evictionRequirements:
429445
description: |-
430446
EvictionRequirements is a list of EvictionRequirements that need to
@@ -478,6 +494,10 @@ spec:
478494
- Auto
479495
type: string
480496
type: object
497+
x-kubernetes-validations:
498+
- message: evictAfterOOMThreshold must be greater than 0
499+
rule: '!has(self.evictAfterOOMThreshold) || duration(self.evictAfterOOMThreshold)
500+
>= duration(''0s'')'
481501
required:
482502
- targetRef
483503
type: object

vertical-pod-autoscaler/docs/flags.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ This document is auto-generated from the flag definitions in the VPA updater cod
140140
| `add-dir-header` | | | If true, adds the file directory to the header of the log messages |
141141
| `address` | string | ":8943" | The address to expose Prometheus metrics. |
142142
| `alsologtostderr` | | | log to standard error as well as files (no effect when -logtostderr=true) |
143-
| `evict-after-oom-threshold` | | 10m0s | duration Evict pod that has OOMed in less than evict-after-oom-threshold since start. |
143+
| `evict-after-oom-threshold` | | 10m0s | duration The default duration to evict pod that has OOMed in less than evict-after-oom-threshold since start. |
144144
| `eviction-rate-burst` | int | 1 | Burst of pods that can be evicted. |
145145
| `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) |
146146
| `eviction-tolerance` | float | 0.5 | Fraction of replica count that can be evicted for update, if more than one pod can be evicted. |
@@ -174,4 +174,3 @@ This document is auto-generated from the flag definitions in the VPA updater cod
174174
| `v,` | | : 4 | , --v Level set the log level verbosity (default 4) |
175175
| `vmodule` | moduleSpec | | comma-separated list of pattern=N settings for file-filtered logging |
176176
| `vpa-object-namespace` | string | | Specifies the namespace to search for VPA objects. Leave empty to include all namespaces. If provided, the garbage collector will only clean this namespace. |
177-

vertical-pod-autoscaler/e2e/utils/common.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ var RecommenderLabels = map[string]string{"app": "vpa-recommender"}
7070
// HamsterLabels are labels of hamster app
7171
var HamsterLabels = map[string]string{"app": "hamster"}
7272

73+
// OOMLabels are labels for OOM test pods
74+
var OOMLabels = map[string]string{"app": "oom-test"}
75+
7376
// SIGDescribe adds sig-autoscaling tag to test description.
7477
// Takes args that are passed to ginkgo.Describe.
7578
func SIGDescribe(scenario, name string, args ...interface{}) bool {
@@ -226,7 +229,7 @@ func NewNHamstersDeployment(f *framework.Framework, n int) *appsv1.Deployment {
226229
DefaultHamsterReplicas, /*replicas*/
227230
HamsterLabels, /*podLabels*/
228231
GetHamsterContainerNameByIndex(0), /*imageName*/
229-
"registry.k8s.io/ubuntu-slim:0.14", /*image*/
232+
"ubuntu:25.10", /*image*/
230233
appsv1.RollingUpdateDeploymentStrategyType, /*strategyType*/
231234
)
232235
d.ObjectMeta.Namespace = f.Namespace.Name

vertical-pod-autoscaler/e2e/v1/admission_controller.go

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,3 +1111,238 @@ func waitForVpaWebhookRegistration(f *framework.Framework) {
11111111
return false
11121112
}, 3*time.Minute, 5*time.Second).Should(gomega.BeTrue(), "Webhook was not registered in the cluster")
11131113
}
1114+
1115+
var _ = AdmissionControllerE2eDescribe("Admission-controller", ginkgo.Label("FG:PerVPAConfig"), func() {
1116+
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
1117+
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
1118+
1119+
ginkgo.BeforeEach(func() {
1120+
waitForVpaWebhookRegistration(f)
1121+
})
1122+
1123+
ginkgo.It("accepts valid and rejects invalid VPA object", func() {
1124+
ginkgo.By("Setting up valid VPA object")
1125+
validVPA := []byte(`{
1126+
"kind": "VerticalPodAutoscaler",
1127+
"apiVersion": "autoscaling.k8s.io/v1",
1128+
"metadata": {"name": "hamster-vpa-valid"},
1129+
"spec": {
1130+
"targetRef": {
1131+
"apiVersion": "apps/v1",
1132+
"kind": "Deployment",
1133+
"name":"hamster"
1134+
},
1135+
"resourcePolicy": {
1136+
"containerPolicies": [{"containerName": "*", "minAllowed":{"cpu":"50m"}}]
1137+
}
1138+
}
1139+
}`)
1140+
err := InstallRawVPA(f, validVPA)
1141+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Valid VPA object rejected")
1142+
1143+
ginkgo.By("Setting up invalid VPA objects")
1144+
testCases := []struct {
1145+
name string
1146+
vpaJSON string
1147+
expectedErr string
1148+
}{
1149+
{
1150+
name: "Invalid oomBumpUpRatio (negative value)",
1151+
vpaJSON: `{
1152+
"apiVersion": "autoscaling.k8s.io/v1",
1153+
"kind": "VerticalPodAutoscaler",
1154+
"metadata": {"name": "oom-test-vpa"},
1155+
"spec": {
1156+
"targetRef": {
1157+
"apiVersion": "apps/v1",
1158+
"kind": "Deployment",
1159+
"name": "oom-test"
1160+
},
1161+
"updatePolicy": {
1162+
"updateMode": "Auto"
1163+
},
1164+
"resourcePolicy": {
1165+
"containerPolicies": [{
1166+
"containerName": "*",
1167+
"oomBumpUpRatio": -1,
1168+
"oomMinBumpUp": 104857600
1169+
}]
1170+
}
1171+
}
1172+
}`,
1173+
expectedErr: "admission webhook \"vpa.k8s.io\" denied the request: oomBumpUpRatio must be greater than or equal to 1.0, got -1",
1174+
},
1175+
{
1176+
name: "Invalid oomBumpUpRatio (string value)",
1177+
vpaJSON: `{
1178+
"apiVersion": "autoscaling.k8s.io/v1",
1179+
"kind": "VerticalPodAutoscaler",
1180+
"metadata": {"name": "oom-test-vpa"},
1181+
"spec": {
1182+
"targetRef": {
1183+
"apiVersion": "apps/v1",
1184+
"kind": "Deployment",
1185+
"name": "oom-test"
1186+
},
1187+
"updatePolicy": {
1188+
"updateMode": "Auto"
1189+
},
1190+
"resourcePolicy": {
1191+
"containerPolicies": [{
1192+
"containerName": "*",
1193+
"oomBumpUpRatio": "not-a-number",
1194+
"oomMinBumpUp": 104857600
1195+
}]
1196+
}
1197+
}
1198+
}`,
1199+
expectedErr: "admission webhook \"vpa\\.k8s\\.io\" denied the request: quantities must match the regular expression",
1200+
},
1201+
{
1202+
name: "Invalid oomBumpUpRatio (less than 1)",
1203+
vpaJSON: `{
1204+
"apiVersion": "autoscaling.k8s.io/v1",
1205+
"kind": "VerticalPodAutoscaler",
1206+
"metadata": {"name": "oom-test-vpa"},
1207+
"spec": {
1208+
"targetRef": {
1209+
"apiVersion": "apps/v1",
1210+
"kind": "Deployment",
1211+
"name": "oom-test"
1212+
},
1213+
"updatePolicy": {
1214+
"updateMode": "Auto"
1215+
},
1216+
"resourcePolicy": {
1217+
"containerPolicies": [{
1218+
"containerName": "*",
1219+
"oomBumpUpRatio": 0.5,
1220+
"oomMinBumpUp": 104857600
1221+
}]
1222+
}
1223+
}
1224+
}`,
1225+
expectedErr: "admission webhook \"vpa.k8s.io\" denied the request: oomBumpUpRatio must be greater than or equal to 1.0, got 0.5",
1226+
},
1227+
{
1228+
name: "Invalid oomMinBumpUp (negative value)",
1229+
vpaJSON: `{
1230+
"apiVersion": "autoscaling.k8s.io/v1",
1231+
"kind": "VerticalPodAutoscaler",
1232+
"metadata": {"name": "oom-test-vpa"},
1233+
"spec": {
1234+
"targetRef": {
1235+
"apiVersion": "apps/v1",
1236+
"kind": "Deployment",
1237+
"name": "oom-test"
1238+
},
1239+
"updatePolicy": {
1240+
"updateMode": "Auto"
1241+
},
1242+
"resourcePolicy": {
1243+
"containerPolicies": [{
1244+
"containerName": "*",
1245+
"oomBumpUpRatio": 2,
1246+
"oomMinBumpUp": -1
1247+
}]
1248+
}
1249+
}
1250+
}`,
1251+
expectedErr: "admission webhook \"vpa\\.k8s\\.io\" denied the request: oomMinBumpUp must be greater than or equal to 0, got -1 bytes",
1252+
},
1253+
{
1254+
name: "Invalid evictAfterOOMThreshold (negative duration)",
1255+
vpaJSON: `{
1256+
"apiVersion": "autoscaling.k8s.io/v1",
1257+
"kind": "VerticalPodAutoscaler",
1258+
"metadata": {"name": "evict-threshold-vpa"},
1259+
"spec": {
1260+
"targetRef": {
1261+
"apiVersion": "apps/v1",
1262+
"kind": "Deployment",
1263+
"name": "hamster"
1264+
},
1265+
"updatePolicy": {
1266+
"updateMode": "Auto",
1267+
"evictAfterOOMThreshold": "-5m"
1268+
}
1269+
}
1270+
}`,
1271+
expectedErr: "spec\\.updatePolicy\\.evictAfterOOMThreshold: Invalid value:.*evictAfterOOMThreshold must be greater than 0",
1272+
},
1273+
{
1274+
name: "Invalid evictAfterOOMThreshold (invalid format)",
1275+
vpaJSON: `{
1276+
"apiVersion": "autoscaling.k8s.io/v1",
1277+
"kind": "VerticalPodAutoscaler",
1278+
"metadata": {"name": "evict-threshold-vpa"},
1279+
"spec": {
1280+
"targetRef": {
1281+
"apiVersion": "apps/v1",
1282+
"kind": "Deployment",
1283+
"name": "hamster"
1284+
},
1285+
"updatePolicy": {
1286+
"updateMode": "Auto",
1287+
"evictAfterOOMThreshold": "not-a-duration"
1288+
}
1289+
}
1290+
}`,
1291+
expectedErr: "admission webhook.*denied the request:.*invalid duration",
1292+
},
1293+
{
1294+
name: "Invalid evictAfterOOMThreshold (invalid unit)",
1295+
vpaJSON: `{
1296+
"apiVersion": "autoscaling.k8s.io/v1",
1297+
"kind": "VerticalPodAutoscaler",
1298+
"metadata": {"name": "evict-threshold-vpa"},
1299+
"spec": {
1300+
"targetRef": {
1301+
"apiVersion": "apps/v1",
1302+
"kind": "Deployment",
1303+
"name": "hamster"
1304+
},
1305+
"updatePolicy": {
1306+
"updateMode": "Auto",
1307+
"evictAfterOOMThreshold": "5x"
1308+
}
1309+
}
1310+
}`,
1311+
expectedErr: "admission webhook.*denied the request:.*unknown unit.*in duration",
1312+
},
1313+
{
1314+
name: "Invalid minAllowed (invalid requests field)",
1315+
vpaJSON: `{
1316+
"apiVersion": "autoscaling.k8s.io/v1",
1317+
"kind": "VerticalPodAutoscaler",
1318+
"metadata": {"name": "hamster-vpa-invalid"},
1319+
"spec": {
1320+
"targetRef": {
1321+
"apiVersion": "apps/v1",
1322+
"kind": "Deployment",
1323+
"name": "hamster"
1324+
},
1325+
"resourcePolicy": {
1326+
"containerPolicies": [{
1327+
"containerName": "*",
1328+
"minAllowed": {
1329+
"requests": {
1330+
"cpu": "50m"
1331+
}
1332+
}
1333+
}]
1334+
}
1335+
}
1336+
}`,
1337+
expectedErr: "admission webhook .*vpa.* denied the request:",
1338+
},
1339+
}
1340+
for _, tc := range testCases {
1341+
err := InstallRawVPA(f, []byte(tc.vpaJSON))
1342+
gomega.Expect(err).To(gomega.HaveOccurred(),
1343+
fmt.Sprintf("Test case '%s': Invalid VPA object accepted", tc.name))
1344+
gomega.Expect(err.Error()).To(gomega.MatchRegexp(tc.expectedErr),
1345+
fmt.Sprintf("Test case '%s': Expected error pattern not matched. Got error: %v", tc.name, err))
1346+
}
1347+
})
1348+
})

vertical-pod-autoscaler/e2e/v1/autoscaling_utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"k8s.io/apimachinery/pkg/runtime/schema"
3232
"k8s.io/apimachinery/pkg/util/intstr"
3333
"k8s.io/apimachinery/pkg/util/wait"
34+
"k8s.io/autoscaler/vertical-pod-autoscaler/e2e/utils"
3435
clientset "k8s.io/client-go/kubernetes"
3536
"k8s.io/kubernetes/test/e2e/framework"
3637
e2edebug "k8s.io/kubernetes/test/e2e/framework/debug"
@@ -444,6 +445,7 @@ func runOomingReplicationController(c clientset.Interface, ns, name string, repl
444445
Namespace: ns,
445446
Timeout: timeoutRC,
446447
Replicas: replicas,
448+
Labels: utils.OOMLabels,
447449
Annotations: make(map[string]string),
448450
MemRequest: 1024 * 1024 * 1024,
449451
MemLimit: 1024 * 1024 * 1024,

0 commit comments

Comments
 (0)