<h2>FinOps em Kubernetes: Uma Introdução Prática</h2>
<p>FinOps é a disciplina que une finanças, operações e engenharia para otimizar os gastos em infraestrutura cloud. No contexto de Kubernetes, essa prática se torna crítica porque clusters podem consumir recursos de forma ineficiente rapidamente, gerando contas inesperadas. A maioria das organizações descobre que está gastando 30-40% a mais do que deveria apenas porque não há visibilidade sobre o que cada aplicação consome.</p>
<p>O desafio fundamental do FinOps em Kubernetes é que você tem múltiplos layers de abstração: nodes, pods, containers, volumes, ingresses, e cada um tem um custo associado. Sem as ferramentas certas, é impossível saber se um pod está desperdiçando CPU ou se um node rodando com 5% de utilização deveria ser desligado. É por isso que ferramentas como Kubecost existem — para trazer luz a essa caixa preta. Neste artigo, vamos explorar como implementar FinOps de verdade, partindo de uma visão clara dos custos até a otimização prática com right-sizing e spot instances.</p>
<h2>Kubecost: Visibilidade Total de Custos</h2>
<h3>O que é Kubecost e por que usar</h3>
<p>Kubecost é uma plataforma open-source que oferece visibilidade em tempo real dos custos de Kubernetes. Ela integra-se nativamente com seu cluster e coleta métricas de consumo de recursos, relaciona com preços de cloud providers (AWS, GCP, Azure) e te dá um dashboard completo. A beleza do Kubecost é que ele não é invasivo — roda como um pod como qualquer outro, mas tem acesso às métricas do Prometheus e aos dados de billing da sua cloud.</p>
<p>A ferramenta te responde perguntas essenciais: quanto custa rodar meu namespace <code>production</code>? Qual pod está consumindo mais CPU? Qual deployment seria mais barato se migrássemos para spot instances? Se você não tem respostas claras para essas perguntas, está operando Kubernetes às cegas.</p>
<h3>Instalando Kubecost em seu cluster</h3>
<p>Para instalar Kubecost, você vai usar Helm, que é o gerenciador de pacotes do Kubernetes. Vou assumir que você já tem um cluster funcional com Prometheus rodando (Kubecost precisa disso).</p>
<pre><code class="language-bash"># Adicionar o repositório Helm do Kubecost
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm repo update
Instalar Kubecost no namespace kubecost-system
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost-system \
--create-namespace \
--set kubecostModel.warmCache=true \
--set kubecostModel.warmSavingsCache=true \
--set prometheus.server.global.external_labels.cluster_id=production-cluster</code></pre>
<p>Após a instalação, você pode acessar o dashboard do Kubecost via port-forward:</p>
<pre><code class="language-bash">kubectl port-forward -n kubecost-system svc/kubecost-cost-analyzer 9090:9090
Acesse http://localhost:9090 no seu navegador</code></pre>
<p>No dashboard, você verá imediatamente seus gastos agregados, custos por namespace, custos por label e muito mais. Mas conhecer os custos é apenas o primeiro passo — agora você precisa agir.</p>
<h3>Usando a API do Kubecost para relatórios customizados</h3>
<p>Além do dashboard, Kubecost expõe uma API REST que você pode usar para extrair dados programaticamente. Isso é particularmente útil se você quer integrar relatórios de custo em seu pipeline de CI/CD ou em ferramentas internas.</p>
<pre><code class="language-python">import requests
import json
from datetime import datetime, timedelta
Função para buscar custos de um namespace específico
def obter_custo_namespace(namespace, dias=7):
kubecost_url = "http://kubecost-cost-analyzer.kubecost-system.svc.cluster.local:9090"
agora = datetime.utcnow()
inicio = agora - timedelta(days=dias)
Formatar datas no padrão Unix timestamp
inicio_ts = int(inicio.timestamp())
fim_ts = int(agora.timestamp())
endpoint = f"{kubecost_url}/model/allocation"
params = {
"window": f"{inicio_ts}s,{fim_ts}s",
"aggregate": "namespace",
"namespace": namespace
}
response = requests.get(endpoint, params=params)
dados = response.json()
if "data" in dados and len(dados["data"]) > 0:
custo_total = dados["data"][0].get("totalCost", 0)
return {
"namespace": namespace,
"custo_total_usd": round(float(custo_total), 2),
"periodo_dias": dias
}
return {"erro": "Nenhum dado encontrado"}
Exemplo de uso
resultado = obter_custo_namespace("production", dias=30)
print(json.dumps(resultado, indent=2))
Output esperado:
{
"namespace": "production",
"custo_total_usd": 1245.67,
"periodo_dias": 30
}</code></pre>
<p>Este script conecta à API do Kubecost e extrai o custo de um namespace específico nos últimos 7 dias. Você pode expandir isso para monitorar custos crescentes e disparar alertas — por exemplo, se um namespace custar mais de 50% a mais que a média histórica, algo anormal está acontecendo.</p>
<h2>Right-sizing: O Alicerce da Otimização</h2>
<h3>Entendendo requests e limits</h3>
<p>Right-sizing é a prática de configurar requests e limits de CPU e memória nas suas aplicações de forma realista. Muitos engenheiros ou deixam esses valores vazios (o que faz o Kubernetes não conseguir fazer scheduling correto) ou definem valores absurdamente altos "por segurança" (o que desperdiça recursos e dinheiro).</p>
<p>Um <code>request</code> é a quantidade de recurso que o Kubernetes reserva para o pod — é uma garantia. Um <code>limit</code> é o máximo que o container pode usar — ultrapassar isso resulta em throttling ou kill do container. A diferença entre o que você requisita e o que realmente usa é desperdício puro.</p>
<p>Vamos à verdade: a maioria das aplicações usa 10-30% do que foi alocado para elas. Um microserviço que requisita 2 CPUs provavelmente usa 200m (0,2 CPUs) durante operação normal. Isso significa que você está pagando por 1.8 CPUs de desperdício.</p>
<h3>Coletando dados reais de uso</h3>
<p>Antes de fazer qualquer mudança, você precisa coletar dados reais. O Prometheus já está armazenando essas métricas no seu cluster. O Kubecost usa essas mesmas métricas, mas você também pode consultá-las diretamente.</p>
<pre><code class="language-bash"># Port-forward para o Prometheus
kubectl port-forward -n prometheus svc/prometheus 9091:9090
Agora acesse http://localhost:9091 e execute esta query para encontrar
o uso máximo de CPU por pod em uma janela de 7 dias
max(max_over_time(rate(container_cpu_usage_seconds_total{pod!=""}[5m])[7d:5m])) by (pod, namespace)</code></pre>
<p>Essa query retorna o pico de uso de CPU que cada pod atingiu nos últimos 7 dias. Se um pod tem um request de 2000m (2 CPUs) mas o pico observado foi 400m, você acabou de identificar um desperdício de 75%.</p>
<p>Para uma análise mais robusta e visível no Kubecost, você pode usar este dashboard/query para percentis de uso:</p>
<pre><code class="language-promql"># 95o percentil de uso de CPU por pod (recomendado para right-sizing)
quantile(0.95, max_over_time(rate(container_cpu_usage_seconds_total{pod!=""}[5m])[30d:5m])) by (pod, namespace)
95o percentil de uso de memória por pod
quantile(0.95, max_over_time(container_memory_working_set_bytes{pod!=""}[30d:5m])) by (pod, namespace)</code></pre>
<p>O 95º percentil é a métrica certa para usar aqui — significa que 95% do tempo o pod usa essa quantidade ou menos. Baseando seus requests no 95º percentil, você garante que terá headroom para picos ocasionais sem desperdiçar recursos na maioria do tempo.</p>
<h3>Implementando right-sizing em produção</h3>
<p>Vamos a um exemplo prático. Suponha que você tem um deployment de API chamado <code>user-service</code> e descobriu via Prometheus que:</p>
<ul>
<li>95º percentil de CPU: 300m</li>
<li>95º percentil de memória: 512Mi</li>
<li>Atual request de CPU: 1000m</li>
<li>Atual request de memória: 1Gi</li>
</ul>
<pre><code class="language-yaml"># Antes (desperdiçando recursos)
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry.azurecr.io/user-service:v1.2.0
resources:
requests:
cpu: "1000m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "2Gi"</code></pre>
<p>Agora vamos otimizar baseado em dados reais:</p>
<pre><code class="language-yaml"># Depois (right-sized)
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry.azurecr.io/user-service:v1.2.0
resources:
requests:
cpu: "350m" # 95º percentil + 50m de buffer
memory: "600Mi" # 95º percentil + 88Mi de buffer
limits:
cpu: "800m" # 2x o request para permitir picos curtos
memory: "1Gi" # Limite conservador</code></pre>
<p>Essa mudança simples reduz o custo de CPU desse deployment em ~65% e o de memória em ~40%. Multiplicado por múltiplos deployments, o impacto financeiro é significativo. Mas atenção: sempre faça essas mudanças gradualmente em produção e monitore métricas de erro e latência após as alterações.</p>
<h2>Spot Instances: Otimização Agressiva para Workloads Toleráveis</h2>
<h3>O que são spot instances e quando usá-las</h3>
<p>Spot instances (ou preemptible VMs no GCP, ou EC2 Spot no AWS) são máquinas virtuais que a cloud vende a um desconto de até 70-90% em troca de poderem ser desligadas com poucas horas (ou minutos) de aviso. Para cargas de trabalho que podem tolerar interrupções — como jobs em batch, processamento de dados, testes, ambientes de desenvolvimento — spot instances são ouro puro financeiramente.</p>
<p>A chave é identificar quais workloads são tolerantes a interrupções. Um banco de dados que perde toda sua memória quando o pod é desligado não é tolerante. Um job de ETL que pode ser reiniciado sem perder dados é perfeitamente tolerante. A maioria das aplicações web também é tolerante — quando um pod morre, o Kubernetes inicia outro em outro node.</p>
<h3>Configurando Kubernetes para Spot Instances com Karpenter</h3>
<p>Gerenciar spot instances manualmente é uma dor de cabeça. O Karpenter é um projeto open-source que automatiza o provisionamento e desprovisionamento inteligente de nodes. Ele entende o custo de diferentes tipos de instância e prioriza spot instances automaticamente.</p>
<pre><code class="language-bash"># Instalar Karpenter no cluster (assumindo AWS)
helm repo add karpenter https://charts.karpenter.sh
helm repo update
helm install karpenter karpenter/karpenter \
--namespace karpenter \
--create-namespace \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::ACCOUNT_ID:role/karpenter \
--set settings.clusterName=my-cluster \
--set settings.interruptionQueue=karpenter-interruption-queue</code></pre>
<p>Agora vamos criar um <code>Provisioner</code> do Karpenter que prefere spot instances mas pode cair back para on-demand se necessário:</p>
<pre><code class="language-yaml">apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
Prioridade: spot instances em primeiro lugar, depois on-demand
providerRef:
name: default
Limpar nodes ociosos após 30 segundos
ttlSecondsAfterEmpty: 30
Limpar nodes após 30 dias para forçar renovação e puxar imagens atualizadas
ttlSecondsUntilExpired: 2592000
consolidation:
enabled: true
Consolidar nodes a cada 30 segundos
ttlSecondsAfterLastProvisioned: 30
limits:
Limite de CPU total que esse provisioner pode alocar
resources:
cpu: 1000
memory: 1000Gi
Requerimentos de capacity type
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["t3.medium", "t3.large", "t3a.medium", "t3a.large", "m5.large", "m5.xlarge"]
- key: "karpenter.sh/do-not-evict"
operator: DoesNotExist
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: EC2NodeClass
metadata:
name: default
spec:
subnetSelector:
karpenter.sh/discovered: "true"
securityGroupSelector:
karpenter.sh/discovered: "true"
tags:
ManagedBy: "Karpenter"</code></pre>
<p>Com essa configuração, o Karpenter tentará sempre provisionar nodes de spot instances, economizando significativamente. Quando o Karpenter detecta que uma spot instance vai ser interrompida (AWS envia um aviso), ele drena o node gracefully, migrando os pods para outras máquinas.</p>
<h3>Taints e tolerations para workloads spot-only</h3>
<p>Para certos workloads que você quer que rodem exclusivamente em spot instances (porque tem tolerância máxima), você pode usar taints e tolerations:</p>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: CronJob
metadata:
name: batch-processor
namespace: processing
spec:
schedule: "0 2 *" # Roda todo dia às 2 da manhã
jobTemplate:
spec:
template:
spec:
Tolera apenas nodes com spot instances
tolerations:
- key: karpenter.sh/capacity-type
operator: Equal
value: spot
effect: NoSchedule
Prefere nodes de spot (soft requirement)
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
containers:
- name: processor
image: myregistry.azurecr.io/batch-processor:latest
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "2Gi"
restartPolicy: OnFailure</code></pre>
<p>Esse CronJob rodará um job que processa dados em batch. Como é um job que pode ser reiniciado sem problemas se a instância for interrompida, ele fica isolado em nodes de spot, economizando até 80% em relação ao custo on-demand.</p>
<h3>Monitorando economia com Spot Instances</h3>
<p>Para medir o impacto financeiro real de usar spot instances, você pode usar uma query do Kubecost combinada com a API:</p>
<pre><code class="language-python">import requests
import json
from datetime import datetime, timedelta
def comparar_custo_spot_vs_ondemand():
kubecost_url = "http://kubecost-cost-analyzer.kubecost-system.svc.cluster.local:9090"
agora = datetime.utcnow()
inicio = agora - timedelta(days=30)
inicio_ts = int(inicio.timestamp())
fim_ts = int(agora.timestamp())
Custos reais (o que você pagou)
params_reais = {
"window": f"{inicio_ts}s,{fim_ts}s",
"aggregate": "node"
}
response_reais = requests.get(f"{kubecost_url}/model/allocation", params=params_reais)
dados_reais = response_reais.json()
Parsear dados para encontrar custos de spot vs on-demand
custo_spot = 0
custo_ondemand = 0
if "data" in dados_reais:
for node_data in dados_reais["data"]:
node_name = list(node_data.keys())[0]
custo = float(node_data[node_name].get("totalCost", 0))
Isso é uma simplificação - no mundo real, você consultaria tags do node
if "spot" in node_name.lower():
custo_spot += custo
else:
custo_ondemand += custo
Estimar custo se tudo fosse on-demand (multiplicar spot por ~4x)
custo_spot_se_ondemand = custo_spot * 4
economia = custo_spot_se_ondemand - custo_spot
return {
"custo_spot_real_usd": round(custo_spot, 2),
"custo_ondemand_real_usd": round(custo_ondemand, 2),
"economia_estimada_spot_usd": round(economia, 2),
"percentual_economia": round((economia / (economia + custo_ondemand)) * 100, 1) if economia > 0 else 0
}
resultado = comparar_custo_spot_vs_ondemand()
print(json.dumps(resultado, indent=2))
Output esperado:
{
"custo_spot_real_usd": 245.33,
"custo_ondemand_real_usd": 1823.45,
"economia_estimada_spot_usd": 730.99,
"percentual_economia": 28.6
}</code></pre>
<h2>Conclusão</h2>
<p>Dominar FinOps em Kubernetes envolve três pilares essenciais que aprendemos aqui. <strong>Primeiro</strong>, visibilidade absoluta através do Kubecost — você não otimiza o que não mede. Ter um dashboard mostrando exatamente onde vai cada dólar gasto é o ponto de partida inegociável para qualquer organização séria com Kubernetes. <strong>Segundo</strong>, right-sizing disciplinado baseado em dados reais, não em palpites de engenheiros — usar Prometheus para extrair percentis de consumo e ajustar requests e limits consequentemente reduz custos em 40-70% sem impactar performance. <strong>Terceiro</strong>, aproveitamento agressivo de spot instances para workloads tolerantes através de ferramentas como Karpenter — esse é o multiplicador final que pode levar sua economia a 80% em comparação com on-demand puro. A combinação dos três — visibilidade clara, alocação realista de recursos e aproveitamento de instâncias baratas — transforma FinOps de uma prática abstrata em um programa concreto que adiciona milhares de dólares em valor ao seu negócio a cada mês.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.kubecost.com" target="_blank" rel="noopener noreferrer">Kubecost Documentation - Official</a></li>
<li><a href="https://karpenter.sh" target="_blank" rel="noopener noreferrer">Karpenter - Kubernetes Autoscaling</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/basics/" target="_blank" rel="noopener noreferrer">Prometheus Queries for Kubernetes Monitoring</a></li>
<li><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html" target="_blank" rel="noopener noreferrer">AWS EC2 Spot Instances - Best Practices</a></li>
<li><a href="https://www.amazon.com/Kubernetes-Book-Nigel-Poulton/dp/1521823634" target="_blank" rel="noopener noreferrer">The Kubernetes Book - Nigel Poulton (Capítulo sobre Resource Management)</a></li>
</ul>
<p><!-- FIM --></p>