Kubernetes HPA on Memory: Working Examples, CPU + Memory Combined, and Troubleshooting

Kubernetes HPA on Memory: Working Examples, CPU + Memory Combined, and Troubleshooting

You can scale a Deployment on memory with a plain autoscaling/v2 HorizontalPodAutoscaler and no extra tooling: set a Resource metric named memory, give every container a memory request, and have metrics-server running. The manifest below does exactly that. The rest of this article is about what the HPA actually does with that manifest — how the number is computed, what changes when you add CPU next to it, why it scales down slower than you expect, and what every error message in kubectl describe hpa means.

Related reading: HPA scale-down behavior and stabilizationWindowSeconds explained.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: worker-memory
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75   # % of the memory REQUEST, averaged across pods

Apply it, wait one sync period (15 seconds by default), and kubectl get hpa worker-memory should show memory: <unknown>/75% replaced by a real percentage. If it stays <unknown>, jump to the troubleshooting section — the cause is almost always a missing request or a missing metrics-server.

This is the how-to page. If you want the argument for why memory is usually the wrong signal for stateless services, that is a separate article: Kubernetes HPA best practices. Here I assume you have decided memory is your signal, or you need to understand a memory HPA someone else wrote.

How the HPA Computes Memory Utilization

Three facts explain almost every “why did it do that” question about memory-based HPA.

Utilization is a percentage of the request, not the limit. The HPA controller reads each pod’s memory usage from the resource metrics API (metrics.k8s.io, served by metrics-server), divides it by the sum of the memory requests of that pod’s containers, and averages the result across all pods of the target. A pod with requests.memory: 512Mi using 400Mi is at 78% utilization even if its limit is 2Gi. If any container in the pod has no memory request, utilization for that pod is undefined and the HPA does not act on the metric at all — which is the single most common reason a memory HPA silently does nothing.

The formula is a ratio, not a threshold. Every 15 seconds the controller computes:

desiredReplicas = ceil( currentReplicas × ( currentMetricValue / desiredMetricValue ) )

With 4 replicas averaging 90% against a 75% target, that is ceil(4 × 90/75) = ceil(4.8) = 5. With 4 replicas averaging 30%, it is ceil(4 × 30/75) = ceil(1.6) = 2. The HPA does not “add one pod when over the line”; it jumps straight to the replica count that would bring the average back to target, capped by maxReplicas, minReplicas and the scaling policies described later.

There is a tolerance band around the target. If the ratio currentMetricValue / desiredMetricValue is within ±10% of 1.0 (between 0.9 and 1.1), the HPA does nothing. That prevents flapping when memory hovers around the target. The 10% is a cluster-wide default set on kube-controller-manager with --horizontal-pod-autoscaler-tolerance. Since Kubernetes 1.37 the tolerance is also configurable per HPA and per direction through spec.behavior.scaleUp.tolerance and spec.behavior.scaleDown.tolerance (the HPAConfigurableTolerance feature: alpha in 1.33, beta but disabled by default in 1.35, stable and locked on in 1.37). On memory this matters more than on CPU, because memory moves slowly: a wide scale-down tolerance keeps a worker fleet from oscillating on a slow leak-and-GC cycle.

Two more details that bite on memory specifically. Pods that are not Ready are excluded from the average, and pods with no metrics yet are also excluded — so a burst of fresh pods that are still warming up does not drag the average down. And there is a CPU-only grace period (--horizontal-pod-autoscaler-cpu-initialization-period, 5 minutes by default) that has no memory equivalent: a new pod’s memory is counted as soon as metrics-server reports it, which for a JVM means “at full heap, immediately”.

Kubernetes HPA Memory and CPU Example

Most production HPAs that use memory use it alongside CPU. The manifest is just two entries in metrics:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-cpu-memory
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

The part people get wrong is the combination logic. Multiple metrics are OR, not AND. The controller computes a desired replica count for each metric independently and then takes the maximum. If CPU says “4 replicas” and memory says “9 replicas”, you get 9. The Deployment scales up when either metric is over its target, and it only scales down when both say fewer replicas would be fine.

This is why “I added memory to my HPA and it never scales down any more” is such a common complaint. CPU drops to nothing at night and proposes 3 replicas; memory, being sticky, is still at 70% of request across 9 pods and proposes 8. The maximum wins. The Deployment sits at 8 replicas until memory actually falls — which for a runtime that holds its heap is never. If that is your situation, the fix is not in the HPA: either the memory request is wrong (see the requests and limits guide), or memory should be a safety valve at 90% rather than a scaling signal at 80%.

A useful pattern is exactly that asymmetric configuration: CPU as the real driver at 60%, memory at 90% purely so a leak or a runaway cache spreads load before pods start getting OOM-killed. In normal operation memory never proposes more replicas than CPU does; in a leak it does, and the fleet grows while you investigate.

averageUtilization vs averageValue

Utilization targets are relative to requests. AverageValue targets are absolute. Both work for memory:

metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: AverageValue
        averageValue: 1536Mi   # scale so that the average pod uses ~1.5Gi

Use AverageValue when the request is deliberately not a good baseline — for example, a batch worker whose request is set low so it schedules on any node, but whose real working set is known and stable per unit of work. Use Utilization when you want the HPA to follow whatever the request is, so that right-sizing the request automatically re-tunes the autoscaler.

There is a trap with AverageValue and the ratio formula: the target is compared against the average per pod, and the replica count is still derived from the ratio. A target of 1536Mi with pods averaging 1700Mi gives a ratio of 1.107 — just outside the 10% tolerance — and a scale-up. A target of 1536Mi with pods averaging 1600Mi gives 1.04 and nothing happens. If you want the HPA to react to smaller deviations, that is what the per-HPA tolerance field is for.

There is also a third target.type, Value, which compares the total across all pods rather than the average. It is rarely what you want for memory.

Controlling Scale-Down: behavior for a Slow Metric

Memory does not fall the moment traffic stops. Caches stay warm, runtimes hold heap, and a pod that was at 85% ten minutes ago may still be at 80%. The default scale-down behavior — a 300-second stabilization window that uses the highest desired replica count seen in the last five minutes, then removes up to 100% of pods per 15 seconds — is aggressive once the window clears. For memory-driven workloads, slow it down explicitly:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: worker-memory
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60        # at most double the fleet per minute
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 600   # look back 10 minutes, not 5
      policies:
        - type: Pods
          value: 1
          periodSeconds: 120       # remove at most one pod every 2 minutes
      selectPolicy: Min

Read this as: scale up fast, scale down one pod at a time and only when the last ten minutes agree. The selectPolicy: Min on scale-down means that if you list several policies, the most conservative one wins. Setting selectPolicy: Disabled on scaleDown turns scale-down off entirely — occasionally the right answer for a memory HPA that exists only to absorb leaks, where you would rather scale down through a deploy than have the HPA do it.

The stabilization window is a rolling maximum of desired replica counts, not a delay. If memory dropped enough to justify 3 replicas eight minutes ago and enough to justify 2 replicas now, a 600-second window still holds the fleet at whatever the maximum proposal in the window was. That is what keeps a GC pause from triggering a scale-down.

Troubleshooting: What the Errors Mean

Everything you need is in kubectl describe hpa <name>, in the Conditions and Events sections, plus kubectl top pod to see what metrics-server is actually reporting. The messages below are the ones you will actually see.

unable to get metrics for resource memory: no metrics returned from resource metrics api — the HPA asked metrics.k8s.io for the pods’ memory and got an empty answer. Either metrics-server is not installed (kubectl get apiservice v1beta1.metrics.k8s.io should show Available: True), or it is installed but cannot scrape the kubelets (the classic symptom is kubectl top pod returning error: Metrics API not available, fixed on many local clusters by adding --kubelet-insecure-tls to the metrics-server args), or the pods are so new that no sample exists yet. The event reason is FailedGetResourceMetric and the ScalingActive condition will be False.

