Skip to content

Add VerticalPodAutoscaler support with HPA-style workload reverse-matching#656

Merged
bergerx merged 2 commits into
masterfrom
worktree-bridge-cse_01UGZA2iZGfBr7UGSWf8676x
Jul 11, 2026
Merged

Add VerticalPodAutoscaler support with HPA-style workload reverse-matching#656
bergerx merged 2 commits into
masterfrom
worktree-bridge-cse_01UGZA2iZGfBr7UGSWf8676x

Conversation

@bergerx

@bergerx bergerx commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a standalone VerticalPodAutoscaler.tmpl (target ref, update mode, per-container min/max policy, applied recommendation) built from the live autoscaling.k8s.io/v1 CRD schema.
  • Reverse-matches VPAs onto their target workload the same way HorizontalPodAutoscaler already does (matching_hpas), via a new matching_vpas template in common.tmpl, gated by a vpaTargetable flag separate from HPA's scalable gate since VPA can also target DaemonSet directly (unlike HPA).
  • install-e2e-deps now installs the real VPA controllers (recommender/updater/admission-controller) via the cowboysysop Helm chart, since the new e2e scenario exercises the updater actually evicting/recreating a Pod to apply a recommendation, not just rendering a static object.
  • New e2e test creates a CPU-burning Deployment with deliberately low resource requests, attaches a VPA, waits for a real recommendation and for the updater to evict/recreate the Pod, then asserts both the Deployment's reverse-match line and the VPA's own rendered output against fully anchored (\A...\z) fixtures.

Test plan

  • go build ./..., go vet ./..., staticcheck ./...
  • go test ./... (unit suite)
  • TestE2ERegexFixturesAreAnchored passes for both new fixtures
  • TestE2EDynamicManifests/VerticalPodAutoscaler... passes against real minikube (verified on two independent fresh runs)
  • Manually verified --deep terminates cleanly (mutual-recursion guard, same as HPA) and DaemonSet reverse-matching works

🤖 Generated with Claude Code

…ching

Adds a standalone VerticalPodAutoscaler.tmpl (target ref, update mode,
per-container min/max policy, applied recommendation) and reverse-matches
VPAs onto their target workload the same way HorizontalPodAutoscaler
already does, via a new matching_vpas template in common.tmpl gated by a
vpaTargetable flag (Deployment/StatefulSet/ReplicaSet/DaemonSet -- VPA,
unlike HPA, can also target DaemonSet directly).

install-e2e-deps now installs the VPA controllers via Helm (CRDs alone
aren't enough since the new e2e scenario exercises the updater actually
evicting/recreating a Pod to apply a recommendation), and a new e2e test
covers the full loop with fully anchored fixtures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for rendering VerticalPodAutoscaler (VPA) resources, including reverse-matching them to their target workloads (Deployments, StatefulSets, ReplicaSets, and DaemonSets). It also introduces end-to-end tests that deploy VPA controllers via Helm to verify recommendation and eviction behavior. The review feedback highlights several opportunities to prevent potential nil pointer dereferences during template evaluation by defensively guarding .Spec and .Status fields with default empty dictionaries. Additionally, the reviewer suggests renaming misleading labels, such as changing 'Scales' to 'Targets' and clarifying the 'Min replicas to evict' label.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

