Docker & Kubernetes

Como Usar FinOps em Kubernetes: Kubecost, Right-sizing e Spot Instances em Produção

17 min de leitura

Como Usar FinOps em Kubernetes: Kubecost, Right-sizing e Spot Instances em Produção

FinOps em Kubernetes: Uma Introdução Prática 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. 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

<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 = &quot;http://kubecost-cost-analyzer.kubecost-system.svc.cluster.local:9090&quot;

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&quot;{kubecost_url}/model/allocation&quot;

params = {

&quot;window&quot;: f&quot;{inicio_ts}s,{fim_ts}s&quot;,

&quot;aggregate&quot;: &quot;namespace&quot;,

&quot;namespace&quot;: namespace

}

response = requests.get(endpoint, params=params)

dados = response.json()

if &quot;data&quot; in dados and len(dados[&quot;data&quot;]) &gt; 0:

custo_total = dados[&quot;data&quot;][0].get(&quot;totalCost&quot;, 0)

return {

&quot;namespace&quot;: namespace,

&quot;custo_total_usd&quot;: round(float(custo_total), 2),

&quot;periodo_dias&quot;: dias

}

return {&quot;erro&quot;: &quot;Nenhum dado encontrado&quot;}

Exemplo de uso

resultado = obter_custo_namespace(&quot;production&quot;, dias=30)

print(json.dumps(resultado, indent=2))

Output esperado:

{

&quot;namespace&quot;: &quot;production&quot;,

&quot;custo_total_usd&quot;: 1245.67,

&quot;periodo_dias&quot;: 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 &quot;por segurança&quot; (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!=&quot;&quot;}[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!=&quot;&quot;}[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!=&quot;&quot;}[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: &quot;1000m&quot;

memory: &quot;1Gi&quot;

limits:

cpu: &quot;2000m&quot;

memory: &quot;2Gi&quot;</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: &quot;350m&quot; # 95º percentil + 50m de buffer

memory: &quot;600Mi&quot; # 95º percentil + 88Mi de buffer

limits:

cpu: &quot;800m&quot; # 2x o request para permitir picos curtos

memory: &quot;1Gi&quot; # 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.&quot;eks\.amazonaws\.com/role-arn&quot;=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: &quot;karpenter.sh/capacity-type&quot;

operator: In

values: [&quot;spot&quot;, &quot;on-demand&quot;]

  • key: &quot;kubernetes.io/arch&quot;

operator: In

values: [&quot;amd64&quot;]

  • key: &quot;node.kubernetes.io/instance-type&quot;

operator: In

values: [&quot;t3.medium&quot;, &quot;t3.large&quot;, &quot;t3a.medium&quot;, &quot;t3a.large&quot;, &quot;m5.large&quot;, &quot;m5.xlarge&quot;]

  • key: &quot;karpenter.sh/do-not-evict&quot;

operator: DoesNotExist

---

apiVersion: karpenter.k8s.aws/v1alpha1

kind: EC2NodeClass

metadata:

name: default

spec:

subnetSelector:

karpenter.sh/discovered: &quot;true&quot;

securityGroupSelector:

karpenter.sh/discovered: &quot;true&quot;

tags:

ManagedBy: &quot;Karpenter&quot;</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: &quot;0 2 *&quot; # 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: &quot;500m&quot;

memory: &quot;512Mi&quot;

limits:

cpu: &quot;2000m&quot;

memory: &quot;2Gi&quot;

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 = &quot;http://kubecost-cost-analyzer.kubecost-system.svc.cluster.local:9090&quot;

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

&quot;window&quot;: f&quot;{inicio_ts}s,{fim_ts}s&quot;,

&quot;aggregate&quot;: &quot;node&quot;

}

response_reais = requests.get(f&quot;{kubecost_url}/model/allocation&quot;, 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 &quot;data&quot; in dados_reais:

for node_data in dados_reais[&quot;data&quot;]:

node_name = list(node_data.keys())[0]

custo = float(node_data[node_name].get(&quot;totalCost&quot;, 0))

Isso é uma simplificação - no mundo real, você consultaria tags do node

if &quot;spot&quot; 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 {

&quot;custo_spot_real_usd&quot;: round(custo_spot, 2),

&quot;custo_ondemand_real_usd&quot;: round(custo_ondemand, 2),

&quot;economia_estimada_spot_usd&quot;: round(economia, 2),

&quot;percentual_economia&quot;: round((economia / (economia + custo_ondemand)) * 100, 1) if economia &gt; 0 else 0

}

resultado = comparar_custo_spot_vs_ondemand()

print(json.dumps(resultado, indent=2))

Output esperado:

{

&quot;custo_spot_real_usd&quot;: 245.33,

&quot;custo_ondemand_real_usd&quot;: 1823.45,

&quot;economia_estimada_spot_usd&quot;: 730.99,

&quot;percentual_economia&quot;: 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Guia Completo de Grafana em Kubernetes: Dashboards, Alertas e Loki para Logs
Guia Completo de Grafana em Kubernetes: Dashboards, Alertas e Loki para Logs

Introdução ao Grafana em Kubernetes Grafana é uma plataforma de visualização...

O que Todo Dev Deve Saber sobre Pod Security Standards em Kubernetes: Restricted, Baseline e Privileged
O que Todo Dev Deve Saber sobre Pod Security Standards em Kubernetes: Restricted, Baseline e Privileged

Pod Security Standards no Kubernetes: Proteção em Camadas Pod Security Standa...

Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção
Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção

DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote Quando você começa...