FailedGetResourceMetric with missing request for memory (sometimes phrased missing request for memory in container <name> of Pod <name>) — at least one container in the target pods has no resources.requests.memory. The HPA cannot compute a percentage of nothing, so it ignores the metric. Sidecars are the usual culprit: your app has a request, the injected proxy or log shipper does not. Add a request to every container, or switch that metric to type: ContainerResource with container: <your-app> so only the container you care about is measured.

the HPA was unable to compute the replica count: ... — this is the generic wrapper; the text after the colon is the real cause and is one of the two messages above, or failed to get memory utilization when the target’s pods all failed to report. Check kubectl top pod -l <selector> — if that works and the HPA still fails, the HPA’s selector and the Deployment’s selector disagree (the HPA uses the scale subresource’s selector, so a Deployment with a changed matchLabels can leave the HPA looking at the wrong pods).

<unknown> in kubectl get hpa that never resolves — same causes as above, in this order: no metrics-server, no request on some container, pods not Ready. If TARGETS shows a value for CPU but <unknown> for memory, it is the request.

ScalingLimited: True with TooManyReplicas or TooFewReplicas — not an error. The computed replica count hit maxReplicas or minReplicas. If memory sits at 95% with the HPA pinned at maxReplicas, the fleet is undersized or the request is too small; adding replicas is not going to help.

ScalingActive: False with ScalingDisabled — the target Deployment has replicas: 0, or the HPA was created against a resource that does not implement the scale subresource. The HPA does not scale from zero on resource metrics.

It scales up but never down — re-read the combination rule above. With CPU and memory both present, memory must also fall below target. Then check the stabilization window and any selectPolicy: Disabled. Then check whether kubectl top pod shows memory genuinely staying high; if it does, the HPA is behaving correctly and the workload is what needs looking at.

A quick diagnostic sequence that covers all of these:

kubectl get apiservice v1beta1.metrics.k8s.io           # Available: True?
kubectl top pod -n default -l app=worker                 # do numbers come back?
kubectl get deploy worker -o jsonpath='{.spec.template.spec.containers[*].resources.requests.memory}'
kubectl describe hpa worker-memory | sed -n '/Conditions/,/Events/p'
kubectl get hpa worker-memory -o jsonpath='{.status.currentMetrics}' | jq

The last line is the underrated one: status.currentMetrics shows the exact average utilization the controller computed, which is the number it is plugging into the formula. When the HPA’s arithmetic looks wrong, it is nearly always because that number is not what you assumed — usually because the average includes a pod with a very different request than the others.

When Memory Actually Works as a Signal

Memory-based HPA works when memory per pod is a function of the work in flight and is released when that work finishes. That describes a narrower set of workloads than most people assume, but it is not empty:

  • Queue consumers and stream processors that buffer messages in memory: more backlog means more memory per pod, and adding pods drains the backlog.
  • In-memory caches and session stores where you want to add capacity before eviction starts, not after latency degrades.
  • Image, PDF, or video processing workers whose per-request working set is large and short-lived.
  • Anything written in a runtime that returns memory to the OS promptly under low load — Rust, Go with GOMEMLIMIT set sensibly, most C++ services.

It does not work for JVM services, which allocate their heap up front and keep it, or for Go services without a memory limit, whose GC targets a percentage of live heap rather than an absolute ceiling. For those, memory utilization is a property of the configuration, not of the load, and the HPA has nothing to react to. The best practices article goes through the runtime-by-runtime detail; the short version is that if a graph of memory against requests per second is flat, memory is not your signal.

One combination to avoid outright: a VerticalPodAutoscaler in Auto mode and an HPA on the same resource. VPA raises the request, which lowers the utilization percentage, which makes the HPA scale in, which raises per-pod usage, which makes VPA raise the request again. Run VPA in Off (recommendations only) or Initial mode on any Deployment that has a memory HPA. And remember the HPA only ever adds pods — when it hits maxReplicas because there is no room on the nodes, that is the node autoscaler’s problem, covered in Cluster Autoscaler vs Karpenter.

CPU vs Memory as an HPA Signal

CPUMemory
Tracks request load for stateless servicesUsually yesUsually no
Compressible (throttled, not killed)YesNo — OOMKill
Falls quickly when load dropsSecondsMinutes to never, runtime-dependent
Warm-up grace period in the HPAYes, cpu-initialization-periodNone
Sensitive to request accuracyModeratelyExtremely
Safe default target60–70%80–90% as a safety valve; 70–75% only when memory is proven load-proportional
Typical failure modeScales late on latency-bound workScales up and never returns, or never scales at all

Frequently Asked Questions

Can Kubernetes HPA scale on memory and CPU at the same time?

Yes. List both as Resource metrics under spec.metrics in an autoscaling/v2 HorizontalPodAutoscaler. The controller computes a desired replica count for each metric separately and applies the largest one, so the Deployment scales up when either CPU or memory exceeds its target and scales down only when both are below target. A common production pattern is CPU at 60% as the driver and memory at 90% as a safety valve.

Does HPA use memory limits or requests?

Requests. averageUtilization is the pod’s memory usage divided by the sum of its containers’ memory requests, averaged across the target’s pods. Limits are never part of the calculation. If any container in a pod has no memory request, the HPA cannot compute utilization for that pod and reports missing request for memory, and it will not scale on that metric until the request is added.

Why is my memory HPA not scaling down?

Three usual reasons. First, if the HPA also has a CPU metric, the maximum of the two proposals wins, so memory has to fall below its target too. Second, the default 300-second scale-down stabilization window uses the highest desired replica count seen in that window, so a single high sample keeps the fleet up for five minutes. Third, and most often, the memory simply is not falling: JVM heaps and Go runtimes without GOMEMLIMIT hold memory after load drops, so the HPA is reporting the truth.

What does “unable to get metrics for resource memory: no metrics returned from resource metrics api” mean?

The HPA queried the metrics.k8s.io API for the target pods’ memory and got nothing back. Check that metrics-server is installed and its APIService is Available, that kubectl top pod returns numbers for the target pods, and that the pods have been running long enough for a sample to exist. On local clusters the fix is often adding --kubelet-insecure-tls to the metrics-server deployment.

Should I use averageUtilization or averageValue for memory?

Use averageUtilization when the memory request is a meaningful baseline, so that right-sizing the request automatically re-tunes the HPA. Use averageValue (an absolute quantity such as 1536Mi) when the request is deliberately set low for scheduling reasons or when you know the real working set per pod and want to target it directly. Both feed the same ratio formula and are subject to the same 10% tolerance.

Can HPA scale a Deployment to zero on memory?

Not on resource metrics. With HPAScaleToZero enabled (beta and on by default since Kubernetes 1.37) an HPA can set minReplicas: 0, but only when at least one object or external metric is configured; with only CPU or memory metrics there are no pods to measure, so the HPA cannot decide when to scale back up. Scale-to-zero on queue depth or request rate is a job for KEDA.

Conclusion

A memory HPA is three lines of YAML and one requirement — a memory request on every container — but the behavior behind those lines is a ratio against requests, a 10% tolerance band, a maximum-wins rule across metrics, and a five-minute rolling maximum on the way down. Once you hold those four facts, every surprising thing a memory HPA does becomes predictable. Set memory as a safety valve next to CPU unless you have a graph proving memory tracks load; slow the scale-down down; and when it misbehaves, read status.currentMetrics before you touch the target.

EKS Auto Mode: Pricing, vs Managed Node Groups and Karpenter, and What Breaks

EKS Auto Mode: Pricing, vs Managed Node Groups and Karpenter, and What Breaks

EKS Auto Mode is Amazon EKS with AWS running the node layer for you: it provisions EC2 instances with Karpenter-style logic, patches and replaces them on a fixed lifetime, and runs networking, DNS, block storage and load balancing as managed capabilities instead of add-ons you install. You pay for it with a management fee per EC2 instance-hour, on top of the normal EC2 price, and you give up node-level access and custom AMIs.

That is the whole trade in one sentence. The rest of this guide is about whether it is a good trade for your cluster: how the pricing actually works (with a worked example), how Auto Mode compares to managed node groups, self-managed Karpenter and Fargate, what changed in 2025-2026, what breaks when you migrate, and a one-week pilot to decide without risking production.

