Skip to content

feat: support per-component Deployment labels and annotations - #882

Open
somaz94 wants to merge 3 commits into
kedacore:mainfrom
somaz94:feat/per-component-deployment-labels-annotations
Open

somaz94 wants to merge 3 commits into
kedacore:mainfrom
somaz94:feat/per-component-deployment-labels-annotations

Conversation

@somaz94

@somaz94 somaz94 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds top-level deploymentLabels and deploymentAnnotations values, keyed per component (keda / metricsAdapter / webhooks), and wires them into the metadata.labels / metadata.annotations of the operator, metrics-apiserver and webhooks Deployments.

Why

Today the only way to set Deployment-level labels/annotations is additionalLabels / additionalAnnotations, which are shared across every component. There is no way to give a different value per component. The use case in #418 is Datadog unified service tagging, where each Deployment needs a distinct service (operator / metrics-apiserver / webhook).

This mirrors the existing per-component podLabels / podAnnotations interface exactly, so the shape is already familiar:

deploymentAnnotations:
  keda:
    ad.datadoghq.com/tags: '{"service":"operator"}'
  metricsAdapter:
    ad.datadoghq.com/tags: '{"service":"metrics-apiserver"}'
  webhooks:
    ad.datadoghq.com/tags: '{"service":"webhook"}'
deploymentLabels:
  keda:
    team: platform

When set alongside additionalAnnotations, both are merged onto the Deployment.

A note on the approach

In #418 you raised the question of whether to follow the existing podLabels/podAnnotations map style or move toward a {component}.foo.bar nested style. I went with the existing top-level map style here for consistency with podLabels/podAnnotations and to keep the diff small. Happy to refactor to the per-component nested style if that is the direction you prefer.

Validation (local)

  • helm lint keda passes.
  • Default render is byte-identical to main (new keys default to {}, so existing installs see no change).
  • helm template with the values above merges additionalAnnotations + per-component deploymentAnnotations, and appends per-component deploymentLabels, on all three Deployments.
  • Installed on a kind v1.34 cluster: all three Deployments carry the expected per-component metadata.labels / metadata.annotations on the live specs, and all pods reach Running.
  • keda/Chart.yaml version intentionally not bumped, per CONTRIBUTING (chart versions are released with KEDA core).

Resolves #418

@somaz94
somaz94 force-pushed the feat/per-component-deployment-labels-annotations branch from 905e912 to 5850a21 Compare June 23, 2026 09:46
@somaz94
somaz94 marked this pull request as ready for review June 24, 2026 01:28
@somaz94
somaz94 requested review from a team as code owners June 24, 2026 01:28
@mindw

mindw commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@somaz94 Can chart templating support added to the values?
That is replace:

{{- toYaml . | nindent 4 }}

with

{{- tpl (toYaml .) $ | nindent 4 }}

@somaz94

somaz94 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Done in 024f997 — thanks for the suggestion! I switched the six deploymentLabels/deploymentAnnotations renders to {{- tpl (toYaml .) $ | nindent 4 }} across the operator, metrics-adapter, and webhooks Deployments, so values can now use template expressions (e.g. {{ .Release.Name }}).

I scoped the change to the new per-component fields only and left the existing additionalAnnotations render as-is to avoid changing its behavior. Verified locally:

  • default render (fields unset) is byte-identical
  • --set deploymentLabels.keda.release='{{ .Release.Name }}' resolves to release: keda
  • helm lint clean

somaz94 added 2 commits July 24, 2026 15:59
Signed-off-by: somaz <genius5711@gmail.com>
…te support

Signed-off-by: somaz <genius5711@gmail.com>
@somaz94
somaz94 force-pushed the feat/per-component-deployment-labels-annotations branch from 024f997 to b0daa8b Compare July 24, 2026 07:01
@somaz94

somaz94 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

The single red leg (Deploy to Kubernetes v1.34 in 'not-keda') is an infra flake unrelated to this change. The post-install hook raced the admission webhook coming up:

Error: INSTALLATION FAILED: failed post-install: Hook post-install keda/templates/extensibility/extra-manifests.yaml failed:
... Kind=ClusterTriggerAuthentication: failed calling webhook "vsclustertriggerauthentication.kb.io":
dial tcp 10.96.49.71:443: connect: connection refused

