writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
devops-kubernetes-autoscalingKubernetes autoscaling strategies and configurations. Covers: Horizontal Pod Autoscaler (HPA) with CPU/memory, custom, and external metrics, Vertical Pod Autoscaler (VPA) update modes and recommender, Keda with 50+ scalers, Cluster Autoscaler node group optimization, combined HPA+VPA+CA strategies, predictive scaling, and cost optimization. Do NOT use for: …
Independent GitHub snapshot · Automated classification · Original documentation governs
Design, implement, and optimize Kubernetes autoscaling strategies using HPA, VPA, Keda, and Cluster Autoscaler to achieve cost-efficient, responsive, and reliable workloads.
Exact user phrases: "HPA", "VPA", "Keda", "Cluster Autoscaler", "autoscaling", "horizontal pod autoscaler", "vertical pod autoscaler", "Keda scaler", "pod scaling", "node scaling", "predictive scaling", "autoscaling strategy".
Before activating, verify:
Writes to YAML manifests for HPA, VPA, ScaledObject, ScaledJob, and ClusterAutoscaler configuration.
YAML manifests with appropriate apiVersion and autoscaling parameters.
This skill is complete when:
Direct file write. No response text.
Deploy metrics-server → Create HPA with CPU target → Add custom metric → Configure VPA in "off" mode for initial recommendations → Deploy Keda for event-driven scale → Configure Cluster Autoscaler node groups → Monitor with K9s or Prometheus.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-custom-metrics
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: 100
- type: Object
object:
metric:
name: requests_total
describedObject:
apiVersion: v1
kind: Service
name: app-service
target:
type: Value
value: 10000
- type: External
external:
metric:
name: sqs_approximate_message_count
selector:
matchLabels:
queue: my-queue
target:
type: AverageValue
averageValue: 5
| Mode | Behavior | Use Case |
|---|---|---|
Off | Only recommends, never applies | Observe recommendations before committing |
Initial | Sets resources only at pod creation | StatefulSets, legacy apps |
Auto | Updates on recreate (evicts pod) | Stateless deployments with restart tolerance |
Recreate | Updates on recreate, similar to Auto | When pod restart triggers reallocation |
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: app-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: app
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: app
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 2000m
memory: 2Gi
controlledResources: ["cpu", "memory"]
- containerName: sidecar
mode: "Off"
kubectl describe vpa app-vpa
# Look for:
# Lower Bound: 250m cpu, 256Mi memory
# Upper Bound: 1500m cpu, 1.5Gi memory
# Target: 500m cpu, 512Mi memory
# Uncapped Target: 450m cpu, 480Mi memory
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer
spec:
scaleTargetRef:
name: consumer-deployment
pollingInterval: 10
cooldownPeriod: 60
minReplicaCount: 1
maxReplicaCount: 20
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 120
triggers:
- type: kafka
metadata:
topic: events
bootstrapServers: kafka-cluster:9092
consumerGroup: consumer-group
lagThreshold: "50"
offsetResetPolicy: latest
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: multi-trigger-worker
spec:
scaleTargetRef:
name: worker
minReplicaCount: 0
maxReplicaCount: 30
triggers:
- type: aws-sqs-queue
authenticationRef:
name: keda-aws-creds
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/12345/my-queue
queueLength: "5"
awsRegion: us-east-1
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
metricName: worker_queue_depth
query: sum(worker_queue_depth{job="worker"})
threshold: "10"
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: batch-job
spec:
jobTargetRef:
template:
spec:
containers:
- name: worker
image: my-worker:latest
restartPolicy: Never
pollingInterval: 30
maxReplicaCount: 20
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 3
triggers:
- type: rabbitmq
metadata:
queueName: tasks
queueLength: "10"
host: amqp://rabbitmq:5672
- type: cron
metadata:
timezone: UTC
start: 30 8 * * *
end: 30 17 * * *
desiredReplicas: "3"
| Feature | Cluster Autoscaler | Karpenter |
|---|---|---|
| Scheduling | Node group based, respects ASG | Instance-type aware, any available capacity |
| Consolidation | Scale-down only (empty nodes) | Consolidates (replaces with cheaper) |
| Node creation | Via ASG, 3-5 min | Direct EC2 API, 60-90s |
| Instance diversity | Fixed instance types | Any compatible type |
| Config | Node group min/max per AZ | Provisioner with requirements |
| Spot handling | Via mixed instances policy | Native via spot capacity type |
# Cluster Autoscaler config
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-autoscaler-config
namespace: kube-system
data:
config: |
balance-similar-node-groups: true
scale-down-delay-after-add: 10m
scale-down-unneeded-time: 10m
scale-down-utilization-threshold: 0.5
skip-nodes-with-local-storage: false
max-node-provision-time: 15m
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: data-worker
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.2xlarge", "m5.4xlarge", "c5.2xlarge"]
nodeClassRef:
name: data-ec2
taints:
- key: workload-type
value: data
effect: NoSchedule
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: data-ec2
spec:
amiFamily: Bottlerocket
subnetSelector:
karpenter.sh/discovery: my-cluster
securityGroupSelector:
karpenter.sh/discovery: my-cluster
role: KarpenterNodeRole
tags:
Environment: production
triggers:
- type: kubernetes-workload
metadata:
podSelector: "app=worker"
value: "10"
- type: cron
metadata:
timezone: UTC
start: 0 9 * * 1-5 # Scale up before business hours
end: 30 18 * * 1-5 # Scale down after
desiredReplicas: "20"
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: prometheus-scaled
spec:
scaleTargetRef:
name: api-server
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
metricName: http_request_duration_seconds_99
query: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[2m])) by (le)
)
threshold: "1.5"
activationThreshold: "0.5"
--balance-similar-node-groups for multi-AZ.kubectl get hpa -w to watch target utilization.kubectl top pods to validate metrics-server is returning data.averageUtilization: 50 as default without workload analysis.| Dimension | HPA | VPA | Keda | Cluster Autoscaler | Karpenter |
|---|---|---|---|---|---|
| Scale target | Replicas | Resource requests | Replicas | Nodes | Nodes |
| Metric types | CPU, memory, custom, external | CPU, memory (actual usage) | 50+ event sources | Pending pod count | Pending pod count |
| Response time | 15-60s | Depends on eviction | 10-30s | 3-15 min | 60-90s |
| Stateful support | No (replicas change) | Yes (Auto mode) | Via VPA integration | Yes | Yes |
| Cost optimization | Indirect | Direct (right-sizing) | Scale-to-zero | Node consolidation | Node consolidation + right-sizing |
| Learning curve | Low | Low-Medium | Medium | Medium | Medium |
| Production readiness | GA | GA (beta stable) | GA | GA | GA |
kubectl describe hpa, verify metrics-server pod is running, check custom metrics API availability.kubectl describe scaledobject, check Keda operator logs, verify authentication with scaler.kubectl logs -n kube-system cluster-autoscaler, check IAM permissions, verify ASG max size.kubectl logs -n karpenter karpenter, check EC2NodeClass subnet/security group selectors.After completing this skill:
| Decision | HPA (Horizontal) | VPA (Vertical) | KEDA (Event-driven) |
|---|---|---|---|
| Scaling dimension | Replica count | CPU/Memory requests | Replica count |
| Metric source | CPU, memory, custom metrics | CPU, memory | 50+ event sources (Kafka, Prometheus, SQS) |
| Response time | ~30-60s | ~60-180s (restart required) | ~5-15s |
| Use case | Web apps, stateless | Stateful, batch | Queue-based, event-driven |
| Coordination | Works with CA | Conflicts with HPA | Works with CA + HPA |
| Recommendation | Default for most workloads | Long-lived pods with variable needs | Serverless-style, async workloads |
| Aspect | Cluster Autoscaler | Karpenter |
|---|---|---|
| Provisioning | Node group → ASG → instance | Direct instance from cloud API |
| Scale-up speed | Minutes (ASG warm-up) | Seconds (direct launch) |
| Scheduling | Pending pod → node group match | Pending pod → optimal instance type |
| Consolidation | Slow (scale-down cooldown) | Aggressive (continuous consolidation) |
| Instance diversity | Limited to ASG config | Wide (any instance family/size) |
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: 500
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
selectPolicy: Max
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: worker-scaler
spec:
scaleTargetRef:
name: worker
minReplicaCount: 1
maxReplicaCount: 50
triggers:
- type: rabbitmq
metadata:
protocol: amqp
queueName: tasks
mode: QueueLength
value: "10"
activationValue: "1"
authenticationRef:
name: rabbitmq-auth
fallback:
failureThreshold: 5
replicas: 5
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 60
#!/usr/bin/env bash
simulate_scale_up() {
local deployment=$1
local replicas=$2
kubectl scale deployment "$deployment" --replicas="$replicas"
# Watch pending pods
while true; do
PENDING=$(kubectl get pods -l app="$deployment" --field-selector status.phase=Pending -o json | jq '.items | length')
RUNNING=$(kubectl get pods -l app="$deployment" --field-selector status.phase=Running -o json | jq '.items | length')
echo "Pending: $PENDING, Running: $RUNNING"
if [ "$PENDING" -eq 0 ] && [ "$RUNNING" -ge "$replicas" ]; then
echo "Scale-up complete"
break
fi
sleep 5
done
}
--scale-down-unneeded-time to 10m for prod, 5m for devspot-to-price diversity--horizontal-pod-autoscaler-sync-period) to 10s for faster reactionkubernetes-image-puller to speed up scale-up--kube-reserved and --system-reserved to give autoscaler accurate capacity info` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).