What EKS Auto Mode is

EKS Auto Mode, generally available since December 1, 2024, shifts more Kubernetes infrastructure responsibility from you to AWS. You still run an EKS cluster in your AWS account, and your workloads still use the Kubernetes API, but AWS takes over much of the compute, storage, networking, node lifecycle, and core add-on management that platform teams usually wire together themselves.

For compute, Auto Mode uses Karpenter under the hood. When pods are unschedulable, Auto Mode provisions nodes that fit the workload’s requirements: instance family, size, architecture, capacity type, and availability zone. When capacity is no longer useful, it consolidates and terminates nodes. The nodes run a locked-down variant of Bottlerocket with SELinux enforcing and a read-only root filesystem, and there is no SSH or SSM access.

The important framing is this: Auto Mode is not “EKS without nodes.” It is EKS where AWS manages the node lifecycle more aggressively. You own the workloads, their scheduling requirements, their disruption behavior, and the operational consequences of those choices. AWS owns more of the infrastructure plumbing.

What it replaces

Before Auto Mode, running EKS in production usually meant choosing and operating several layers yourself:

  • Managed node groups: you chose instance types, defined scaling ranges, managed AMI updates, handled node draining, and configured Cluster Autoscaler or another scaling mechanism.
  • Self-managed Karpenter: more flexible than managed node groups, but you owned the Karpenter controller, IAM, NodePools, EC2NodeClasses, the Spot interruption queue, disruption settings, upgrades, and failure modes.
  • Fargate: AWS-managed compute per pod, with no node management, but no DaemonSets, a narrower workload compatibility envelope, and a different cost model.

EKS Auto Mode replaces a large part of that platform assembly with a managed model: declare workload intent and high-level compute constraints; AWS provisions and manages the EC2 instances behind it. If you are still deciding between the two open-source autoscalers, see Cluster Autoscaler vs Karpenter first; Auto Mode is essentially “Karpenter, operated by AWS”.

EKS Auto Mode pricing: how the fee actually works

This is the part most people search for, and the part most summaries get slightly wrong. The official structure, from the Amazon EKS pricing page (checked September 2026), is:

Monthly cost of an EKS Auto Mode cluster =
    EKS control plane            ($0.10 per cluster-hour, standard support)
  + EC2 instances                (normal price: On-Demand, Spot, RI or Savings Plans)
  + EKS Auto Mode management fee (per instance-hour, by instance type and region)
  + everything else              (EBS, load balancers, NAT, data transfer, CloudWatch...)

Auto Mode fee = Σ (Auto Mode-managed instance runtime × regional fee for that instance type)

Four details matter more than the headline:

  • The fee is per instance, not per cluster. It is charged only on EC2 instances that Auto Mode launches and manages. A cluster that runs a mix of Auto Mode nodes and legacy managed node groups only pays the fee on the Auto Mode ones.
  • It is billed like EC2: per second, with a one-minute minimum.
  • It is independent of how you buy the EC2 capacity. On-Demand, Spot, Reserved Instances and Compute Savings Plans all work with Auto Mode, but the management fee does not get discounted with them. This is the single most important pricing detail, and we will see why in the example below.
  • It varies by instance type and region. AWS does not publish it as a contractual percentage. In the public examples for US West (Oregon) the fee works out to exactly 12% of the On-Demand rate, but always pull the real per-type rate for your region before modelling anything.

The published Oregon examples are:

InstanceEC2 On-Demand ($/h)Auto Mode fee ($/h)Fee as % of On-Demand
m5a.xlarge0.1720.0206412%
m5a.2xlarge0.3440.0412812%
c6a.2xlarge0.3060.0367212%
c6a.4xlarge0.6120.0734412%

GPU and accelerated instances have their own fee schedule, and AWS cut it on July 1, 2026: management fees for G-series instances dropped by 35%, and for P-series and Trainium by 60%, automatically for every Auto Mode cluster. If your earlier cost model for an ML cluster was built in 2025, redo it.

Worked example: 10 × m5a.2xlarge for a month

Take a steady cluster of ten m5a.2xlarge nodes in us-west-2, running 730 hours a month:

Line itemManaged node groupsEKS Auto Mode
Control plane$73.00$73.00
EC2 On-Demand (10 × $0.344 × 730)$2,511.20$2,511.20
Auto Mode fee (10 × $0.04128 × 730)—$301.34
Total$2,584.20$2,885.54

On raw bill, Auto Mode is about +11.7% for this cluster. Now apply discounts to the EC2 part only, because that is what happens in reality (the discount levels below are illustrative, not quotes):

EC2 purchase optionEC2 costAuto Mode feeFee relative to your EC2 bill
On-Demand$2,511.20$301.3412%
Savings Plan at ~30% off$1,757.84$301.34~17%
Spot at ~65% off$878.92$301.34~34%

The better you already are at buying compute, the more expensive Auto Mode looks in relative terms, because the fee is anchored to the On-Demand-scale price and does not shrink with your discount. A team that runs mostly Spot pays the equivalent of roughly a third on top of its compute bill.

The other side of the ledger is efficiency. If Auto Mode’s consolidation lets the same workloads run on nine nodes instead of ten, the Auto Mode bill becomes $2,260.08 EC2 + $271.21 fee + $73 = $2,604.29, within 1% of the managed node group cluster. In other words, you need roughly 11-12% better bin packing to break even on the raw bill at On-Demand prices. Clusters with fixed-shape node groups sized “just in case” often waste more than that; clusters already on well-tuned Karpenter usually do not.

Then add the part the bill does not show. The fee on this cluster is about $3,600 a year. At 100 nodes it is about $36,000 a year. Compare that against the hours your team spends on AMI rotation, autoscaler upgrades, add-on version matrices and node incidents. For a two-person platform team the fee is usually cheap; for a large platform team that already automated all of it, it is usually not.

Fargate for comparison

Fargate is priced per pod, per second, on requested vCPU and memory. In US East (N. Virginia), Linux/x86 is about $0.04048 per vCPU-hour and $0.004446 per GB-hour (September 2026), so a 1 vCPU / 2 GB pod running all month costs about $36. There is no node to pay for, but also nothing to bin-pack: you pay for requests, not usage, which makes right-sized requests and limits even more important than on EC2. And Amazon EKS does not support Fargate Spot.

EKS Auto Mode vs managed node groups vs Karpenter vs Fargate

EKS Auto ModeManaged node groupsSelf-managed KarpenterFargate
Who runs node provisioningAWS (managed Karpenter)You (ASG + Cluster Autoscaler)You (Karpenter controller)AWS, per pod
Instance selectionDynamic via NodePoolsFixed list per node groupDynamic via NodePoolsNot exposed
Node OS / AMIBottlerocket variant, chosen by AWSAL2023, Bottlerocket, Windows or customAL2023, Bottlerocket, Windows or customNot exposed
AMI patchingAutomatic, weekly AMIs, 21-day max node lifetimeYou roll updatesYou set expireAfter and driftN/A
SSH / SSM to nodesNoYes, if enabledYes, if enabledNo
DaemonSetsYes, but no host changesYesYesNo
WindowsNoYesYesNo
SpotYes, interruptions handled nativelyYesYes, you configure the SQS interruption queueNo
Networking, DNS, EBS CSI, LB controllerManaged capabilitiesAdd-ons you install and upgradeAdd-ons you install and upgradeManaged, with Fargate limits
Extra costManagement fee per instance-hourNoneNone (controller runs on your capacity)Per-pod vCPU/GB pricing
Operational burdenLow for nodes, medium for workload compatibilityMediumMedium-highLow for nodes, medium for compatibility
Right forTeams that don’t need node customizationRegulated/custom node environmentsMature platform teams that want full controlIsolated, simple workloads

EKS Auto Mode vs managed node groups

This is the comparison most teams actually face. Managed node groups give you a fixed capacity shape and full control of the node: your AMI, your kubelet flags, your host agents. Auto Mode gives you dynamic capacity and removes node operations, but takes the node away from you.

