Kubernetes Cost Optimization: técnicas práticas para reduzir custos em produção
Otimização de custos em Kubernetes envolve right-sizing de recursos, autoscaling inteligente, node pools estratégicos e monitoramento contínuo de eficiência de infraestrutura.
Resumo executivo
Otimização de custos em Kubernetes envolve right-sizing de recursos, autoscaling inteligente, node pools estratégicos e monitoramento contínuo de eficiência de infraestrutura.
Ultima atualizacao: 12/03/2026
O problema de custos não otimizados
Kubernetes oferece poder de orquestração sem precedentes, mas essa flexibilidade vem com um risco: custos ocultos que crescem exponencialmente quando recursos não são gerenciados proativamente. Em clusters empresariais, é comum encontrar até 40% de desperdício computacional devido a over-provisioning, pods sub-utilizados e node pools desbalanceados.
O desafio não é apenas "reduzir custos" — é reduzir custos sem sacrificar disponibilidade, performance ou capacidade de escala. A otimização efetiva requer abordagem sistemática que combina right-sizing de recursos, autoscaling inteligente e monitoramento contínuo.
Right-sizing: o fundamento da otimização
Request vs Limit: a armadilha do over-provisioning
A configuração correta de requests e limits é o primeiro passo para otimização, mas é também onde mais erros ocorrem.
yaml# ERRADO: over-provisioning típico
apiVersion: v1
kind: Pod
metadata:
name: app-server
spec:
containers:
- name: server
image: myapp:latest
resources:
requests:
cpu: "2000m" # 2 cores para workload que usa 200m
memory: "4Gi" # 4GB para workload que usa 512MB
limits:
cpu: "4000m"
memory: "8Gi"yaml# CORRETO: right-sizing baseado em métricas reais
apiVersion: v1
kind: Pod
metadata:
name: app-server
spec:
containers:
- name: server
image: myapp:latest
resources:
requests:
cpu: "250m" # Margem de 25% sobre baseline
memory: "650Mi" # Margem de 25% + overhead
limits:
cpu: "500m" # 2x request para burst allowance
memory: "1Gi" # 1.5x request para peaksFramework de right-sizing
O processo de right-sizing deve seguir abordagem sistemática:
bash# 1. Coletar métricas baseline por workload
kubectl top pods -n production -l app=api-server --use-protocol-buffers | \
awk '{sum+=$2; count++} END {print "Avg CPU:", sum/count "m"}'
kubectl top pods -n production -l app=api-server | \
awk '{sum+=$3; count++} END {print "Avg Memory:", sum/count "Mi"}'
# 2. Identificar padrões de uso temporal
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/production/pods | \
jq '.items[] | select(.metadata.labels.app=="api-server") | .containers[] | \
{name: .name, cpu: .usage.cpu, memory: .usage.memory}'
# 3. Calcular P95 com ferramentas como Prometheus
# Query example: quantile_over_time(0.95, rate(container_cpu_usage_seconds_total[5m])[24h:])Regra prática de right-sizing:
- CPU Request = P70 usage + 25% buffer
- CPU Limit = Request × 2 (burst allowance)
- Memory Request = P90 usage + 30% buffer
- Memory Limit = Request × 1.5 (prevent OOM)
Autoscaling inteligente: HPA e VPA
Horizontal Pod Autoscaler (HPA)
HPA escala horizontalmente baseado em métricas de utilização ou customizadas.
yamlapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 20
metrics:
# Métrica de CPU (padrão)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# Métrica de memória
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# Métrica customizada (ex: requests per second)
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "1000"
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 300
selectPolicy: MinVertical Pod Autoscaler (VPA)
VPA ajusta automaticamente requests e limits baseados em histórico de utilização.
yamlapiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto" # Off, Initial, Recreate, Auto
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: "100m"
memory: "256Mi"
maxAllowed:
cpu: "2000m"
memory: "4Gi"
controlledResources: ["cpu", "memory"]
controlledValues: RequestsAndLimitsHPA + VPA: quando e como combinar
| Configuração | Quando usar | Trade-offs |
|---|---|---|
| HPA only | Workloads horizontais (stateless) | Desperdício se pods under-provisioned |
| VPA only | Workloads com perfil estável | Não escala horizontalmente |
| HPA + VPA | Workloads com padrões variáveis | Complexidade maior, VPA pode conflitar com HPA |
yaml# Combinação HPA + VPA com controle fino
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
spec:
updatePolicy:
updateMode: "Off" # VPA só recomenda, HPA decide escala
recommenders:
- name: k8s.io/vpa-recommender
- name: k8s.io/vpa-empty-recommender
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
spec:
metrics:
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "1000"Node Pools e Node Autoscaling
Cluster Autoscaler
Cluster Autoscaler ajusta número de nós baseado em pods pending.
bash# Configurar Cluster Autoscaler (Cloud Provider específico)
# AWS EKS example:
eksctl utils associate-iam-oidc-provider \
--region us-east-1 \
--cluster production-cluster
eksctl create iamserviceaccount \
--cluster production-cluster \
--namespace kube-system \
--name cluster-autoscaler \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterAutoscalerPolicy \
--approve \
--override-existing-serviceaccounts
# Deploy com configuração otimizada
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: cluster-autoscaler
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: cluster-autoscaler
template:
metadata:
labels:
app: cluster-autoscaler
spec:
serviceAccountName: cluster-autoscaler
containers:
- image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.28.1
name: cluster-autoscaler
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=aws
- --skip-nodes-with-system-pods=false
- --balance-similar-node-groups=true
- --expander=least-waste
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/production-cluster
env:
- name: AWS_REGION
value: us-east-1
resources:
limits:
cpu: "100m"
memory: "300Mi"
requests:
cpu: "100m"
memory: "300Mi"
EOFNode Pools estratificados
Estratégia de pools baseada em workload permite otimização de custos granular.
bash# AWS EKS node groups otimizados
# 1. On-demand pool para workloads críticos
eksctl create nodegroup \
--cluster production-cluster \
--region us-east-1 \
--name critical-workloads \
--node-type c6i.xlarge \
--nodes 3 \
--nodes-min 2 \
--nodes-max 5 \
--managed \
--asg-access \
--external-dns-access
# 2. Spot pool para workloads interruptíveis
eksctl create nodegroup \
--cluster production-cluster \
--region us-east-1 \
--name spot-workers \
--node-type c6i.2xlarge \
--nodes 10 \
--nodes-min 5 \
--nodes-max 20 \
--managed \
--spot \
--spot-max-price "0.5" \
--instance-selector "vCPUs>=4,Memory>=8Gi"
# 3. Arm pool para workloads memory-intensive
eksctl create nodegroup \
--cluster production-cluster \
--region us-east-1 \
--name memory-workers \
--node-type r6g.xlarge \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4 \
--managedyaml# Taints e tolerations para workloads específicos
apiVersion: v1
kind: Pod
metadata:
name: batch-processor
namespace: batch-jobs
spec:
tolerations:
- key: "workload-type"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
containers:
- name: processor
image: batch-processor:latest
resources:
requests:
cpu: "1000m"
memory: "4Gi"
---
apiVersion: v1
kind: Pod
metadata:
name: api-server
namespace: production
spec:
nodeSelector:
workload-type: "critical"
containers:
- name: server
image: api-server:latestPod Disruption Budgets e eficiência
PDBs garantem disponibilidade durante upgrades e scaling sem over-provisioning.
yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
minAvailable: 2 # Mínimo de pods disponíveis
selector:
matchLabels:
app: api-server
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: batch-worker-pdb
namespace: batch-jobs
spec:
maxUnavailable: 2 # Máximo de pods indisponíveis
selector:
matchLabels:
app: batch-workerEstratégia de PDB otimizada:
- Críticos:
minAvailable: N-1(onde N é número de replicas) - Batch:
maxUnavailable: 50% - Stateful:
minAvailable: quorum(N/2 + 1)
Monitoramento de eficiência de custos
Métricas-chave para FinOps
promql# 1. CPU utilization por namespace
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (pod) /
sum(kube_pod_container_resource_requests{resource="cpu", namespace="production"}) by (pod)
# 2. Memory waste (requested vs used)
(sum(kube_pod_container_resource_requests{resource="memory", namespace="production"}) by (pod) -
sum(container_memory_working_set_bytes{namespace="production"}) by (pod)) /
sum(kube_pod_container_resource_requests{resource="memory", namespace="production"}) by (pod)
# 3. Node efficiency (utilização vs capacidade)
sum(rate(container_cpu_usage_seconds_total{node!=""}[5m])) by (node) /
sum(kube_node_status_capacity{resource="cpu"}) by (node)
# 4. Cost per request (custom business metric)
rate(cost_per_request_total[5m])Dashboards e alertas
yaml# PrometheusRule para alertas de ineficiência
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cost-efficiency-alerts
namespace: monitoring
spec:
groups:
- name: cost-optimization
rules:
- alert: LowCPUEfficiency
expr: |
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[1h])) by (pod) /
sum(kube_pod_container_resource_requests{resource="cpu", namespace="production"}) by (pod) < 0.2
for: 2h
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} has low CPU efficiency (<20%)"
description: "Consider right-sizing CPU requests"
- alert: HighMemoryWaste
expr: |
(sum(kube_pod_container_resource_requests{resource="memory", namespace="production"}) by (pod) -
sum(container_memory_working_set_bytes{namespace="production"}) by (pod)) /
sum(kube_pod_container_resource_requests{resource="memory", namespace="production"}) by (pod) > 0.6
for: 2h
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} has high memory waste (>60%)"
- alert: OverProvisionedReplicas
expr: |
sum(rate(container_cpu_usage_seconds_total[5m])) by (deployment) /
(sum(kube_pod_container_resource_requests{resource="cpu"}) by (deployment) * kube_deployment_spec_replicas) < 0.3
for: 4h
labels:
severity: info
annotations:
summary: "Deployment {{ $labels.deployment }} may be over-provisioned"Estratégias específicas por tipo de workload
Workloads stateless (API servers, web apps)
yaml# Otimizações para stateless workloads
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3 # Baseline mínimo
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api-server
topologyKey: kubernetes.io/hostname
containers:
- name: server
image: api-server:latest
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: FallbackToLogsOnError
terminationGracePeriodSeconds: 30
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
spec:
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Workloads batch (jobs, ETL)
yaml# Otimizações para batch workloads
apiVersion: batch/v1
kind: Job
metadata:
name: data-processor
namespace: batch-jobs
spec:
parallelism: 10
completions: 10
backoffLimit: 3
template:
spec:
tolerations:
- key: "workload-type"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: instance-type
operator: In
values:
- spot
containers:
- name: processor
image: batch-processor:latest
resources:
requests:
cpu: "2000m"
memory: "4Gi"
limits:
cpu: "4000m"
memory: "8Gi"
env:
- name: PARALLELISM
value: "10"
restartPolicy: OnFailure
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: batch-worker-pdb
spec:
maxUnavailable: 50%
selector:
matchLabels:
job-name: data-processorFerramentas de otimização de custos
OpenCost
yaml# Instalação do OpenCost para tracking de custos
kubectl apply -f https://raw.githubusercontent.com/opencost/opencost/develop/kubernetes/opencost.yaml
# Configuração para cloud provider específico
apiVersion: v1
kind: ConfigMap
metadata:
name: opencost
namespace: opencost
data:
opencost.yaml: |
clusterName: production-cluster
prometheus:
internal:
enabled: true
cloudProvider: aws
currencyCode: USD
EOFKubecost
bash# Instalação do Kubecost
kubectl create namespace kubecost
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--set kubecostToken="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" \
--set serviceAccount.create=true \
--set serviceAccount.name=kubecost-service-account \
--set global.prometheus.fqdn=http://prometheus-server.monitoring.svc.cluster.local \
--set global.prometheus.enabled=falseFramework de otimização contínua
Checklist mensal de eficiência
- Audit de resources:
- Identificar pods com CPU utilization < 20% por > 24h
- Identificar pods com memory waste > 50% por > 24h
- Verificar workloads sem VPA configurado
- Audit de autoscaling:
- Verificar HPA configurations para workloads stateless
- Confirmar Cluster Autoscaler está ativo e functional
- Validar node pools tem limits adequados
- Audit de node pools:
- Analisar node utilization por pool
- Identificar pools sub-utilizados (merge opportunity)
- Validar spot instances estão sendo usados onde apropriado
- Cost analysis:
- Comparar custo month-over-month por namespace
- Identificar anomalias de custo
- Correlacionar custo com business metrics
bash# Script de audit automatizado
#!/bin/bash
NAMESPACE=${1:-production}
echo "=== Kubernetes Cost Optimization Audit ==="
echo "Namespace: $NAMESPACE"
echo ""
# 1. Low CPU efficiency pods
echo "1. Pods com baixa eficiência de CPU (<20%):"
kubectl get pods -n $NAMESPACE -o json | \
jq -r '.items[] | select(.spec.containers[].resources.requests.cpu) |
"\(.metadata.name): \(.spec.containers[].resources.requests.cpu // "N/A")"'
# 2. High memory waste
echo ""
echo "2. Pods com alto desperdício de memória (>50%):"
kubectl top pods -n $NAMESPACE --use-protocol-buffers | \
awk '$3 ~ /Mi/ {print $1, $3}'
# 3. Workloads without HPA
echo ""
echo "3. Deployments sem HPA:"
kubectl get hpa -n $NAMESPACE -o json | \
jq -r '.items[].spec.scaleTargetRef.name' | \
sort | uniq > /tmp/hpa_deployments.txt
kubectl get deploy -n $NAMESPACE -o json | \
jq -r '.items[].metadata.name' | \
sort | uniq > /tmp/all_deployments.txt
comm -13 /tmp/hpa_deployments.txt /tmp/all_deployments.txt
# 4. Node utilization
echo ""
echo "4. Utilização de nós:"
kubectl top nodes --use-protocol-buffersConclusão
Otimização de custos em Kubernetes não é atividade única — é disciplina contínua que combina right-sizing de recursos, autoscaling inteligente, node pools estratégicos e monitoramento de eficiência. Organizações que tratam otimização de custos como processo sistemático, não como reação a contas de nuvem surpreendentes, alcançam reduções de 30-50% sem sacrificar disponibilidade ou performance.
Seu cluster Kubernetes está com custos descontrolados e você precisa de uma estratégia de otimização comprovada? Fale com especialistas em cloud da Imperialis para implementar uma framework de FinOps Kubernetes que reduza custos enquanto mantenha disponibilidade e performance.
Fontes
- Kubernetes Cluster Autoscaler documentation — accessed on 2026-03
- Vertical Pod Autoscaler documentation — accessed on 2026-03
- OpenCost documentation — accessed on 2026-03
- Kubernetes resource management best practices — accessed on 2026-03