Skip to content

Pod Resource Autoscaler (PRA)

Pod Resource Autoscaler (PRA) continuously adjusts CPU and/or memory requests/limits for a single container in matching pods, based on observed utilization. Unlike horizontal autoscaling, PRA does not change replica count; it resizes container resources in place.

PRA applies resource changes through the pods/resize subresource. If in-place resize is not supported by your cluster, resize attempts will fail and PRA will emit warning events.

Relevant Kubernetes docs:

  • Kubernetes API access to nodes/proxy (PRA calls kubelet /stats/summary through the apiserver node proxy).
  • Cluster support for in-place pod resize (required for updates to take practical effect).

PRA is available via the Kedify Agent and can be enabled or disabled using the environment variable PRA_ENABLED (defaults to false).

By default, all pods matched by a PodResourceAutoscaler are eligible for reconciliation. If you want explicit per-pod opt-in, set the environment variable PRA_REQUIRES_ANNOTATED_PODS on Kedify Agent to true

Both flags can be set in the Kedify Agent Helm Values file:

agent:
features:
podResourceAutoscalersEnabled: true
praRequiresAnnotatedPods: true

When annotation requirement is enabled, pods must include:

pra.kedify.io/reconcile: enabled

The custom resource uses:

apiVersion: keda.kedify.io/v1alpha1
kind: PodResourceAutoscaler

See PodResourceAutoscaler API reference for fields and validation.

apiVersion: keda.kedify.io/v1alpha1
kind: PodResourceAutoscaler
metadata:
name: load-generator
spec:
target:
kind: deployment
name: load-generator
containerName: load-generator
policy:
pollInterval: 5s
consecutiveSamples: 3
cooldown: 2m
after: containerReady
delay: 10s
cpu:
requests:
scaleUpThreshold: 75
scaleDownThreshold: 60
targetUtilization: 70
limits:
scaleUpThreshold: 85
scaleDownThreshold: 70
targetUtilization: 75
memory:
requests:
scaleUpThreshold: 75
scaleDownThreshold: 60
targetUtilization: 70
limits:
scaleUpThreshold: 85
scaleDownThreshold: 70
targetUtilization: 75
bounds:
cpu:
requests:
min: 100m
max: "2"
step: 100m
stepPercent: 25
limits:
min: 100m
max: "3"
step: 100m
stepPercent: 25
memory:
requests:
min: 96Mi
max: 2Gi
step: 64Mi
stepPercent: 25
limits:
min: 300Mi
max: 3Gi
step: 64Mi
stepPercent: 25

See PodResourceAutoscaler API reference for fields and validation.

PRA observes kubelet usage, evaluates policy, requests a safe resize, and checks container state on the next observation.
Scroll to explore
Diagram description

PRA in the Kedify Agent collects kubelet stats through the API server node proxy. It evaluates each tracked pod on its own interval, checks consecutive samples, bounds, step limits and cooldown, then applies eligible changes through pods/resize. Kubernetes applies feasible changes; inspect actual container resources and resize status. The loop observes updated utilization and keeps replica count unchanged.

  • Watches PodResourceAutoscaler and Pod.
  • Tracks matching pods keyed by pod UID.
  • Groups tracked pods by node and runs one poller per node.
  • The poller fetches kubelet /stats/summary once per cycle and evaluates only tracked pods on that node.
  • Node poll interval is the minimum pollInterval among tracked pods on that node.
  • Even when node polling is more frequent, each pod is evaluated only when its own pollInterval has elapsed.
  • Apply gating (after/delay) controls patch application, not metric sampling.

Consecutive samples confirm a sustained threshold breach; bounds, step limits and cooldown constrain the resulting resize. The following CPU-only example shows requests and limits responding independently to the same usage samples. It is illustrative, not measured output or the exact policy from the YAML above.

Three sustained CPU samples lead to bounded increases or decreases in both CPU requests and limits; one container and one replica remain.
Scroll to explore
Diagram description

Illustrative CPU usage rises then falls. After three qualifying samples, CPU requests change from 200 to 400 to 600 to 400 to 200m, and limits from 300 to 500 to 700 to 500 to 300m. Each side has its own utilization policy. Changes are capped at 200m; replicas remain at one.