The webhook Service wasn't accepting connections yet when the post-install hook applied the ClusterTriggerAuthentication. This PR only changes deploymentLabels/deploymentAnnotations rendering (now tpl-aware per @mindw's request) and doesn't touch the webhook or hook ordering; the same leg passes on re-run. @wozniakjan could you re-run just that failed job when you get a chance? Thanks!

{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.deploymentAnnotations.keda }}
{{- tpl (toYaml .) $ | nindent 4 }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why tpl here?

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.

Using the tpl function for annotations and labels allows end-users to pass dynamic template strings into metadata fields via the values.yaml file. Without tpl, Helm treats configuration values in values.yaml as literal text, causing templates like {{ .Release.Name }} to break or render as raw strings when used inside metadata.
In a nutshell is allows dynamic values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To add the concrete numbers to what @mindw said — he's the one who asked for tpl here originally (review of 2026-07-16), and it shipped in b0daa8b.

Rendered against this branch, --set 'deploymentLabels.keda.team=squad-{{ .Release.Name }}':

# with tpl (this branch)
team: squad-keda

# without tpl (plain toYaml)
team: squad-{{ .Release.Name }}

For annotations the difference is a literal string instead of the intended value. For labels it's worse than cosmetic: squad-{{ .Release.Name }} isn't a valid label value — braces, spaces and the leading . are all outside [A-Za-z0-9]([-A-Za-z0-9_.]*[A-Za-z0-9])? — so the API server rejects the manifest outright. tpl is what makes the values usable at all here, rather than just nicer.

Cost is limited to these six renders (deploymentLabels / deploymentAnnotations × operator, metrics-adapter, webhooks); nothing else in the chart changed behaviour. Happy to drop it back to toYaml if you'd rather not have templating in these fields — it's a three-line revert — but that would take the feature back to what @mindw asked to change.

Copilot AI 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.

Pull request overview

Adds chart values to configure Deployment-level labels/annotations per KEDA component (operator / metrics adapter / webhooks) and applies them to the corresponding Deployment metadata.labels / metadata.annotations, matching the existing per-component pod metadata pattern.

Changes:

  • Introduces deploymentAnnotations and deploymentLabels in values.yaml, keyed by component (keda, metricsAdapter, webhooks).
  • Wires those per-component values into the three Deployment templates’ metadata.annotations and metadata.labels.
  • Documents the new values in keda/README.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
keda/values.yaml Adds new per-component deploymentAnnotations / deploymentLabels values.
keda/templates/manager/deployment.yaml Applies per-component Deployment annotations/labels to the operator Deployment.
keda/templates/metrics-server/deployment.yaml Applies per-component Deployment annotations/labels to the metrics-apiserver Deployment.
keda/templates/webhooks/deployment.yaml Applies per-component Deployment annotations/labels to the webhooks Deployment.
keda/README.md Documents the new per-component Deployment annotation/label values.
Suppressed comments (3)

keda/templates/manager/deployment.yaml:22

  • tpl renders deploymentLabels as a template. This differs from existing podLabels (plain toYaml) and can cause unexpected render errors when label values contain literal {{ ... }}. Prefer rendering labels as plain YAML unless templating is required and documented.
    {{- with .Values.deploymentLabels.keda }}
    {{- tpl (toYaml .) $ | nindent 4 }}
    {{- end }}

keda/templates/metrics-server/deployment.yaml:22

  • tpl renders deploymentLabels as a template, which is inconsistent with existing podLabels usage and can trigger render errors if values contain literal {{ ... }}. Prefer plain toYaml unless templating is explicitly desired.
    {{- with .Values.deploymentLabels.metricsAdapter }}
    {{- tpl (toYaml .) $ | nindent 4 }}
    {{- end }}

keda/templates/webhooks/deployment.yaml:23

  • tpl renders deploymentLabels as a template. To stay consistent with podLabels behavior and avoid unexpected render failures when values contain literal {{ ... }}, render with plain toYaml unless templating is explicitly desired.
    {{- with .Values.deploymentLabels.webhooks }}
    {{- tpl (toYaml .) $ | nindent 4 }}
    {{- end }}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +11 to +13
{{- with .Values.deploymentAnnotations.keda }}
{{- tpl (toYaml .) $ | nindent 4 }}
{{- end }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both halves of this are correct, and I verified them rather than taking them on trust:

# same literal value in both fields
$ helm template keda ./keda -f literal.yaml | grep note:
        note: '{{ hello }}'     # podAnnotations  (toYaml)  -> preserved
    note: 'Hello!'              # deploymentAnnotations (tpl) -> evaluated

So the inconsistency with podAnnotations is real, and an unparseable value is a hard failure rather than a warning:

$ helm template keda ./keda --set-string 'deploymentAnnotations.keda.note={{ .Broken'
Error: ... error calling tpl: cannot parse template ... bad character U+0027

The reason I'm not dropping tpl: it's the requested feature, not an implementation detail — @mindw asked for it in review of 2026-07-16 so {{ .Release.Name }} can be used in these values, and it shipped in b0daa8b. And because deploymentAnnotations / deploymentLabels are new in this PR, there is no existing user with a literal {{ ... }} to break — which is exactly what separates them from podAnnotations, where toYaml can't be changed without breaking people.

You end your comment with "unless templating is intentional and documented". It is intentional, and it's now documented — 9a23e6b:

# Values here are rendered through `tpl`, so `{{ ... }}` is evaluated as a template.
# Escape it as `{{ "{{" }} ... }}` if you need a literal.
deploymentAnnotations:
  # -- Deployment annotations for KEDA operator. Rendered through `tpl`

Escape hatch verified: note: '{{ "{{" }} hello }}' renders as note: '{{ hello }}'. All six rows updated in values.yaml and README.md.

@JorTurFer — this is the same question as your "why tpl here?" thread, so it's your call in the end: keep tpl as @mindw asked, or drop it for consistency with podAnnotations. It's a three-line revert either way and I'm happy to do whichever you prefer.

Comment on lines +12 to +14
{{- with .Values.deploymentAnnotations.metricsAdapter }}
{{- tpl (toYaml .) $ | nindent 4 }}
{{- end }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same point as the operator-deployment thread — answered there, and documented in 9a23e6b. Short version: the tpl behaviour is intentional (it's what @mindw asked for) and these values are new in this PR so nothing existing breaks, but it is now spelled out in values.yaml and the README along with the {{ "{{" }} escape.

Comment on lines +12 to +14
{{- with .Values.deploymentAnnotations.webhooks }}
{{- tpl (toYaml .) $ | nindent 4 }}
{{- end }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same point as the operator-deployment thread — answered there, and documented in 9a23e6b. Short version: the tpl behaviour is intentional (it's what @mindw asked for) and these values are new in this PR so nothing existing breaks, but it is now spelled out in values.yaml and the README along with the {{ "{{" }} escape.

Signed-off-by: somaz <genius5711@gmail.com>
@somaz94
somaz94 force-pushed the feat/per-component-deployment-labels-annotations branch from 9a23e6b to fbbcf7e Compare August 10, 2026 09:53
@somaz94

somaz94 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@JorTurFer thanks for the approval on Aug 11 — checking in on what's left here.

CI is green and there are no outstanding review threads: @mindw's tpl suggestion was applied back in 024f997 (all six deploymentLabels / deploymentAnnotations renders across the operator, metrics-adapter and webhooks now go through {{- tpl (toYaml .) $ | nindent 4 }}).

GitHub still reports the PR as blocked, which I assume is a second approval or a merge window rather than anything on my side — but let me know if there's something I've missed and I'll get to it.

@wozniakjan wozniakjan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd personally structure this differently, there is already additionalAnnotations so instead of deploymentAnnotations.operator, I'd put it as operator.additionalAnnotations where some of component specific overrides already live, somewhere in this block:

charts/keda/values.yaml

Lines 98 to 169 in 1444d00

operator:
# -- Name of the KEDA operator
name: keda-operator
# -- ReplicaSets for this Deployment you want to retain (Default: 10)
revisionHistoryLimit: 10
# -- Capability to configure the number of replicas for KEDA operator.
# While you can run more replicas of our operator, only one operator instance will be the leader and serving traffic.
# You can run multiple replicas, but they will not improve the performance of KEDA, it could only reduce downtime during a failover.
# Learn more in [our documentation](https://keda.sh/docs/latest/operate/cluster/#high-availability).
replicaCount: 1
# --Disable response compression for k8s restAPI in client-go.
# Disabling compression simply means that turns off the process of making data smaller for K8s restAPI in client-go for faster transmission.
disableCompression: true
# -- Leader election ID (Lease resource name) for the controller manager. Defaults to operator.keda.sh.
# Override to allow multiple independent KEDA operator deployments in the same namespace.
# leaderElectionID: "operator.keda.sh"
# -- DNS config for KEDA operator pod
dnsConfig: {}
# use ClusterFirstWithHostNet if `useHostNetwork: true` https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
# -- Defined the DNS policy for the operator
dnsPolicy: ClusterFirst
# -- Enable operator to use host network
useHostNetwork: false
# -- (bool) Sets `hostUsers` on the KEDA operator pod. Leave unset to preserve the cluster default, `false` to run the pod in its own [user namespace](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/), or `true` to explicitly use the host user namespace.
hostUsers:
# -- [Affinity] for pod scheduling for KEDA operator. Takes precedence over the `affinity` field
affinity: {}
# podAntiAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# - labelSelector:
# matchExpressions:
# - key: app
# operator: In
# values:
# - keda-operator
# topologyKey: "kubernetes.io/hostname"
# -- Additional containers to run as part of the operator deployment
extraContainers: []
# - name: hello-many
# args:
# - -c
# - "while true; do echo hi; sleep 300; done"
# command:
# - /bin/sh
# image: 'busybox:glibc'
# -- Additional init containers to run as part of the operator deployment
extraInitContainers: []
# - name: hello-once
# args:
# - -c
# - "echo 'Hello World!'"
# command:
# - /bin/sh
# image: 'busybox:glibc'
# -- Liveness probes for operator ([docs](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/))
livenessProbe:
initialDelaySeconds: 25
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
successThreshold: 1
# -- Readiness probes for operator ([docs](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes))
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 3
timeoutSeconds: 1
failureThreshold: 3
successThreshold: 1
# -- Node selector for pod scheduling ([docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/))
nodeSelector: {}
# -- Tolerations for pod scheduling ([docs](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/))
tolerations: []

same for other components and labels

@somaz94

somaz94 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for taking a look @wozniakjan. Before I move these, can I check the premise with you? I think additionalAnnotations is a different axis rather than the same one.

There are two additionalAnnotations in the chart today and neither is per-component:

  • the top-level one (values.yaml L304, "Custom annotations to add into metadata") is chart-wide — it lands on every resource
  • crds.additionalAnnotations is scoped to a resource kind, not to a component

The operator: block you linked (L98–169) doesn't have one, so there isn't an existing per-component additionalAnnotations for these to be consistent with — putting them there would be establishing that convention rather than following it.

And the global one isn't in competition with this PR; the two already compose:

$ helm template keda ./keda \
    --set-string 'additionalAnnotations.global-one=yes' \
    --set-string 'deploymentAnnotations.keda.per-component=yes'
...
kind: Deployment
metadata:
  name: keda-operator
  annotations:
    global-one: "yes"
    per-component: "yes"

What I did mirror is podAnnotations / podLabels, which sit directly above these in values.yaml and are the exact analog one level down — same three components, same top-level map, pod metadata instead of deployment metadata:

podAnnotations:
  keda: {}
  metricsAdapter: {}
  webhooks: {}

deploymentAnnotations:   # this PR
  keda: {}
  metricsAdapter: {}
  webhooks: {}

One consequence worth weighing before you decide: the component blocks are operator: / metricsServer: / webhooks:, while these maps key on keda / metricsAdapter / webhooks. So operator.additionalAnnotations isn't only a move — it renames two of the three keys for users, and it would leave deployment annotations living somewhere different from the pod annotations right next to them.

Genuinely happy either way, it's your chart. If you'd still prefer the per-component blocks I'll restructure all six renders plus values.yaml and the README, and in that case I'd suggest moving podAnnotations/podLabels the same way later so the two don't drift apart. Just let me know which you'd like and I'll push it.

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.

Provide a way to configure Deployment labels/annotations per component

5 participants