Skip to content

feat(base): support HPA->ScaledObject migration via transfer-hpa-ownership annotation#204

Open
PhillipChaffee wants to merge 1 commit into
dasmeta:mainfrom
PhillipChaffee:feature/keda-transfer-hpa-ownership
Open

feat(base): support HPA->ScaledObject migration via transfer-hpa-ownership annotation#204
PhillipChaffee wants to merge 1 commit into
dasmeta:mainfrom
PhillipChaffee:feature/keda-transfer-hpa-ownership

Conversation

@PhillipChaffee
Copy link
Copy Markdown
Contributor

Summary

Adds two backward-compatible passthroughs to the KEDA ScaledObject template in the base chart so consumers can use KEDA's transfer-hpa-ownership migration path without forking the chart:

  1. autoscaling.scaledObjectAnnotations (map) — rendered as metadata.annotations on the ScaledObject.
  2. autoscaling.hpaName (string) — rendered as advanced.horizontalPodAutoscalerConfig.name. Combined with the existing behavior block under a single advanced clause guarded by or of the two values.

Also adds a "Migrating from plain HPA to KEDA" section to charts/base/README.md documenting the full recipe.

Motivation

The chart already commits to a dual-mode design where templates/hpa.yaml (when autoscaling.triggers is unset) and templates/keda.yaml (when set) are mutually exclusive and both render a resource named {{ include "base.fullname" . }}. Flipping a release between the two via helm upgrade is rejected by KEDA's vscaledobject.kb.io admission webhook because the same-named HPA still exists when the new ScaledObject is created (Helm creates new resources before deleting old ones during upgrade):

Error: UPGRADE FAILED: failed to create resource: admission webhook "vscaledobject.kb.io"
denied the request: the workload '<name>' of type 'apps/v1.Deployment' is already
managed by the hpa '<name>'

KEDA's documented migration path is the scaledobject.keda.sh/transfer-hpa-ownership: "true" annotation paired with advanced.horizontalPodAutoscalerConfig.name matching the existing HPA. Today's chart exposes neither, so a chart consumer hitting this has to either fork the chart, use a post-renderer, or kubectl delete hpa <name> before the upgrade (which leaves a brief window with no autoscaler at all). See kedacore/keda#6250 for the broader context on why this is awkward by default.

This PR finishes the dual-mode design the chart already started.

Changes

charts/base/templates/keda.yaml

  • Add metadata.annotations block guarded by with .Values.autoscaling.scaledObjectAnnotations.
  • Combine advanced.horizontalPodAutoscalerConfig.name with the existing behavior block under a single advanced clause guarded by or .Values.autoscaling.behavior .Values.autoscaling.hpaName. Each child field uses with so it only renders when set.

charts/base/values.yaml

  • Add commented-out scaledObjectAnnotations and hpaName examples in the autoscaling: section with inline guidance.
  • Add a full "Migrate plain HPA to KEDA" example block alongside the existing trigger examples.

charts/base/README.md

  • Add "Migrating from plain HPA to KEDA without deleting the existing HPA" section with the full recipe (chart values + optional kubectl annotate hpa/<name> helm.sh/resource-policy=keep for zero-gap migration), the residual ~30s metrics-blind window note (Helm deletes the old HPA at end of upgrade; KEDA recreates it on next reconcile), and the reverse-direction caveat.

charts/base/Chart.yaml

  • Bump version and appVersion from 0.3.28 to 0.3.29.

Backward Compatibility

  • Existing HPA-only users (autoscaling.enabled: true, no trigger/triggers): zero changes — HPA still renders, no ScaledObject.
  • Existing single-trigger KEDA users (autoscaling.trigger): zero changes — singular trigger path is untouched.
  • Existing multi-trigger KEDA users (autoscaling.triggers, possibly with behavior): zero changes — the new with .Values.autoscaling.hpaName is conditionally rendered only when the new field is set, and the existing behavior block is wrapped in its own with to remain unchanged.
  • New scaledObjectAnnotations/hpaName: fully opt-in.

Example Usage

Migrate an existing plain HPA named my-release-name to a KEDA ScaledObject without manually deleting the HPA:

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 50
  scaledObjectAnnotations:
    scaledobject.keda.sh/transfer-hpa-ownership: "true"
  hpaName: my-release-name # must match the existing HPA's metadata.name
  triggers:
    - type: cpu
      metricType: Utilization
      metadata:
        value: "70"

