Cloud e plataforma

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.

12/03/20268 min de leituraCloud
Kubernetes Cost Optimization: técnicas práticas para reduzir custos em produção

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 peaks

Framework 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: Min

Vertical 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: RequestsAndLimits

HPA + VPA: quando e como combinar

ConfiguraçãoQuando usarTrade-offs
HPA onlyWorkloads horizontais (stateless)Desperdício se pods under-provisioned
VPA onlyWorkloads com perfil estávelNão escala horizontalmente
HPA + VPAWorkloads com padrões variáveisComplexidade 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"
EOF

Node 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 \
  --managed
yaml# 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:latest

Pod 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-worker

Estraté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: 70

Workloads 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-processor

Ferramentas 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
EOF

Kubecost

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=false

Framework de otimização contínua

Checklist mensal de eficiência

  1. Audit de resources:
  • Identificar pods com CPU utilization < 20% por > 24h
  • Identificar pods com memory waste > 50% por > 24h
  • Verificar workloads sem VPA configurado
  1. Audit de autoscaling:
  • Verificar HPA configurations para workloads stateless
  • Confirmar Cluster Autoscaler está ativo e functional
  • Validar node pools tem limits adequados
  1. Audit de node pools:
  • Analisar node utilization por pool
  • Identificar pools sub-utilizados (merge opportunity)
  • Validar spot instances estão sendo usados onde apropriado
  1. 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-buffers

Conclusã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

Leituras relacionadas