Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,82 @@ spec:
- Auto
- "Off"
type: string
startupBoost:
description: |-
StartupBoost specifies the startup boost policy for the container.
This overrides any pod-level startup boost policy.
properties:
cpu:
description: CPU specifies the CPU startup boost policy.
properties:
duration:
description: |-
Duration indicates for how long to keep the pod boosted after it goes to Ready.
Defaults to 0s.
type: string
factor:
description: Factor specifies the factor to apply
to the CPU request.
format: int32
type: integer
quantity:
anyOf:
- type: integer
- type: string
description: Quantity specifies the absolute CPU
resource quantity.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type:
description: |-
Type specifies the kind of boost to apply.
Supported values are: "Factor", "Quantity".
Defaults to "Factor".
enum:
- Factor
- Quantity
type: string
type: object
type: object
type: object
type: array
type: object
startupBoost:
description: StartupBoost specifies the startup boost policy for the
pod.
properties:
cpu:
description: CPU specifies the CPU startup boost policy.
properties:
duration:
description: |-
Duration indicates for how long to keep the pod boosted after it goes to Ready.
Defaults to 0s.
type: string
factor:
description: Factor specifies the factor to apply to the CPU
request.
format: int32
type: integer
quantity:
anyOf:
- type: integer
- type: string
description: Quantity specifies the absolute CPU resource
quantity.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type:
description: |-
Type specifies the kind of boost to apply.
Supported values are: "Factor", "Quantity".
Defaults to "Factor".
enum:
- Factor
- Quantity
type: string
type: object
type: object
targetRef:
description: |-
TargetRef points to the controller managing the set of pods for the
Expand Down
3 changes: 2 additions & 1 deletion vertical-pod-autoscaler/e2e/v1/actuation.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/autoscaler/vertical-pod-autoscaler/e2e/utils"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
restriction "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/restriction"
updaterutils "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/utils"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
Expand All @@ -57,7 +58,7 @@ var _ = ActuationSuiteE2eDescribe("Actuation", ginkgo.Label("FG:InPlaceOrRecreat
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline

ginkgo.BeforeEach(func() {
checkInPlaceOrRecreateTestsEnabled(f, true, true)
checkFeatureGateTestsEnabled(f, features.InPlaceOrRecreate, true, true)
})

ginkgo.It("still applies recommendations on restart when update mode is InPlaceOrRecreate", func() {
Expand Down
120 changes: 119 additions & 1 deletion vertical-pod-autoscaler/e2e/v1/admission_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/utils/test"
"k8s.io/kubernetes/test/e2e/framework"
framework_deployment "k8s.io/kubernetes/test/e2e/framework/deployment"
Expand All @@ -47,7 +48,7 @@ var _ = AdmissionControllerE2eDescribe("Admission-controller", ginkgo.Label("FG:
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline

ginkgo.BeforeEach(func() {
checkInPlaceOrRecreateTestsEnabled(f, true, false)
checkFeatureGateTestsEnabled(f, features.InPlaceOrRecreate, true, false)
waitForVpaWebhookRegistration(f)
})

Expand Down Expand Up @@ -961,6 +962,123 @@ var _ = AdmissionControllerE2eDescribe("Admission-controller", func() {
})
})

var _ = AdmissionControllerE2eDescribe("Admission-controller", ginkgo.Label("FG:CPUStartupBoost"), func() {
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline

ginkgo.BeforeEach(func() {
checkFeatureGateTestsEnabled(f, features.CPUStartupBoost, true, false)
waitForVpaWebhookRegistration(f)
})

ginkgo.It("boosts CPU by factor on pod creation", func() {
initialCPU := ParseQuantityOrDie("100m")
boostedCPU := ParseQuantityOrDie("200m")
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))

ginkgo.By("Setting up a VPA with a startup boost policy (factor)")
containerName := GetHamsterContainerNameByIndex(0)
factor := int32(2)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithCPUStartupBoost(&factor, nil, "15s").
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("100m", "100Mi").
GetContainerResources(),
).
Get()
InstallVPA(f, vpaCRD)

ginkgo.By("Starting the deployment and verifying the pod is boosted")
podList := startDeploymentPods(f, d)
pod := podList.Items[0]
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(boostedCPU)).To(gomega.Equal(0))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().Cmp(boostedCPU)).To(gomega.Equal(0))
})

ginkgo.It("boosts CPU by quantity on pod creation", func() {
initialCPU := ParseQuantityOrDie("100m")
boostedCPU := ParseQuantityOrDie("500m")
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))

ginkgo.By("Setting up a VPA with a startup boost policy (quantity)")
containerName := GetHamsterContainerNameByIndex(0)
quantity := ParseQuantityOrDie("500m")
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithCPUStartupBoost(nil, &quantity, "15s").
AppendRecommendation(
test.Recommendation().
WithContainer(containerName).
WithTarget("100m", "100Mi").
GetContainerResources(),
).
Get()
InstallVPA(f, vpaCRD)

ginkgo.By("Starting the deployment and verifying the pod is boosted")
podList := startDeploymentPods(f, d)
pod := podList.Items[0]
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(boostedCPU)).To(gomega.Equal(0))
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().Cmp(boostedCPU)).To(gomega.Equal(0))
})