For zero metrics-blind window during the transition, also pre-annotate the existing HPA so Helm leaves it alone during the upgrade:

kubectl annotate hpa/my-release-name -n <ns> helm.sh/resource-policy=keep
helm upgrade <release> dasmeta/base --version 0.3.29 -f values.yaml
kubectl annotate hpa/my-release-name -n <ns> helm.sh/resource-policy- # cleanup post-upgrade

Validation

  • helm lint charts/base — passes clean.
  • helm template charts/base --values <file> smoke tests:
    • Old HPA-only (autoscaling.enabled: true, no triggers, with behavior): renders an HPA, no ScaledObject, no annotations. Byte-for-byte identical to pre-change template output (only the chart-version label moves 0.3.280.3.29).
    • Old multi-trigger KEDA (existing triggers array + behavior, no new fields): renders ScaledObject with no annotations and no advanced.horizontalPodAutoscalerConfig.name. Byte-for-byte identical to pre-change output (only chart-version label change).
    • New transfer-ownership (scaledObjectAnnotations + hpaName set): renders ScaledObject with metadata.annotations.scaledobject.keda.sh/transfer-hpa-ownership: "true" and advanced.horizontalPodAutoscalerConfig.name: my-existing-hpa. No behavior block (correctly omitted when not set).
    • hpaName + behavior combo: both render correctly nested under a single advanced.horizontalPodAutoscalerConfig block.
  • pre-commit run --all-files — all hooks pass (including helmlint, gitleaks, conventional-commits).

…rship annotation

Adds two backward-compatible passthroughs to the KEDA ScaledObject template
in the `base` chart so consumers can use KEDA's transfer-hpa-ownership
migration path without forking the chart:

1. `autoscaling.scaledObjectAnnotations` (map) - rendered as
   `metadata.annotations` on the ScaledObject. Useful for
   `scaledobject.keda.sh/transfer-hpa-ownership: "true"` and similar.
2. `autoscaling.hpaName` (string) - rendered as
   `advanced.horizontalPodAutoscalerConfig.name`. Combined with the
   existing `behavior` block under a single `advanced` clause guarded
   by `or` of the two values.

Also adds a "Migrating from plain HPA to KEDA" section to charts/base/README.md
documenting the full recipe (annotation + matching HPA name +
optional `helm.sh/resource-policy: keep` for zero-gap migration), the
~30s metrics-blind window without the keep annotation, and the
reverse-direction caveat.

Bumps charts/base/Chart.yaml from 0.3.28 to 0.3.29.

Background: kedacore/keda#6250
@what-the-diff
Copy link
Copy Markdown

what-the-diff Bot commented Apr 30, 2026

🎉🥳 Congratulations on your new adventurous journey! 🥳🎉

This PR is an absolute game-changer! So, let's explore the awesome new features and improvements we've bundled.

PR Summary

  • 🚀 Software Version Upgrade
    We've boosted our app's engine to version 0.3.29. That basically means: we've made our system run smoother, and it's ready to perform at its best for you!

  • 📚 KEDA Migration- a Handy Guide Added
    Hey, buddy! Planning on moving from basic Horizontal Pod Autoscaler (HPA) to KEDA ScaledObject? We've got your back! We've included step-by-step instructions in the README.md document to make the transition smooth as silk!

  • ✍️ Making Annotations Easier for KEDA
    So, you're working with a KEDA ScaledObject, huh? We've created a new section in keda.yaml that enables you to apply annotations from autoscaling.scaledObjectAnnotations. Hassle-free, isn't it?

  • Name-Your-HPA: An Exciting Upgrade
    You've asked, we've listened! Now you get to name the HPA managed by KEDA in keda.yaml using the new hpaName value. What's in a name? Well, now there's a lot!

  • New Fields in 'Values'- For You, By You!
    We've added some new optional fields in values.yaml. These new fields are scaledObjectAnnotations and hpaName to better assist you on your KEDA migration journey. You're welcome! 😉

  • Migration Example: Because A Template Makes it Better!
    We understand that examples are the best way to learn. So, here's a template in the values.yaml that showcases how you can smoothly migrate an existing HPA to a KEDA ScaledObject. Let's learn it by doing it! 🤓

So, embrace these changes and thrive on the benefits they offer! 🎈💪🌟

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