The chart uses three consecutive samples and a maximum change of 200m per resize. Request thresholds are 75% up / 35% down with a 50% target; limit thresholds are 60% up / 25% down with a 40% target. Each percentage uses that side’s current value as its denominator. For example, 200m usage targets a 400m request and a 500m limit. Later changes hit the 200m step cap. The example assumes cooldown expires before the next evaluation and each resize completes before the next sample.

Only sides configured in policy change. A request-only policy leaves limits unchanged; memory uses its own policy and bounds. Compare the requested configuration with actual allocated resources and resize status.

See PodResourceAutoscaler API reference for fields and validation.

See PodResourceAutoscaler API reference for fields and validation.

Set:

spec:
paused: true

When paused, PRA stops tracking/scaling for that autoscaler and resumes on unpause.

While paused, PRA status conditions are set to:

  • Ready=True (reason=Paused)
  • Active=False (reason=Paused)
  • Scaling=False (reason=Paused)

PRP and PRA can both write pod resources. If both target the same container/resource fields, regular patch semantics apply (last writer wins), which can cause control-loop fights.

Recommendation:

  • Do not overlap ownership of the same container requests/limits fields between PRP and PRA. Use the field-level ownership example to review the split.

PRA patches the pods/resize subresource using a merge-style patch (not server-side apply). If another controller (or manual apply) writes the same fields, last writer wins.

  • Pod QoS class must not change during in-place resize; candidate updates that would change QoS are skipped and reported via event.
  • If memory resize may restart a container (for example resizePolicy.restartPolicy=RestartContainer), PRA emits an event to signal a restart may occur.
  • Scale up is skipped while resize is pending/deferred/infeasible; any scaling is skipped while resize is actively in progress.
  • Apply is gated by policy.after and policy.delay:
    • after=containerReady (default): applies only when the target container is ready
    • after=running: applies as soon as the pod is Running
    • after=podReady: requires PodReady instead of container readiness

PRA emits Kubernetes events for validation, metrics health, and scaling lifecycle. Common events include:

  • PodResourceAutoscalerMetricsUnavailable / PodResourceAutoscalerMetricsRecovered
  • PodResourceAutoscalerBaselineUnavailable / PodResourceAutoscalerBaselineAvailable
  • PodResourceAutoscalerScaleUp / PodResourceAutoscalerScaleDown
  • PodResourceAutoscalerResizeInProgress / PodResourceAutoscalerResizePending
  • PodResourceAutoscalerQoSInvariantBlocked
  • PodResourceAutoscalerResizeMayRestart
  • PodResourceAutoscalerScaleFailed

See PodResourceAutoscaler API reference for fields and validation.

1. Create a cluster with in-place resize enabled

Section titled “1. Create a cluster with in-place resize enabled”
Terminal window
k3d cluster create dyn-resources --no-lb \
--k3s-arg "--disable=traefik,servicelb@server:*" \
--k3s-arg "--kube-apiserver-arg=feature-gates=InPlacePodVerticalScaling=true@server:*"
Terminal window
kubectl create deployment my-app --image=ghcr.io/kedify/sample-minute-metrics:latest
kubectl rollout status deploy/my-app
kubectl set resources deployment/my-app --requests=cpu=100m,memory=128Mi --limits=cpu=2,memory=256Mi
kubectl rollout status deploy/my-app
Terminal window
cat <<EOF | kubectl apply -f -
apiVersion: keda.kedify.io/v1alpha1
kind: PodResourceAutoscaler
metadata:
name: my-app-pra
spec:
target:
kind: deployment
name: my-app
containerName: my-app
policy:
pollInterval: 5s
consecutiveSamples: 2
cooldown: 30s
cpu:
requests:
scaleUpThreshold: 1
scaleDownThreshold: 0
targetUtilization: 50
bounds:
cpu:
requests:
min: 100m
max: "2"
step: 100m
EOF
Terminal window
kubectl get pra -o wide
kubectl get events --sort-by=.lastTimestamp | grep PodResourceAutoscaler
kubectl get pod -l app=my-app -o jsonpath="{.items[0].spec.containers[0].resources}"

See Metrics, status and health signals for fields and validation.

PRA requires (at minimum):

  • Pods: get/list/watch
  • Pods/resize subresource: patch
  • Nodes proxy: get
  • PodResourceAutoscalers: get/list/watch/update/patch + status get/update/patch
  • Deployments/DaemonSets/StatefulSets: get/list/watch

Configure and verify: Test PRA under load.

Diagnose: Pod resources do not resize as expected.

Related capabilities: Vertical scaling: PRA and PRP.