ginkgo.It("boosts CPU on pod creation when VPA update mode is Off", func() {
initialCPU := ParseQuantityOrDie("100m")
boostedCPU := ParseQuantityOrDie("200m")
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))

ginkgo.By("Setting up a VPA with updateMode Off and a startup boost policy")
containerName := GetHamsterContainerNameByIndex(0)
factor := int32(2)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithUpdateMode(vpa_types.UpdateModeOff). // VPA is off, but boost should still work
WithCPUStartupBoost(&factor, nil, "15s").
Get()
InstallVPA(f, vpaCRD)

ginkgo.By("Starting the deployment and verifying the pod is boosted")
podList := startDeploymentPods(f, d)
pod := podList.Items[0]
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(boostedCPU)).To(gomega.Equal(0))
})

ginkgo.It("doesn't boost CPU on pod creation when scaling mode is Off", func() {
initialCPU := ParseQuantityOrDie("100m")
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))

ginkgo.By("Setting up a VPA with a startup boost policy and scaling mode Off")
containerName := GetHamsterContainerNameByIndex(0)
factor := int32(2)
vpaCRD := test.VerticalPodAutoscaler().
WithName("hamster-vpa").
WithNamespace(f.Namespace.Name).
WithTargetRef(hamsterTargetRef).
WithContainer(containerName).
WithCPUStartupBoost(&factor, nil, "15s").
WithScalingMode(containerName, vpa_types.ContainerScalingModeOff).
Get()
InstallVPA(f, vpaCRD)

ginkgo.By("Starting the deployment and verifying the pod is NOT boosted")
podList := startDeploymentPods(f, d)
pod := podList.Items[0]
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(initialCPU)).To(gomega.Equal(0))
})
})

