Chapter 16 of 24

Autoscaling and Capacity

Separate pod demand, node supply, and AWS prerequisites so each controller has one job.

01Learning objectives

02Three loops, three signals

HPA scales replicas; Karpenter observes unschedulable requests and supplies nodes. Neither can compensate for missing requests and limits.

MechanismChangesCourse boundary
HPAReplica countWorkload GitOps, later chapter.
VPARecommended or applied requestsUse recommendation mode first; avoid fighting HPA on the same CPU or memory signal.
KarpenterApplication EC2 nodesArgo owns NodePool/EC2NodeClass; Terraform owns AWS identity and events.
Managed node groupsFixed platform poolsThe managed system and Cassandra node groups stay outside Karpenter.
FargateOne matching pod's computeKarpenter does not scale Fargate.

03AWS capacity details

CNI IP pressure is a separate capacity ceiling

Each EC2 node and Fargate pod needs VPC addresses. A subnet with no usable IPs can leave pods Pending while CPU and memory look empty. Inspect subnet free addresses, ENI limits, pod density, and VPC CNI prefix delegation before increasing node limits.

04Lab: test the ownership graph

Run mocked plans only. Provider installation may require registry access; these commands never apply AWS resources.

# Terraform
terraform -chdir=learn-terraform/capstone/infra/modules/eks test \
  -filter=tests/eks.tftest.hcl
terraform -chdir=learn-terraform/capstone/infra/modules/eks graph

# OpenTofu
tofu -chdir=learn-terraform/capstone/infra/modules/eks test \
  -filter=tests/eks.tftest.hcl
tofu -chdir=learn-terraform/capstone/infra/modules/eks graph

The Terraform graph and OpenTofu graph infer edges from references: the queue policy depends on rule ARNs and the Karpenter role policy depends on the node role, instance profile, cluster, and queue. Use explicit depends_on only for behavior that references cannot express.

05Review the plan as a capacity contract

Plan objectQuestionReject when
Karpenter roleAre launch and termination actions tag scoped?It can terminate arbitrary EC2 instances.
Interruption queueAre encryption, retention, SourceArn, SourceAccount, and TLS denial visible?The queue accepts arbitrary senders.
Node access entryIs it EC2_LINUX for the dedicated role?It grants administrator access.
OutputsDo they expose names/ARNs needed by GitOps?They export credentials or duplicate resources.

An output adds a consumer contract, not an AWS object. Both tools derive output dependencies from its value expression; an explicit output dependency is a last resort.

!Failure drill

Pods remain Pending while Karpenter reports no matching capacity. Read the scheduler event first. Check requests, taints/tolerations, NodePool requirements, EC2 quota and offerings, then subnet IP capacity. Raising the NodePool CPU limit does nothing when the VPC CNI cannot allocate another pod address.

06Verify boundaries

07Cleanup

Mocked tests and graph commands create no AWS resources. In a future applied environment, delete the Argo-owned NodePool first and wait for its NodeClaims to drain before removing the Terraform-owned Karpenter IAM, queue, or access entry.

08Quick check

Why can Karpenter not fix a pod with no request?

Scheduling demand is based on requests. Without them, packing and scaling signals do not represent the pod's real need.

Should VPA and HPA both change CPU for the same workload?

Usually not without careful policy. VPA changing CPU requests changes HPA utilization math and can create feedback loops.

What survives if Karpenter is unavailable?

The fixed system and Cassandra node groups, existing application nodes, and the Fargate profile remain. New application EC2 capacity may not appear.

09Recap and next

Requests describe demand, HPA changes pods, and Karpenter changes application EC2 supply. Terraform/OpenTofu provide the AWS identity and interruption graph; Argo owns cluster desired state.

Next: connect EBS CSI, encrypted gp3 storage, Availability Zone scheduling, snapshots, and clean-target restore.

10Source notes