Pick managed node groups if you need custom or hardened AMIs, Windows, host-level security tooling, specific kernel settings, or if your capacity is steady and already heavily discounted (the fee hurts most there). Pick Auto Mode if your node groups are over-provisioned, your team spends real time on AMI rotations and add-on upgrades, and your workloads tolerate nodes being replaced at least every 21 days.

The two can coexist in the same cluster, which is also how migrations work: new workloads on Auto Mode, legacy or special ones on node groups.

EKS Auto Mode vs Karpenter

Auto Mode is Karpenter, but not your Karpenter. Both share the karpenter.sh/v1 NodePool API; the node class differs (NodeClass in eks.amazonaws.com/v1 for Auto Mode, EC2NodeClass in karpenter.k8s.aws/v1 for self-managed), and so do the instance labels (eks.amazonaws.com/instance-family vs karpenter.k8s.aws/instance-family).

Self-managed Karpenter has no fee and lets you choose the AMI, OS (including Windows), and node lifetime. In exchange you operate the controller, its IAM, the Spot interruption queue, GPU drivers and device plugins, and every upgrade. Auto Mode handles Spot interruptions without an SQS queue, ships NVIDIA and Neuron drivers, enables node repair by default, and upgrades the controller for you. If you already run Karpenter well, the fee buys you little. If you were about to adopt Karpenter, Auto Mode removes most of the adoption cost.

How it works in practice

You create or update an EKS cluster with Auto Mode enabled. The default setup uses two AWS-managed built-in node pools, system and general-purpose, which you can enable or disable but not modify. If you need more control, you create a NodeClass for Auto Mode infrastructure settings and a NodePool for workload-facing scheduling constraints.

# NodeClass: EKS Auto Mode infrastructure settings for managed EC2 nodes.
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
  name: private-compute
spec:
  subnetSelectorTerms:
    - tags:
        kubernetes.io/role/internal-elb: "1"
  securityGroupSelectorTerms:
    - tags:
        aws:eks:cluster-name: prod-eks
  ephemeralStorage:
    size: "100Gi"
# NodePool: workload-facing constraints for nodes that Auto Mode may provision.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-purpose-custom
spec:
  template:
    metadata:
      labels:
        billing-team: platform
    spec:
      nodeClassRef:
        group: eks.amazonaws.com
        kind: NodeClass
        name: private-compute
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: eks.amazonaws.com/instance-category
          operator: In
          values: ["c", "m", "r"]
  limits:
    cpu: "1000"
    memory: 1000Gi
  disruption:
    consolidationPolicy: Balanced
    consolidateAfter: 1m

The NodeClass is where you express AWS infrastructure placement and node-level defaults. The NodePool is where you express what kind of capacity is acceptable for workloads. Do not copy a self-managed Karpenter EC2NodeClass into Auto Mode; Auto Mode uses its own NodeClass API.

A few defaults are worth knowing because they drive both disruption and cost. Auto Mode consolidates underutilized nodes, expires nodes after 336 hours (14 days) unless you change it, never lets a node live beyond 21 days, applies a disruption budget of 10% of nodes, and replaces nodes for drift when a new Auto Mode AMI ships, roughly once a week. consolidationPolicy accepts WhenEmpty, WhenEmptyOrUnderutilized and Balanced; the last one weighs disruption cost against savings and skips consolidations that are not worth it, which is a sensible default for most production pools. A billing label in the NodePool template (like billing-team above) is the cheapest way to get per-team cost allocation later.

Built-in components are managed differently than in a classic EKS build. Pod networking (with network policy enforcement), service networking, cluster DNS, compute autoscaling, EBS block storage, load balancing, the Pod Identity agent and node monitoring are Auto Mode capabilities. With Auto Mode compute, add-ons such as Amazon VPC CNI, kube-proxy, CoreDNS, the Amazon EBS CSI Driver and the EKS Pod Identity Agent become redundant for Auto Mode nodes, and the controllers run on AWS-owned infrastructure rather than as visible pods in your account. Autoscaling of replicas is still yours: Auto Mode adds nodes for pending pods, but you still need an HPA or KEDA to create those pods (see HPA best practices).

The workload support matrix is broader than Fargate, but not identical to self-managed nodes:

CapabilityAuto Mode status
EC2 SpotSupported through karpenter.sh/capacity-type (spot, on-demand, reserved)
Graviton / arm64Supported through kubernetes.io/arch: arm64
GPU / acceleratorsSupported for documented NVIDIA, Trainium and Inferentia families; drivers and device plugins managed
Capacity reservationsODCRs and Capacity Blocks for ML via capacityReservationSelectorTerms
Windows nodesNot supported (Linux only)
DaemonSetsSupported, but host-level assumptions must be validated against locked-down nodes

What changed in 2025 and 2026

Auto Mode has moved fast since launch, and several of the early “reasons not to use it” are gone. The ones worth knowing, all from AWS announcements:

  • October 2025, networking and security controls. The NodeClass gained podSubnetSelectorTerms and podSecurityGroupSelectorTerms for separate pod subnets, advancedNetworking options for public IP control and forward HTTPS proxies (httpsProxy, noProxy), certificateBundles for enterprise CAs, KMS encryption of root and ephemeral volumes, and support for On-Demand Capacity Reservations and Capacity Blocks for ML. Corporate proxies and private CAs used to be hard blockers; they are not anymore.
  • October 2025, GovCloud. Auto Mode became available in AWS GovCloud (US-East) and (US-West), and later in the AWS China Regions.
  • February 2026, enhanced logging. Auto Mode’s managed capabilities (compute autoscaling, block storage, load balancing, pod networking) can be configured as CloudWatch Vended Logs sources and delivered to CloudWatch Logs, S3 or Firehose. This closes the “black box” gap: you can finally see why the managed Karpenter did or did not launch a node.
  • April 2026, hidden managed instances. New Auto Mode instances and their resources are hidden by default from EC2 console views and API list operations. Good for accidental-deletion safety, surprising for cost and inventory tooling that lists EC2 instances directly. Adjust the managed resource visibility settings if your FinOps scripts rely on them.
  • July 2026, cheaper accelerated compute. Management fees cut by 35% for G-series and 60% for P-series and Trainium, effective July 1.
  • July 2026, zonal shift. Integration with Application Recovery Controller zonal shift: during a zonal shift Auto Mode stops provisioning in the impaired AZ and pauses voluntary disruption, at no extra cost.
  • July 2026, EFA and placement groups for Auto Mode node pools, aimed at distributed training.
  • Static capacity NodePools. Setting replicas on a NodePool keeps a fixed number of nodes regardless of demand, useful for latency-sensitive inference. Once set it cannot be removed, and static pools are never consolidated, so set limits.nodes above replicas to leave headroom for node replacement.

What you gain

Reduced operational surface. Node group management, AMI lifecycle, Cluster Autoscaler tuning, Karpenter controller upgrades, and a chunk of add-on wiring move out of your day-to-day scope.

Better provisioning shape by default. Dynamic provisioning is usually a better fit than fixed node group shapes. You get nodes that more closely match actual pod requirements instead of trying to pre-plan a small set of instance types, and that is exactly the efficiency that has to pay for the fee.

Automatic node patching. AWS manages the node image and replacement flow. That reduces toil, but it also means your workloads need disruption policies that let AWS replace nodes safely.

Faster cluster bootstrapping. A new Auto Mode cluster gets to a usable production baseline faster than a hand-assembled EKS cluster with node groups, autoscaling, networking add-ons, storage drivers, and load balancer controllers.

Native Spot integration. Spot interruptions, rebalance recommendations, scheduled maintenance and instance health events are handled for you, with no Node Termination Handler or SQS queue. You still need workload-level interruption tolerance: replicas, budgets, graceful shutdown, and queue semantics where relevant.

What you give up

Node-level access. Auto Mode nodes are intentionally locked down. If your incident response process assumes SSH, SSM, manual package inspection, or ad hoc host changes, it needs to change. Ephemeral debug containers become your main tool; the patterns in debugging distroless containers apply almost verbatim.

