Consider a straightforward operational question: what should the checkout-api service run at right now, and how many instances does it need? Helm configuration declares three replicas with 1000m CPU and 2Gi memory. Yet the running cluster presents a different reality. The Horizontal Pod Autoscaler has expanded the application to 11 replicas responding to CPU pressure. The Vertical Pod Autoscaler simultaneously claims the service is oversized, suggesting 350m CPU and 3.1Gi memory instead. KEDA has pushed for 24 replicas due to Kafka lag upstream. Karpenter has added two fresh nodes, sizing physical infrastructure against stated resource requests rather than what the workload actually consumes.

Five sources yield five contradictory answers. Each controller acts logically in isolation, but none sees the complete picture.

This disconnect between what each tool observes independently and what the workload genuinely requires exposes a fundamental architectural problem in how Kubernetes handles elasticity today.

The breakdown of the Unix philosophy in elastic systems

Kubernetes was designed around Unix principles: create focused, single-purpose tools that excel at one job. For orchestration, this seemed sensible:

  • HPA manages horizontal scaling based on observed resource consumption at runtime.
  • VPA adjusts container resource allocations based on what has actually been used historically.
  • KEDA connects external event sources—like message queue depth—to replica decisions.
  • Karpenter and Cluster Autoscaler provision node capacity when workloads cannot be scheduled.
  • GitOps tools (Argo CD, Flux) maintain baseline configuration from version control repositories.

In traditional Unix, tools like grep and awk compose effectively because they process unchanging text sequentially. One finishes, passes output to the next, and the chain completes.

Autoscaling controllers operate differently. They run concurrently, targeting the same workload and modifying identical API fields: .spec.replicas, .resources.requests, and the node pool itself. Each executes within its own reconciliation loop, assuming sole authority over sizing without knowing what adjacent tools are doing.

Determining workload capacity is a single, coupled mathematical equation. Splitting it across decoupled controllers creates thrashing instead of modular efficiency.

The four collisions: how autoscalers fight

When separate control loops modify variables that depend on each other, the result destabilizes into four recognizable failure patterns.

1. The denominator collapse: HPA vs. VPA

HPA determines how many replicas are needed by dividing actual CPU usage by the CPU requests declared in the pod specification:

current metric = total actual usage / total declared requests

The declared requests form the denominator. When VPA runs in parallel, it reduces CPU requests to cut waste. The denominator shrinks. A small traffic increase now pushes utilization percentage above HPA's threshold, causing aggressive horizontal expansion. As traffic distributes across more pods, per-pod usage falls, prompting VPA to cut requests further. The workload enters a destructive cycle: VPA continuously reduces pod size while HPA spawns an uncontrollable swarm of tiny containers.

2. Multi-writer race condition: native HPA vs. KEDA

When organizations use KEDA for queue-driven scaling alongside standard CPU-based HPA, both controllers write to the same deployment's /scale endpoint. A spike in the event queue triggers KEDA to scale up and process the backlog. But if those worker pods are I/O-bound with minimal CPU usage, standalone HPA observes low utilization, decides the app is oversized, and scales it back down. The API server logs alternating scale events while actual throughput remains stuck.

3. GitOps drift ping-pong: declarative IaC vs. dynamic runtime

GitOps platforms treat Git repositories as the authoritative source. When HPA scales a deployment during traffic spikes, Argo CD marks the app as OutOfSync. If automatic synchronization is active, GitOps resets the live deployment to its baseline configuration, removing capacity precisely when demand peaks. Many teams configure GitOps to ignore replica counts to avoid this, but that breaks the declarative model: Git no longer matches production, and future deployments risk silently restoring stale baselines.

4. The multiplier effect at the node layer

Node autoscalers like Karpenter provision instances based exclusively on declared container requests, ignoring actual usage. If a container requests 1000m CPU but uses only 150m, the scheduler still reserves the full core. Karpenter buys physical capacity for phantom demand, amplifying upstream sizing mistakes into enormous infrastructure costs. Across many production environments, average cluster CPU utilization stays around 8% despite substantial node investment.

The path to a better autoscaler

Current autoscalers each answer a narrow question: "What should my specific parameter be?" The actual operational need is wider: What mix of replicas, resource requests, limits, and scaling parameters meets latency SLOs while minimizing cost, and when should that mix change?

Horizontal and vertical sizing are linked parts of one optimization challenge. A genuine autoscaler cannot depend on reactive, single-metric rules. It must operate as a unified control plane with five essential qualities:

  1. Unified signal ingestion: Processing CPU and memory metrics, queue depth, transaction rates, latency SLO status, node pricing, and IaC definitions all at once.
  2. Joint decision-making: Producing a single action that sets both horizontal limits and vertical resource requests together, rather than splitting replicas to one tool and pod sizing to another.
  3. Continuous GitOps synchronization: Submitting automated, justified pull requests to application repositories instead of forcing GitOps to ignore live cluster state, keeping version control aligned with actual operations.
  4. Predictive and proactive capacity planning: Reactive scaling fails because new compute takes time to initialize. Reactive controllers wait for load to exceed arbitrary thresholds, causing latency spikes when demand jumps suddenly. A true autoscaler uses predictive models to study historical patterns, identify daily or weekly cycles, and anticipate workload demand. It prepares capacity and adjusts resources before traffic arrives, turning scaling from an emergency response into a smooth, planned operation.
  5. Explainable auditing: Documenting the reasoning behind each change, giving SREs complete insight into the tradeoffs between cost reduction, expected demand, and latency limits.

From reactive reflexes to true autonomy

The cloud-native world often treats "automated" and "autonomous" as synonyms. They are not.

Today's autoscalers are automated: they behave like biological reflexes, detect an isolated signal, and execute a preset action. HPA fires when an event occurs, KEDA scales on queue depth, Karpenter provisions nodes. But reflexes have no central control. When five independent reflexes activate on the same limb simultaneously, the result is chaos.

Today's autoscalers are automated: they act like biological reflexes. But reflexes lack a central nervous system. When five independent reflexes fire simultaneously on the same limb, the system thrashes.

Autonomy demands a closed loop that surveys the entire operating context, grasps the tradeoffs between performance and expense, and takes responsibility for the complete result.

The Unix philosophy still fits the execution layer: HPA, VPA, KEDA, and Karpenter should remain focused on the specific API changes they were built to make. What Kubernetes is missing is the intelligence layer above them that determines what those tools should actually do.

When your platform team next argues over HPA versus VPA, ask a deeper question: Who owns the decision about this workload's size, and do they have complete information?

In today's clusters, nobody does. Fixing Kubernetes rightsizing means creating a system that finally can.