Scale Adapter
ScaleAdapter (autoscaling.kedify.io/v1alpha1, short name sca) lets KEDA and the Kubernetes HPA scale resources whose /scale subresource is incomplete, or that have no /scale subresource at all. It is available starting with Kedify Agent v0.6.8.
When You Need It
Section titled “When You Need It”KEDA can target any custom resource that exposes the Kubernetes /scale subresource. Some operators, however, expose a partially functional implementation: desired replicas can be read and written, but the scale selector is missing or empty.
Agones Fleet is the motivating example. Its /scale endpoint reads and writes replicas just fine, but the returned Scale object has an empty status.selector (the CRD points at a status field that Agones never populates and that gets pruned by the structural schema, see agones#2051). The HPA controller rejects any scale target with an empty selector before it evaluates a single metric, so a KEDA-generated HPA for a Fleet stays in ScalingActive=False with reason InvalidSelector forever.
The adapter sits between the HPA and the target and normalizes the scale contract:
KEDA scaler ──metric──> HPA (or KEDA for 0<->1 activation) │ writes /scale.spec.replicas ▼ ScaleAdapter exposes a complete /scale: replicas + selector │ forwards desired replicas ▼ target /scale e.g. Agones FleetKEDA and the HPA keep all their responsibilities: metric evaluation, min/max bounds, stabilization, and scaling behavior. The adapter only exposes an HPA-compatible /scale subresource with an explicit Pod selector, forwards desired replica changes to the target’s /scale endpoint, and mirrors the target’s current replicas back. Any KEDA or Kedify trigger works, including scale to zero.
Enabling
Section titled “Enabling”Enable the controller on the Kedify Agent helm chart and grant RBAC for the target kind’s /scale subresource, which is not part of the default role:
agent: features: scaleAdaptersEnabled: true extraRbacRules: - apiGroups: ["agones.dev"] resources: ["fleets/scale"] verbs: ["get", "update"]The env var equivalent of the feature toggle is SCALE_ADAPTER_ENABLED (default false). In this default mode the controller only uses the target’s /scale endpoint; it never needs update permission on the whole target resource. The exception is targets without a /scale subresource, which need a wider grant.
Usage with an Agones Fleet
Section titled “Usage with an Agones Fleet”First, label the Fleet’s Pod template so the adapter has something to select. Agones does not put a per-fleet label on Pods by default, and the label has to go on the inner Pod template (spec.template.spec.template); labels on the GameServer template end up on GameServer objects, not on Pods:
apiVersion: agones.dev/v1kind: Fleetmetadata: name: game-fleetspec: template: spec: template: metadata: labels: kedify.io/scale-target: game-fleet # ...Create the adapter. Leave spec.replicas unset; the controller initializes it from the Fleet, so creating an adapter never changes the Fleet’s replica count:
apiVersion: autoscaling.kedify.io/v1alpha1kind: ScaleAdaptermetadata: name: game-fleet namespace: defaultspec: targetRef: apiVersion: agones.dev/v1 kind: Fleet name: game-fleet selector: matchLabels: kedify.io/scale-target: game-fleetThen point a regular ScaledObject at the adapter instead of the Fleet:
apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: game-fleet namespace: defaultspec: scaleTargetRef: apiVersion: autoscaling.kedify.io/v1alpha1 kind: ScaleAdapter name: game-fleet minReplicaCount: 0 maxReplicaCount: 100 triggers: - type: metrics-api metadata: # any KEDA or Kedify trigger worksScaling to and from zero is handled by KEDA writing to the adapter’s /scale directly (activation bypasses the HPA), which the adapter forwards like any other replica change.
Targets Without a /scale Subresource
Section titled “Targets Without a /scale Subresource”For targets that do not implement the scale contract at all, spec.desiredReplicasPath switches the adapter to explicit replica field paths instead of the target’s /scale subresource. This makes anything with a numeric “how many” field scalable by KEDA, including resources that represent systems outside the cluster, such as Crossplane managed resources for VM pools or autoscaling groups:
apiVersion: autoscaling.kedify.io/v1alpha1kind: ScaleAdaptermetadata: name: worker-pool namespace: defaultspec: targetRef: apiVersion: example.org/v1 kind: Pool name: worker-pool selector: matchLabels: kedify.io/scale-target: worker-pool desiredReplicasPath: ".spec.size" currentReplicasPath: ".status.size" # optional- Paths are simple dot-separated field paths; wildcards and array indices are not supported.
currentReplicasPathis optional. When unset,status.replicasmirrors the desired replica count. Pods are deliberately not counted, since field-path targets may have none.- In this mode the adapter reads and updates the whole target resource instead of its
/scalesubresource, with reads served from an informer cache, so the RBAC grant widens accordingly:
agent: extraRbacRules: - apiGroups: ["example.org"] resources: ["pools"] verbs: ["get", "list", "watch", "update"]- The HPA still requires a non-empty
status.selector, sospec.selectorremains required. For targets without Pods a nominal selector works, and only external metrics with anAverageValuetarget (KEDA’s default) make sense, since pods and resource metrics andValuetargets count the selected Pods. TheSelectorMatchesPodscondition is not evaluated in this mode. - Initialization, forwarding, conflict detection, and scaling to and from zero behave exactly as with
/scaletargets.
Behavior
Section titled “Behavior”- Initialization. On the first reconciliation, the controller reads the target’s
/scale, copies its desired replicas intospec.replicasand current replicas intostatus.replicas, and serializesspec.selectorintostatus.selector. - Forwarding. Whenever
spec.replicasdiffers from the target’s desired replicas, the controller updates the target through its/scaleendpoint, or throughdesiredReplicasPathwhen configured, never by touching any other field. - Mirroring. The target’s current replicas are continuously mirrored into
status.replicas, which the HPA uses for per-pod metric math. - Single writer. Exactly one component may manage the target’s desired replicas. Do not combine the adapter with a native autoscaler (for Agones, a
FleetAutoscaler) on the same target. The adapter is authoritative: if something else changes the target’s desired replicas, the adapter overwrites them, raises theConflictingReplicaWritercondition, and emits a Warning event with reasonExternalReplicaChange.
Conditions
Section titled “Conditions”| Type | Meaning |
|---|---|
Ready | Target resolved, selector usable, replicas synchronized |
TargetResolved | spec.targetRef maps to a resource with a readable /scale (or readable replica field paths) |
SelectorValid | spec.selector is a valid, non-empty selector |
SelectorMatchesPods | Warning only: False when the target reports replicas but the selector matches no Pods (labels missing from the Pod template) |
ConflictingReplicaWriter | Warning only: True when something else wrote the target’s desired replicas |
Selector Caveat
Section titled “Selector Caveat”The HPA validates that status.selector is non-empty and parseable before evaluating metrics. For external metrics with an AverageValue target (KEDA’s default), that is all the selector is used for. For external metrics with a Value target, and for pods and resource metrics, the HPA counts the Pods matched by the selector, so spec.selector must actually match the workload’s Pods. The SelectorMatchesPods condition flags a mismatch.
Scope and Limitations
Section titled “Scope and Limitations”- The target must expose a readable and writable
/scalesubresource, or explicit replica field paths must be configured throughspec.desiredReplicasPath. - The target must live in the same namespace as the adapter; cluster-scoped and cross-namespace targets are not supported.
- The adapter never computes desired replicas from metrics; that stays with KEDA and the HPA.