A complete Kubernetes Cluster Autoscaler solution for automatic node scaling (1-8 nodes) managed via GitOps.
-
File:
infrastructure/terraform/prod/modules/kubernetes/variables.tf- Updated
asg_max_sizefrom 5 to 8 nodes
- Updated
-
File:
infrastructure/terraform/prod/variables.tf- Updated
asg_max_sizefrom 5 to 8 nodes
- Updated
Base Manifests (gitops/base/cluster-autoscaler/):
- ✅
namespace.yaml- Creates cluster-autoscaler namespace - ✅
serviceaccount.yaml- Service account for the autoscaler - ✅
rbac.yaml- ClusterRole, Role, and bindings with required permissions - ✅
deployment.yaml- Autoscaler deployment with optimized configuration - ✅
service.yaml- Service for metrics exposure - ✅
podmonitor.yaml- Prometheus monitoring integration - ✅
kustomization.yaml- Kustomize configuration - ✅
README.md- Component documentation
ArgoCD Configuration:
- ✅
gitops/apps/cluster-autoscaler.yaml- ArgoCD Application definition - ✅
gitops/argocd/projects/infrastructure.yaml- Updated to include cluster-autoscaler namespace
- ✅
infrastructure/docs/cluster-autoscaler-setup-guide.md- Complete setup and operations guide - ✅
infrastructure/docs/asg-ssm-setup-guide.md- Updated with autoscaler information - ✅
CLUSTER_AUTOSCALER_DEPLOYMENT.md- Step-by-step deployment checklist
- Scale Up: Automatic when pods can't be scheduled (2-3 minutes)
- Scale Down: Automatic when nodes idle for 10+ minutes below 50% utilization
- Anti-thrashing: 10-minute delay after scale-up prevents rapid cycling
- Graceful draining: Respects PodDisruptionBudgets and pod constraints
Min nodes: 1
Max nodes: 8
Scale-down threshold: 50% utilization
Scale-down unneeded time: 10 minutes
Scale-down delay after add: 10 minutes
Max node provision time: 15 minutes
Expander strategy: least-waste- ✅ Monitors pending pods
- ✅ Respects node taints and labels
- ✅ Honors pod affinity/anti-affinity
- ✅ Respects PodDisruptionBudgets
- ✅ Gracefully drains nodes
- ✅ Auto-discovers ASGs via tags
- ✅ Prometheus metrics on port 8085
- ✅ PodMonitor for automatic scraping
- ✅ Health checks (liveness/readiness)
- ✅ Detailed logging (v=4)
- ✅ Runs on control plane node
- ✅ Non-root user (65534)
- ✅ Read-only root filesystem
- ✅ No privilege escalation
- ✅ Dropped capabilities
- ✅ RBAC with minimal required permissions
- ✅ Fully declarative configuration
- ✅ Auto-sync enabled
- ✅ Self-healing enabled
- ✅ Version controlled
- ✅ Easy to modify and rollback
✅ ASG configured with autoscaler tags:
k8s.io/cluster-autoscaler/enabled=truek8s.io/cluster-autoscaler/kubestock=owned
✅ IAM permissions already configured in kubestock-node-role:
- AutoScaling permissions (SetDesiredCapacity, TerminateInstance, Describe*)
- EC2 permissions (DescribeInstances, DescribeInstanceTypes, etc.)
✅ Nodes auto-join via golden AMI and SSM parameters
✅ Token refresh Lambda keeps join-token current
-
Update Terraform (apply ASG max capacity change):
cd infrastructure/terraform/prod terraform plan terraform apply -
Commit and push GitOps changes:
git add gitops/ infrastructure/ git commit -m "Add Kubernetes Cluster Autoscaler" git push -
Deploy via ArgoCD:
kubectl apply -f gitops/apps/cluster-autoscaler.yaml
-
Verify deployment:
kubectl get pods -n cluster-autoscaler kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -f
Scale Up Test:
kubectl create deployment scale-test --image=nginx --replicas=20
kubectl set resources deployment scale-test --requests=cpu=500m,memory=512Mi
watch kubectl get nodesScale Down Test:
kubectl delete deployment scale-test
watch kubectl get nodes # Wait 10+ minutesView Logs:
kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -fView Metrics:
kubectl port-forward -n cluster-autoscaler svc/cluster-autoscaler 8085:8085
curl http://localhost:8085/metricsKey metrics available:
cluster_autoscaler_nodes_count- Current node count by statecluster_autoscaler_unschedulable_pods_count- Pods waiting for resourcescluster_autoscaler_scaled_up_nodes_total- Total scale-up eventscluster_autoscaler_scaled_down_nodes_total- Total scale-down eventscluster_autoscaler_failed_scale_ups_total- Failed scale attemptscluster_autoscaler_last_activity- Timestamp of last scaling action
The autoscaler will:
- ✅ Automatically remove idle nodes after 10 minutes (saves ~85% of idle costs)
- ✅ Use least-waste strategy to minimize over-provisioning
- ✅ Balance nodes across AZs for efficient resource usage
- ✅ Scale based on actual demand, not static schedules
Example savings:
- Without autoscaler: 5 nodes × 24h × $0.05/h = $6/day
- With autoscaler (avg 2-3 nodes): 2.5 nodes × 24h × $0.05/h = $3/day
- Savings: ~50% on compute costs during low-demand periods
| Issue | Check Command |
|---|---|
| Pod not running | kubectl get pods -n cluster-autoscaler |
| View errors | kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler |
| Check ASG | aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names kubestock-workers-asg |
| Node utilization | kubectl top nodes |
| Status ConfigMap | kubectl get cm cluster-autoscaler-status -n cluster-autoscaler -o yaml |
- Setup Guide: infrastructure/docs/cluster-autoscaler-setup-guide.md
- ASG Documentation: infrastructure/docs/asg-ssm-setup-guide.md
- Deployment Checklist: CLUSTER_AUTOSCALER_DEPLOYMENT.md
- Official Docs: https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler
After deployment, you should see:
- ✅ Autoscaler pod running in
cluster-autoscalernamespace - ✅ No errors in logs
- ✅ Status ConfigMap shows cluster discovered
- ✅ Scale-up test adds nodes within 2-3 minutes
- ✅ Scale-down test removes nodes after 10+ minutes
- ✅ Prometheus metrics available at
:8085/metrics - ✅ ArgoCD Application shows "Healthy" and "Synced"
┌──────────────────────────────────────────────────────────────────┐
│ Control Plane Node │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Cluster Autoscaler (Deployment) │ │
│ │ - Monitors: Pending pods, node utilization │ │
│ │ - Decides: When to scale up/down │ │
│ │ - Acts: Modifies ASG desired capacity │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────┬───────────────────┘
│ │
Scale Up │ │ Scale Down
▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ AWS Auto Scaling Group (1-8 nodes) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Worker 1 │ │ Worker 2 │ │ Worker N │ │ (Future) │ │
│ │ (Ready) │ │ (Ready) │ │ (Ready) │ │ (Scaled) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Tags: │
│ - k8s.io/cluster-autoscaler/enabled = true │
│ - k8s.io/cluster-autoscaler/kubestock = owned │
└──────────────────────────────────────────────────────────────────┘
│
│ Golden AMI + SSM Join
▼
Nodes auto-join cluster
After successful deployment:
- Monitor scaling behavior for a few days
- Adjust thresholds if needed based on workload patterns
- Set up Grafana dashboards for visualization
- Configure HPA for pod-level autoscaling (complements Cluster Autoscaler)
- Consider spot instances for cost savings on non-critical workloads
If issues occur:
# Delete ArgoCD Application
kubectl delete application -n argocd cluster-autoscaler
# Delete namespace
kubectl delete namespace cluster-autoscaler
# Revert Terraform (if needed)
cd infrastructure/terraform/prod
# Edit variables.tf to set max_size back to 5
terraform applyImplementation Date: December 14, 2025
Version: 1.0
Autoscaler Version: v1.31.0
Kubernetes Version: v1.31+