Custom AMIs. AWS determines the operating system and AMI for Auto Mode managed instances; you cannot directly access the instance or install software on it. If your organization requires internally built, hardened, or certified AMIs, Auto Mode is likely blocked. (For how Bottlerocket compares to other container-optimized OSes, see Talos vs Bottlerocket vs Flatcar.)

Unrestricted host agents. Kubernetes DaemonSets are supported, but they are the sharp edge. Anything that assumes privileged host access, custom kernel modules, hostPath writes, or low-level runtime integration needs a proof of compatibility.

Less tuning surface. You give up direct control over kubelet flags, container runtime configuration, bootstrap scripts, and arbitrary node setup. That is the point of the product, but it is also the boundary.

Different cost visibility. Managed node groups make capacity easy to reason about because you chose it up front. Auto Mode changes capacity dynamically and, since April 2026, hides its instances from default EC2 listings, so cost control moves toward NodePool limits, billing labels, Cost Explorer and workload-level resource hygiene.

When NOT to use EKS Auto Mode

Auto Mode is a good default, but there are clear cases where it is the wrong answer:

  • You need Windows nodes. Auto Mode is Linux only.
  • You must own the node image supply chain: certified or hardened AMIs, CIS images built in-house, or specific kernel modules.
  • Your security or observability stack needs host access that the locked-down Bottlerocket nodes do not allow, and the vendor has no supported Auto Mode deployment.
  • Your compute is already heavily discounted and efficiently packed. With mostly Spot or Savings Plans and a well-tuned Karpenter, the fee is a large relative premium for work you have already automated.
  • You run singleton stateful workloads that cannot tolerate node replacement at least every 21 days. Auto Mode will rotate the node anyway; if the PDB blocks it, you are just postponing an incident.
  • You rely on an alternative CNI or networking setup that Auto Mode does not support.
  • You need tight kubelet or runtime tuning (custom eviction thresholds, runtime configuration, special sysctls on the host).

When to use EKS Auto Mode

Use Auto Mode if:

  • You run EKS on AWS and do not have a hard requirement to manage nodes yourself
  • You do not require custom AMIs or custom node bootstrap logic
  • You want to reduce the operations surface for node lifecycle management
  • You want Karpenter-like provisioning without operating Karpenter yourself
  • Your workloads are mostly stateless or disruption-tolerant
  • Your observability, security, and storage agents are compatible with Auto Mode

Stick with managed node groups if:

  • Your organization requires internally certified or hardened AMIs
  • You need specific kernel configuration, kubelet flags, bootstrap scripts, or host packages
  • You depend on privileged DaemonSets or host-level security tooling that Auto Mode cannot support
  • You are in a regulated environment where the node image supply chain must be owned internally
  • Your platform team already operates Karpenter well and values the extra control

Use Fargate if:

  • You specifically want per-pod compute isolation
  • Your workload does not need DaemonSets or host-level integrations
  • You accept Fargate’s scheduling, networking, storage, and observability constraints
  • You want to avoid managing EC2 capacity entirely for a narrow class of workloads

Migration from managed node groups

Migrating an existing cluster to Auto Mode is supported, but it is not a one-command operational migration. You must update the cluster IAM role permissions and trust policy, enable the compute, block storage, and load balancing capabilities together, and meet required add-on versions when those add-ons are installed. AWS does not support direct migration of EBS volumes from the standard EBS CSI provisioner to the Auto Mode one, of existing load balancers from AWS Load Balancer Controller to Auto Mode, or of clusters using alternative CNIs. A conservative path looks like this:

  1. Enable Auto Mode on a non-production cluster running a supported Kubernetes version (1.29 or later).
  2. Inventory workloads by scheduling assumptions: node selectors, affinities, tolerations, topology spread, PDBs, privileged mode, hostPath, local storage, and DaemonSet dependencies.
  3. Create or select the relevant Auto Mode NodeClass and NodePool resources.
  4. Move a low-risk namespace first by changing selectors, tolerations, or labels so pods land on Auto Mode nodes (eks.amazonaws.com/compute-type: auto identifies them).
  5. Watch scheduling, replacement, load balancer behavior, persistent volume provisioning, logging, metrics, and security events.
  6. Taint old node groups to stop new scheduling once the pilot workloads are stable.
  7. Drain old nodes gradually and delete old managed node groups only after workload owners have signed off.

You can keep AWS Load Balancer Controller installed during the migration when you need both models; separate them with IngressClass or loadBalancerClass and plan a blue-green move for existing load balancers.

The hardest part is usually not enabling Auto Mode. It is discovering which workloads and platform agents quietly depended on a mutable node.

What breaks when you migrate

Auto Mode changes the node contract. The Kubernetes API still looks familiar, but the host underneath is no longer yours in the same way.

DaemonSets need a compatibility audit. Logging agents, metrics agents, service mesh node components, security scanners, CSI node plugins, and custom infrastructure daemons often assume host access. Datadog, Falco, custom CSI drivers, eBPF agents, file integrity tools, and in-house node agents should be tested explicitly rather than assumed compatible.

PodDisruptionBudgets can block AWS-managed maintenance. If every critical Deployment has maxUnavailable: 0, or singleton workloads have no safe disruption path, node replacement becomes harder, and the 21-day lifetime means it will happen anyway. Auto Mode can manage nodes, but it cannot make an application disruption-tolerant after the fact.

nodeSelector and affinity rules can strand pods. Workloads pinned to old node group labels, instance types, capacity labels, zones, or custom AMI labels may never schedule on Auto Mode capacity. Replace legacy labels with stable requirements Auto Mode can satisfy, and remember that instance labels use the eks.amazonaws.com/ prefix, not karpenter.k8s.aws/.

topologySpreadConstraints can become too strict. Auto Mode provisions capacity dynamically, but strict zone spreading plus narrow selectors can create unschedulable pods. Check whenUnsatisfiable, label selectors, and minimum domain assumptions.

Privileged pods and hostPath volumes are migration blockers until proven otherwise. Anything that needs /var/lib, /proc, /sys, container runtime sockets, kernel capabilities, or host networking deserves a separate test. For a broader list of patterns that hurt under node churn, see Kubernetes features that hurt in production.

Observability and security agents may lose host assumptions. The dangerous failure mode is not CrashLoopBackOff; it is silent loss of telemetry or enforcement.

Storage drivers must be reviewed. EBS integration is part of Auto Mode, but it uses the provisioner ebs.csi.eks.amazonaws.com, not the standard ebs.csi.aws.com. Custom CSI drivers, EFS patterns, snapshot controllers, and topology-aware storage classes should be validated. Pay attention to provisioner names, volume binding mode, encryption settings, and IAM assumptions.

Runbooks need rewriting. “SSH to the node and inspect X” is not a valid first response anymore. Incident procedures should move toward kubectl describe, events, logs, ephemeral debug containers, the Auto Mode vended logs, cloud-side metrics, and vendor-supported diagnostics.

Cost and inventory scripts may go blind. Since April 2026, Auto Mode instances are hidden from default EC2 list operations. Anything that inventories or tags instances by listing EC2 directly needs the visibility setting changed or a switch to Kubernetes-side data.

1-week pilot: evaluate Auto Mode without risking production

Use a short pilot to answer compatibility and economics questions before touching production.

Day 1: build a representative test cluster. Same region, Kubernetes minor version, VPC shape, IAM model, ingress pattern, and storage classes where possible.

Day 1-2: enable Auto Mode with one custom NodeClass and NodePool. Keep the first pool boring: on-demand capacity, two or three common instance families, the same private subnet pattern as production, and a billing label.

Day 2-3: move three workload types. One stateless service, one stateful service with EBS, and one platform-heavy workload that uses observability or security agents. Run the scheduling audit (selectors, affinity, topology spread, PDBs, tolerations, privileged mode, hostPath, DaemonSets) before each move.

Day 4: force normal failure modes. Roll deployments, delete pods, scale replicas up and down, trigger consolidation, and simulate one Spot-tolerant workload if you plan to use Spot.

Day 5: validate platform signals. Confirm logs, metrics, traces, runtime alerts, security events, load balancer provisioning, DNS, and persistent volume operations. Enable the Auto Mode vended logs and check that you can explain every node launch.

Day 6-7: compare costs and toil. Record the EC2 instance mix, the published fee for each instance type in your region, pod density, pending time, interruption behavior, and operator actions required. Rerun the worked example above with your real numbers and your real discounts.

Success criteria should be explicit:

  • 95%+ of pilot pods schedule without manual intervention
  • No silent loss of logs, metrics, traces, or security alerts
  • PDBs allow node replacement for replicated services
  • Stateful workloads survive rescheduling and volume attachment tests
  • Cost model is understood at instance-type level, including the fee on discounted capacity
  • Production migration blockers are documented with owners

If the pilot fails, that is still useful. It tells you which node assumptions are real and which workloads should stay on managed node groups.

The honest assessment

EKS Auto Mode is a good default candidate for many teams running Kubernetes on AWS, and a better one in late 2026 than at launch: proxies, private CAs, capacity reservations, logging and zonal shift were real gaps that have been closed. The operational simplification is real: node provisioning, AMI updates, core add-on integration, and scaling behavior are areas where teams burn time and create incidents.

The constraints are also real. Custom AMIs, Windows, unrestricted host access from DaemonSets, privileged pods, custom CSI drivers, and strict disruption policies are the common blockers. And the fee is not trivial for teams that already buy compute cheaply: it does not shrink with Spot or Savings Plans.

The right question is not whether Auto Mode is “better” than managed node groups. It is which operational contract your workloads can live with, and whether the fee is smaller than the node-management work it removes. The more your organization can treat nodes as replaceable capacity, the more value you get. The more your platform treats nodes as customized machines, the less Auto Mode fits.

Frequently Asked Questions

How much does EKS Auto Mode cost?

You pay the normal EKS control plane fee ($0.10 per cluster-hour on standard support), the normal EC2 price for the instances, and an EKS Auto Mode management fee per instance-hour that depends on instance type and region, billed per second with a one-minute minimum. In AWS’s published US West (Oregon) examples the fee equals 12% of the On-Demand price (for example $0.04128/hour on an m5a.2xlarge), but always check the rate for your instance types and region on the EKS pricing page.

Do Savings Plans, Reserved Instances or Spot reduce the EKS Auto Mode fee?

No. You can use On-Demand, Spot, Reserved Instances and Compute Savings Plans for the EC2 capacity that Auto Mode launches, and those discounts apply to the EC2 part of the bill, but the Auto Mode management fee is independent of the purchase option. That is why the fee is a larger relative premium for teams that already run mostly Spot or discounted capacity.

Is EKS Auto Mode cheaper than managed node groups?

Not on the raw bill for the same capacity: managed node groups have no management fee. Auto Mode can still lower total cost if its consolidation and right-sized instance selection remove enough idle capacity (around 11-12% better packing breaks even at On-Demand prices) or if it saves meaningful platform engineering time. Measure it with a pilot and your real discounts.

What is the difference between EKS Auto Mode and Karpenter?

Auto Mode runs Karpenter for you. Both use the same karpenter.sh/v1 NodePool API, but Auto Mode uses its own NodeClass (eks.amazonaws.com/v1) instead of EC2NodeClass, a locked-down Bottlerocket AMI chosen by AWS, native Spot interruption handling and managed GPU drivers. Self-managed Karpenter has no fee and lets you choose AMIs, OS and node lifetime, but you operate the controller, IAM, interruption queue and upgrades.

Can I enable EKS Auto Mode on an existing cluster?

Yes. Auto Mode can be enabled on existing clusters on a supported Kubernetes version, provided the cluster meets the IAM, add-on and networking requirements. Existing managed node groups keep running while you move workloads gradually. EBS volumes from the standard CSI provisioner and existing AWS Load Balancer Controller load balancers do not migrate directly and need a planned cut-over.

Can I use my existing Karpenter NodePools and EC2NodeClasses with Auto Mode?

Not directly. NodePools are the same API and usually port with small changes (instance labels use the eks.amazonaws.com/ prefix), but the self-managed EC2NodeClass has no equivalent in Auto Mode; you need to recreate its settings as an Auto Mode NodeClass and review every field before porting anything.

Does EKS Auto Mode support Windows nodes or SSH access?

No to both. Auto Mode supports only Linux nodes, and the nodes do not allow SSH or SSM access. Windows workloads should stay on managed node groups or self-managed nodes, and troubleshooting moves to Kubernetes-level tools such as ephemeral debug containers, events, logs and the Auto Mode vended logs.

Does EKS Auto Mode replace HPA or KEDA?

No. Auto Mode handles node provisioning and lifecycle: it adds nodes when pods are pending and removes them when they are not needed. It does not decide how many replicas your application should run. You still need HPA, KEDA or application-level scaling for pod replica counts.

Sources

Kubernetes Vertical Pod Autoscaling Explained: When and Why to Scale Vertically

Kubernetes Vertical Pod Autoscaling Explained: When and Why to Scale Vertically

Kubernetes has introduced as its alpha version in its Kubernetes 1.27 release the Vertical Pod Autoscaling capability to provide the option for the Kubernetes workload to be able to scale using the “vertical” approach by adding more resources to an existing pod. This increases the autoscaling capabilities of your Kubernetes workloads that you have at your disposal such as KEDA or Horizontal Pod Autoscaling.

Vertical Scaling vs Horizontal Scaling

Vertical and horizontal scaling are two approaches used in scaling up the performance and capacity of computer systems, particularly in distributed systems and cloud computing. Vertical scaling, also known as scaling up or scaling vertically, involves adding more resources, such as processing power, memory, or storage, to a single instance or server. This means upgrading the existing compute components or migrating to a more powerful infrastructure. Vertical scaling is often straightforward to implement and requires minimal changes to the software architecture. It is commonly used when the system demands can be met by a single, more powerful infrastructure.

On the other hand, horizontal scaling, also called scaling out or scaling horizontally, involves adding more instances or servers to distribute the workload. Instead of upgrading a single instance, multiple instances are employed, each handling a portion of the workload. Horizontal scaling offers the advantage of increased redundancy and fault tolerance since multiple instances can share the load. Additionally, it provides the ability to handle larger workloads by simply adding more machines to the cluster. However, horizontal scaling often requires more complex software architectures, such as load balancing and distributed file systems, to efficiently distribute and manage the workload across the machines.

In summary, vertical scaling involves enhancing the capabilities of a single object, while horizontal scaling involves distributing the workload across multiple instances. Vertical scaling is easier to implement but may have limitations in terms of the maximum resources available on a single machine. Horizontal scaling provides better scalability and fault tolerance but requires more complex software infrastructure. The choice between vertical and horizontal scaling depends on factors such as the specific requirements of the system, the expected workload, and the available resources.

Why Kubernetes Vertical AutoScaling?

This is an interesting topic because we have been living in a world where the state was that was always better to scale out (using Horizontal Scaling) rather than scaling up (using Vertical Scaling) and especially this was one of the mantras you heard in cloud-native developments. And, that hasn’t changed because horizontal scaling provides much more benefits than vertical scaling and it is well covered with the Autoscaling capabilities or side-projects such as KEDA. So, in that case, why is Kubernetes including this feature and why are we using this site to discuss it?

Because with the transformation of Kubernetes to be the de-facto alternative to any deployment you do nowadays, the characteristic and capabilities of the workloads that you need to handle have extended and that’s why you need to use different techniques to provide the best experience to each of the workloads types

How Kubernetes Vertical Autoscaling?

Here you will find all the documentation about this new feature that as commented is still in the “alpha” stage to is something to try as an experimental mode rather than using it at the production level HPA Documentation

Vertical Scaling works in the way that you will be able to change the resources assigned to the pod, CPU, and memory without needing to restart the pod and change the manifest declaration and that’s a clear benefit of this approach. As you know, until now if you want to change the resources applied to a workload you need to update the manifest document and restart the pod to apply the new changes.

To define this you need to specify the resizePolicy by adding a new section to the manifest pod as you can see here:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-5
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr-5
    image: nginx
    resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired
    - resourceName: memory
      restartPolicy: RestartContainer
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

For example in this case we define for the different resource names the policy that we want to apply, if we’re going to change the cpu assigned it won’t require a restart but in case we’re changing the memory it would require a restart.

That implied that if would like to change the CPU assigned you can directly patch the manifest as you can see in the snippet below and that provides an update of the assigned resources:

 kubectl -n qos-example patch pod qos-demo-5 --patch '{"spec":{"containers":[{"name":"qos-demo-ctr-5", "resources":{"requests":{"cpu":"800m"}, "limits":{"cpu":"800m"}}}]}}'

When to use Vertical Scaling are the target scenarios?

It will depend on a lot of different scenarios from the use-case but also from the technology stack that your workload is using to know what of these capabilities can apply. As a normal thing, the CPU change will be easy to adapt to any technology but the memory one would be more difficult depending on the technology used as in most of the technologies the memory assigned is defined at the startup time.

This will help to update components that have changed their requirements as an average scenario or when you’re testing new workloads with live load and you don’t want to disrupt the current processing of the application or simply workloads that don’t support horizontal scaling because are designed on a single-replica mode

 Conclusion

In conclusion, Kubernetes has introduced Vertical Pod Autoscaling, enabling Kubernetes vertical autoscaling of workloads by adding resources to existing pods. Kubernetes Vertical autoscaling allows for resource changes without restarting pods, providing flexibility in managing CPU and memory allocations.

Kubernetes Vertical autoscaling offers a valuable option for adapting to evolving workload needs. It complements horizontal scaling by providing flexibility without the need for complex software architectures. By combining vertical and horizontal scaling approaches, Kubernetes users can optimize their deployments based on specific workload characteristics and available resources.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Kubernetes Autoscaling 1.26 Explained: HPA v2 Changes and Impact on KEDA

Kubernetes Autoscaling 1.26 Explained: HPA v2 Changes and Impact on KEDA

Introduction

Kubernetes Autoscaling has suffered a dramatic change. Since the Kubernetes 1.26 release, all components should migrate their HorizontalPodAutoscaler objects from the v1 to the new release v2that has been available since Kubernetes 1.23.

HorizontalPodAutoscaler is a crucial component in any workload deployed on a Kubernetes cluster, as the scalability of this solution is one of the great benefits and key features of this kind of environment.

A little bit of History

Kubernetes has introduced a solution for the autoscaling capability since the version Kubernetes 1.3 a long time ago, in 2016. And the solution was based on a control loop that runs at a specific interval that you can configure with the property --horizontal-pod-autoscaler-sync-period parameters that belong to the kube-controller-manager.

So, once during this period, it will get the metrics and evaluate through the condition defined on the HorizontalPodAutoscaler component. Initially, it was based on the compute resources used by the pod, main memory, and CPU.

Kubernetes Autoscaling 1.26: A Game-Changer for KEDA Users?

This provided an excellent feature, but with the past of time and adoption of the Kubernetes environment, it has been shown as a little narrow to handle all the scenarios that we should have, and here is where other awesome projects we have discussed here, such as KEDA brings into the picture to provide a much more flexible set of features.

Kubernetes AutoScaling Capabilities Introduced v2

With the release of the v2 of the Autoscaling API objects, we have included a range of capabilities to upgrade the flexibility and options available now. There most relevant ones are the following:

  • Scaling on custom metrics: With the new release, you can configure an HorizontalPodAutoscaler object to scale using custom metrics. When we talk about custom metrics, we talk about any metric generated from Kubernetes. You can see a detailed walkthrough about using Custom metrics in the official documentation
  • Scaling on multiple metrics: With the new release, you also have the option to scale based on more than one metric. So now the HorizontalPodAutoscalerwill evaluate each scaling rule condition, propose a new scale value for each of them, and take the maximum value as the final one.
  • Support for Metrics API: With the new release, the controller from the HoriztalPodAutoscaler components retrieves metrics from a series of registered APIs, such as metrics.k8s.io, custom.metrics.k8s.io ,external.metrics.k8s.io. For more information on the different metrics available, you can take a look at the design proposal
  • Configurable Scaling Behavior: With the new release, you have a new field, behavior, that allows configuring how the component will behave in terms of scaling up or scaling down activity. So, you can define different policies for the scaling up and others for the scaling down, limit the max number of replicas that can be added or removed in a specific period, to handle the issues with the spikes of some components as Java workloads, among others. Also, you can define a stabilization window to avoid stress when the metric is still fluctuating.

Kubernetes Autoscaling v2 vs KEDA

We have seen all the new benefits that Autoscaling v2 provides, so I’m sure that most of you are asking the same question: Is Kubernetes Autoscaling v2 killing KEDA?

Since the latest releases of KEDA, KEDA already includes the new objects under the autoscaling/v2 group as part of their development, as KEDA relies on the native objects from Kubernetes, and simplify part of the process you need to do when you want to use custom metric or external ones as they have scalers available for pretty much everything you could need now or even in the future.

But, even with that, there are still features that KEDA provides that are not covered here, such as the scaling “from zero” and “to zero” capabilities that are very relevant for specific kinds of workloads and to get a very optimized use of resources. Still, it’s safe to say that with the new features included in the autoscaling/v2 release, the gap is now smaller. Depending on your needs, you can go with the out-of-the-box capabilities without including a new component in your architecture.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)

KEDA provides a rich environment to scale your application apart from the traditional HPA approach using CPU and Memory

Autoscaling is one of the great things of cloud-native environments and helps us to provide an optimized use of the operations. Kubernetes provides many options to do that being one of those the Horizontal Pod Autoscaler (HPA) approach.

HPA is the way Kubernetes has to detect if it is needed to scale any of the pods, and it is based on the metrics such as CPU usage or memory.

https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

Sometimes those metrics are not enough to decide if the number of replicas we have available is enough. Other metrics can provide a better perspective, such as the number of requests or the number of pending events.

Kubernetes Event-Driven Autoscaling (KEDA)

Here is where KEDA comes to help. KEDA stands for Kubernetes Event-Driven Autoscaling and provides a more flexible approach to scale our pods inside a Kubernetes cluster.

It is based on scalers that can implement different sources to measure the number of requests or events that we receive from different messaging systems such as Apache Kafka, AWS Kinesis, Azure EventHub, and other systems as InfluxDB or Prometheus.

KEDA works as it is shown in the picture below:

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)

We have our ScaledObject that links our external event source (i.e., Apache Kafka, Prometheus ..) with the Kubernetes Deployment we would like to scale and register that in the Kubernetes cluster.

KEDA will monitor the external source, and based on the metrics gathered, will communicate the Horizontal Pod Autoscaler to scale the workload as defined.

Testing the Approach with a Use-Case

So, now that we know how that works, we will do some tests to see it live. We are going to show how we can quickly scale one of our applications using this technology. And to do that, the first thing we need to do is to define our scenario.

In our case, the scenario will be a simple cloud-native application developed using a Flogo application exposing a REST service.

The first step we need to do is to deploy KEDA in our Kubernetes cluster, and there are several options to do that: Helm charts, Operation, or YAML files. In this case, we are going to use the Helm charts approach.

So, we are going to type the following commands to add the helm repository and update the charts available, and then deploy KEDA as part of our cluster configuration:

helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda

After running this command, KEDA is deployed in our K8S cluster, and it types the following command kubectl get all will provide a situation similar to this one:

pod/keda-operator-66db4bc7bb-nttpz 2/2 Running 1 10m
pod/keda-operator-metrics-apiserver-5945c57f94-dhxth 2/2 Running 1 10m

Now, we are going to deploy our application. As already commented to do that we are going to use our Flogo Application, and the flow will be as simple as this one:

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)
Flogo application listening to the requests
  • The application exposes a REST service using the /hello as the resource.
  • Received requests are printed to the standard output and returned a message to the requester

Once we have our application deployed on our Kubernetes application, we need to create a ScaledObject that is responsible for managing the scalability of that component:

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)
ScaleObject configuration for the application

We use Prometheus as a trigger, and because of that, we need to configure where our Prometheus server is hosted and what query we would like to do to manage the scalability of our component.

In our sample, we will use the flogo_flow_execution_count that is the metric that counts the number of requests that are received by this component, and when this has a rate higher than 100, it will launch a new replica.

After hitting the service with a Load Test, we can see that as soon as the service reaches the threshold, it launch a new replica to start handling requests as expected.

Event-Driven Autoscaling in Kubernetes with KEDA (Beyond CPU and Memory Metrics)
Autoscaling being done using Prometheus metrics.

All of the code and resources are hosted in the GitHub repository shown below:

https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/


Summary

This post has shown that we have unlimited options in deciding the scalability options for our workloads. We can use the standard metrics like CPU and memory, but if we need to go beyond that, we can use different external sources of information to trigger that autoscaling.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Kubernetes Autoscaling: Learn How to Scale Your Kubernetes Deployments Dynamically

Kubernetes Autoscaling: Learn How to Scale Your Kubernetes Deployments Dynamically

Discover the different options to scale your platform based on the traffic load you receive

When talking about Kubernetes, you’re always talking about the flexibility options that it provides. Usually, one of the topics that come into the discussion is the elasticity options that come with the platform — especially when working on a public cloud provider. But how can we really implement it?

Before we start to show how to scale our Kubernetes platform, we need to do a quick recap of the options that are available to us:

  • Cluster Autoscaler: When the load of the whole infrastructure reaches its peak, we can improve it by creating new worker nodes to host more service instances.
  • Horizontal Pod Autoscaling: When the load for a specific pod or set of pods reaches its peak, we deploy a new instance to ensure that we can have the global availability that we need.

Let’s see how we can implement these using one of the most popular Kubernetes-managed services, Amazon’s Elastic Kubernetes Services (EKS).


Setup

The first thing that we’re going to do is create a cluster with a single worker node to demonstrate the scalability behavior easily. And to do that, we’re going to use the command-line tool eksctl to manage an EKS cluster easily.

To be able to create the cluster, we’re going to do it with the following command:

eksctl create cluster --name=eks-scalability --nodes=1 --region=eu-west-2 --node-type=m5.large --version 1.17 --managed --asg-access

After a few minutes, we will have our own Kubernetes cluster with a single node to deploy applications on top of it.

Now we’re going to create a sample application to generate load. We’re going to use TIBCO BusinessWorks Application Container Edition to generate a simple application. It will be a REST API that will execute a loop of 100,000 iterations acting as a counter and return a result.

BusinessWorks sample application to show the scalability options
BusinessWorks sample application to show the scalability options

And we will use the resources available in this GitHub repository:

We will build the container image and push it to a container registry. In my case, I am going to use my Amazon ECR instance to do so, and I will use the following commands:

docker build -t testeks:1.0 .
docker tag testeks:1.0 938784100097.dkr.ecr.eu-west-2.amazonaws.com/testeks:1.0
docker push 938784100097.dkr.ecr.eu-west-2.amazonaws.com/testeks:1.0

And once that the image is pushed into the registry, we will deploy the application on top of the Kubernetes cluster using this command:

kubectl apply -f .\testeks.yaml

After that, we will have our application deployed there, as you can see in the picture below:

Image deployed on the Kubernetes cluster
Image deployed on the Kubernetes cluster

So, now we can test the application. To do so, I will make port 8080 available using a port-forward command like this one:

kubectl port-forward pod/testeks-v1-869948fbb-j5jh7 8080:8080

With that, I can see and test the sample application using the browser, as shown below:

Swagger UI tester for the Kubernetes sample application
Swagger UI tester for the Kubernetes sample application

Horizontal pod autoscaling

Now, we need to start defining the autoscale rules, and we will start with the Horizontal Pod Autoscaler (HPA) rule. We will need to choose the resource that we would like to use to scale our pod. In this test, I will use the CPU utilization to do so, and I will use the following command:

kubectl autoscale deployment testeks-v1 --min=1 --max=5 --cpu-percent=80

That command will scale the replica set testeks from one (1) instance to five (5) instances when the CPU utilization percent is higher than 80%.

If now we check the status of the components, we will get something similar to the image below:

HPA rule definition for the application using CPU utilization as the key metric
HPA rule definition for the application using CPU utilization as the key metric

If we check the TARGETS column, we will see this value: <unknown>/80%. That means that 80% is the target to trigger the new instances and the current usage is <unknown>.

We do not have anything deployed on the cluster to get the metrics for each of the pods. To solve that, we need to deploy the Metrics Server. To do so, we will follow the Amazon AWS documentation:

So, running the following command, we will have the Metrics Server installed.

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.7/components.yaml

And after doing that, if we check again, we can see that the current user has replaced the <unknown>:

Current resource utilization after installing the metrics-server on the Kubernetes cluster
Current resource utilization after installing the Metrics Server on the Kubernetes cluster

If that works, I am going to start sending requests using a Load Test inside the cluster. I will use the sample app defined below:

To deploy, we will use a YAML file with the following content:

https://gist.github.com/BetterProgramming/53181f3aa7bee7b7e3adda7c4ed8ca40#file-deploy-yaml

And we will deploy it using the following command:

kubectl apply -f tester.yaml

After doing that, we will see that the current utilization is being increased. After a few seconds, it will start spinning new instances until it meets the maximum number of pods defined in the HPA rule.

Pods increasing when the load exceeds the target defined in previous steps.
Pods increasing when the load exceeds the target defined in previous steps.

Then, as soon as the load also decreases, the number of instances will be deleted.

Pods are deleted as soon as the load decreases.
Pods are deleted as soon as the load decreases.

Cluster autoscaling

Now, we need to see how we can implement the Cluster Autoscaler using EKS. We will use the information that Amazon provides:

https://github.com/alexandrev/testeks

The first step is to deploy the cluster autoscaling, and we will do it using the following command:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml

Then we will run this command:

kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict=”false”

And we will edit the deployment to provide the current name of the cluster that we are managing. To do that, and we will run the following command:

kubectl -n kube-system edit deployment.apps/cluster-autoscaler

When your default text editor opens with the text content, you need to make the following changes:

  • Set your cluster name in the placeholder available.
  • Add these additional properties:
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false
Deployment edits that are needed to configure the Cluster Autoscaler
Deployment edits that are needed to configure the Cluster Autoscaler

Now we need to run the following command:

kubectl -n kube-system set image deployment.apps/cluster-autoscaler cluster-autoscaler=eu.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.17.4

The only thing that is left is to define the AutoScaling policy. To do that, we will use the AWS Services portal:

  • Enter into the EC service page on the region in which we have deployed the cluster.
  • Select the Auto Scaling Group options.
  • Select the Auto Scaling Group that has been created as part of the EKS cluster-creating process.
  • Go to the Automatic Scaling tab and click on the Add Policy button available.
Autoscaling policy option in the EC2 Service console
Autoscaling policy option in the EC2 Service console

Then we should define the policy. We will use the Average CPU utilization as the metric and set the target value to 50%:

Autoscaling policy creation dialog
Autoscaling policy creation dialog

To validate the behavior, we will generate load using the tester as we did in the previous test and validate the node load using the following command:

kubectl top nodes
kubectl top nodes’ sample output
kubectl top nodes’ sample output

Now we deploy the tester again. As we already have it deployed in this cluster, we need to delete it first to deploy it again:

kubectl delete -f .\tester.yaml
kubectl apply -f .\tester.yaml

As soon as the load starts, new nodes are created, as shown in the image below:

kubectl top nodes’ sample output
kubectl top nodes showing how nodes have been scaled up

After the load finishes, we go back to the previous situation:

kubectl top nodes showing how nodes have been scaled down
kubectl top nodes showing how nodes have been scaled down

Summary

In this article, we have shown how we can scale a Kubernetes cluster in a dynamic way both at the worker node level using the Cluster Autoscaler capability and at the pod level using the Horizontal Pod Autoscaler. That gives us all the options needed to create a truly elastic and flexible environment able to adapt to each moment’s needs with the most efficient approach.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.