func startDeploymentPods(f *framework.Framework, deployment *appsv1.Deployment) *apiv1.PodList {
// Apiserver watch can lag depending on cached object count and apiserver resource usage.
// We assume that watch can lag up to 5 seconds.
Expand Down
50 changes: 33 additions & 17 deletions vertical-pod-autoscaler/e2e/v1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ 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/component-base/featuregate"
"k8s.io/kubernetes/test/e2e/framework"
framework_deployment "k8s.io/kubernetes/test/e2e/framework/deployment"
)
Expand Down Expand Up @@ -359,14 +359,30 @@ func PatchVpaRecommendation(f *framework.Framework, vpa *vpa_types.VerticalPodAu

// AnnotatePod adds annotation for an existing pod.
func AnnotatePod(f *framework.Framework, podName, annotationName, annotationValue string) {
bytes, err := json.Marshal([]patchRecord{{
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(context.TODO(), podName, metav1.GetOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to get pod.")

patches := []patchRecord{}
if pod.Annotations == nil {
patches = append(patches, patchRecord{
Op: "add",
Path: "/metadata/annotations",
Value: make(map[string]string),
})
}

patches = append(patches, patchRecord{
Op: "add",
Path: fmt.Sprintf("/metadata/annotations/%v", annotationName),
Path: fmt.Sprintf("/metadata/annotations/%s", annotationName),
Value: annotationValue,
}})
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Patch(context.TODO(), podName, types.JSONPatchType, bytes, metav1.PatchOptions{})
})

bytes, err := json.Marshal(patches)
gomega.Expect(err).NotTo(gomega.HaveOccurred())

patchedPod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Patch(context.TODO(), podName, types.JSONPatchType, bytes, metav1.PatchOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to patch pod.")
gomega.Expect(pod.Annotations[annotationName]).To(gomega.Equal(annotationValue))
gomega.Expect(patchedPod.Annotations[annotationName]).To(gomega.Equal(annotationValue))
}

// ParseQuantityOrDie parses quantity from string and dies with an error if
Expand Down Expand Up @@ -613,35 +629,35 @@ func WaitForPodsUpdatedWithoutEviction(f *framework.Framework, initialPods *apiv
return err
}

// checkInPlaceOrRecreateTestsEnabled check for enabled feature gates in the cluster used for the
// InPlaceOrRecreate VPA feature.
// Use this in a "beforeEach" call before any suites that use InPlaceOrRecreate featuregate.
func checkInPlaceOrRecreateTestsEnabled(f *framework.Framework, checkAdmission, checkUpdater bool) {
ginkgo.By("Checking InPlacePodVerticalScaling cluster feature gate is on")
// checkFeatureGateTestsEnabled check for enabled feature gates in the cluster used for the
// given VPA feature.
// Use this in a "beforeEach" call before any suites that use a featuregate.
func checkFeatureGateTestsEnabled(f *framework.Framework, feature featuregate.Feature, checkAdmission, checkUpdater bool) {
ginkgo.By(fmt.Sprintf("Checking %s cluster feature gate is on", feature))

if checkUpdater {
ginkgo.By("Checking InPlaceOrRecreate VPA feature gate is enabled for updater")
ginkgo.By(fmt.Sprintf("Checking %s VPA feature gate is enabled for updater", feature))

deploy, err := f.ClientSet.AppsV1().Deployments(VpaNamespace).Get(context.TODO(), "vpa-updater", metav1.GetOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(deploy.Spec.Template.Spec.Containers).To(gomega.HaveLen(1))
vpaUpdaterPod := deploy.Spec.Template.Spec.Containers[0]
gomega.Expect(vpaUpdaterPod.Name).To(gomega.Equal("updater"))
if !anyContainsSubstring(vpaUpdaterPod.Args, fmt.Sprintf("%s=true", string(features.InPlaceOrRecreate))) {
ginkgo.Skip("Skipping suite: InPlaceOrRecreate feature gate is not enabled for the VPA updater")
if !anyContainsSubstring(vpaUpdaterPod.Args, fmt.Sprintf("%s=true", string(feature))) {
ginkgo.Skip(fmt.Sprintf("Skipping suite: %s feature gate is not enabled for the VPA updater", feature))
}
}

if checkAdmission {
ginkgo.By("Checking InPlaceOrRecreate VPA feature gate is enabled for admission controller")
ginkgo.By(fmt.Sprintf("Checking %s VPA feature gate is enabled for admission controller", feature))

deploy, err := f.ClientSet.AppsV1().Deployments(VpaNamespace).Get(context.TODO(), "vpa-admission-controller", metav1.GetOptions{})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(deploy.Spec.Template.Spec.Containers).To(gomega.HaveLen(1))
vpaAdmissionPod := deploy.Spec.Template.Spec.Containers[0]
gomega.Expect(vpaAdmissionPod.Name).To(gomega.Equal("admission-controller"))
if !anyContainsSubstring(vpaAdmissionPod.Args, fmt.Sprintf("%s=true", string(features.InPlaceOrRecreate))) {
ginkgo.Skip("Skipping suite: InPlaceOrRecreate feature gate is not enabled for VPA admission controller")
if !anyContainsSubstring(vpaAdmissionPod.Args, fmt.Sprintf("%s=true", string(feature))) {
ginkgo.Skip(fmt.Sprintf("Skipping suite: %s feature gate is not enabled for VPA admission controller", feature))
}
}
}
Expand Down
Loading
Loading