{{- $obj := $.KubeGetFirst $.Namespace (.kind | default "Deployment") .name }}
{{- if $obj.Object }}{{ $.IncludeRenderableObject $obj | nindent 2 }}{{ end }}
{{- else }}
{{- "Scales" | bold | nindent 2 }}: {{ $.Include "resource_ref" (dict "kind" (.kind | default "Deployment") "name" .name "namespace" $.Namespace) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The label 'Scales' is misleading for a VerticalPodAutoscaler because VPA does not scale the number of replicas (like an HPA does); instead, it autoscales the resource requests and limits of the target workload. Consider using 'Targets' or 'Target workload' to accurately describe this relationship.

            {{- \"Targets\" | bold | nindent 2 }}: {{ $.Include \"resource_ref\" (dict \"kind\" (.kind | default \"Deployment\") \"name\" .name \"namespace\" $.Namespace) }}

Comment on lines +16 to +23
{{- $updateMode := (.Spec.updatePolicy | default dict).updateMode | default "Auto" }}
{{- if ne $updateMode "Auto" }}
{{- "Update mode" | bold | nindent 2 }}: {{ $updateMode | cyan }}
{{- end }}
{{- with (.Spec.updatePolicy | default dict).minReplicas }}
{{- "Min replicas to evict" | bold | nindent 2 }}: {{ . | toString | cyan }}
{{- end }}
{{- range (.Spec.resourcePolicy | default dict).containerPolicies }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block has two issues:\n1. Defensive Programming: If .Spec is nil (which can happen for newly created resources or when rendering local files with missing fields), accessing .Spec.updatePolicy or .Spec.resourcePolicy directly will cause a template evaluation error (nil pointer dereference). Guarding .Spec with a default empty dictionary prevents this.\n2. Misleading Label: The label 'Min replicas to evict' is misleading. In the VPA updatePolicy, minReplicas defines the minimum number of replicas that must be running for the updater to be allowed to perform any evictions. It does not mean the minimum number of replicas that will be evicted. Using 'Min replicas for eviction' or 'Min replicas required to evict' is much more accurate.

    {{- $updateMode := ((.Spec | default dict).updatePolicy | default dict).updateMode | default \"Auto\" }}\n    {{- if ne $updateMode \"Auto\" }}\n        {{- \"Update mode\" | bold | nindent 2 }}: {{ $updateMode | cyan }}\n    {{- end }}\n    {{- with ((.Spec | default dict).updatePolicy | default dict).minReplicas }}\n        {{- \"Min replicas for eviction\" | bold | nindent 2 }}: {{ . | toString | cyan }}\n    {{- end }}\n    {{- range ((.Spec | default dict).resourcePolicy | default dict).containerPolicies }}

{{- end }}
{{- end }}
{{- end }}
{{- with (.Status.recommendation | default dict).containerRecommendations }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If .Status is nil (e.g., when rendering local files or resources that haven't been reconciled yet), accessing .Status.recommendation directly will cause a template evaluation error. Guard .Status with a default empty dictionary to prevent nil pointer dereferences.

    {{- with ((.Status | default dict).recommendation | default dict).containerRecommendations }}

Comment on lines +698 to +700
{{- $updateMode := (.Spec.updatePolicy | default dict).updateMode | default "Auto" }}
{{- if ne $updateMode "Auto" }}, {{ $updateMode | cyan }}{{ end }}
{{- range (.Status.recommendation | default dict).containerRecommendations }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If .Spec or .Status is nil, accessing .Spec.updatePolicy or .Status.recommendation directly will cause a template evaluation error. Use defensive programming to guard these fields with default empty dictionaries.

    {{- $updateMode := ((.Spec | default dict).updatePolicy | default dict).updateMode | default \"Auto\" }}\n    {{- if ne $updateMode \"Auto\" }}, {{ $updateMode | cyan }}{{ end }}\n    {{- range ((.Status | default dict).recommendation | default dict).containerRecommendations }}

{{- if not ($ctx.Config.GetBool "shallow") }}
{{- $vpas := list }}
{{- range $ctx.KubeGet $namespace "VerticalPodAutoscalers" }}
{{- $ref := .Spec.targetRef | default dict }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If .Spec is nil for any retrieved VPA, accessing .Spec.targetRef directly will cause a template evaluation error. Guard .Spec with a default empty dictionary.

            {{- $ref := (.Spec | default dict).targetRef | default dict }}

ReplicaSets/ControllerRevisions were sorted by creationTimestamp, which
only has second resolution: revisions created in the same second (e.g.
in a fast e2e run) could tie and render rollout diffs out of order. Sort
by the revision annotation/field instead for a reliable chronological
order in Deployment, StatefulSet and DaemonSet rollout rendering.

Also renames VerticalPodAutoscaler's "Scales" label to "Targets" (VPA
doesn't scale replica count, unlike HPA) and clarifies "Min replicas to
evict" to "Min replicas for eviction", per gemini-code-assist review on
PR #656.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bergerx
bergerx merged commit 3299249 into master Jul 11, 2026
2 checks passed
@bergerx
bergerx deleted the worktree-bridge-cse_01UGZA2iZGfBr7UGSWf8676x branch July 11, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant