Por Gabriel Xavier Supervisor de Gestão e Arquitetura de Sistemas | Especialista em Governança de TI e Analytics
Existe um paradoxo silencioso operando em boa parte das infraestruturas Kubernetes em produção: a camada responsável por garantir visibilidade sobre o sistema é, com frequência, a mesma que está consumindo os recursos que deveriam sustentar a carga real. Não é uma hipótese teórica, é o que equipes de plataforma descobrem, geralmente tarde demais, quando um incidente de “causa desconhecida” começa a mostrar sinais de OOMKilled em pods de telemetria antes de qualquer microserviço crítico.
Este artigo não vai explicar o que é observabilidade. Quem está lendo isso já passou por essa curva. O que vamos dissecar é o que acontece quando a estratégia de telemetria não é tratada como engenharia de software de primeira classe e como um sidecar de logging mal dimensionado em um workload CPU-bound pode, sem exagero, consumir mais recursos do que a aplicação que ele monitora.
O problema real de telemetria em produção
Imagine um cluster com 40 nós, carregando workloads de processamento de dados em Go, pipelines de transformação que fazem chamadas intensivas a goroutines, com picos de CPU em torno de 85% durante janelas de processamento em lote. A equipe de plataforma decidiu, em determinado momento, instrumentar o ambiente com OpenTelemetry e habilitar tracing distribuído em todas as chamadas de serviço. A ideia era razoável: ganhar visibilidade sobre gargalos entre serviços.
O que ninguém previu foi o efeito composto. Com tracing ativo em 100% das requisições, o OpenTelemetry Collector implantado como DaemonSet começou a competir por CPU com as próprias aplicações. A latência no percentil P99 saltou de 12ms para 19ms. Ninguém apontou o dedo para o coletor de telemetria porque ele “não estava na criticidade”. Ele estava rodando tranquilamente, coletando spans, transformando dados, exportando para o backend. Até que o cluster começou a fazer evições.
Esse cenário não é incomum. Pesquisa publicada no ResearchGate em 2025, comparando frameworks de observabilidade cloud-native, identificou que o OpenTelemetry pode introduzir entre 7% e 42% de overhead de CPU dependendo da abordagem de instrumentação adotada, com acréscimo de até 42% na latência em configurações mais agressivas. A amplitude desse intervalo é, por si só, um dado preocupante: significa que o impacto é altamente dependente de decisões de configuração que a maioria das equipes não testa antes de ir para produção.
Anatomia do Overhead: o que realmente acontece dentro do pod
Para entender por que sidecars de logging são particularmente problemáticos em workloads CPU-bound, é necessário entrar na mecânica de como esses containers operam dentro de um pod.
Quando você adiciona um sidecar de logging, seja Fluentd, Fluent Bit ou um agente customizado, ao seu pod, ele compartilha o mesmo cgroup e, dependendo da configuração de resources.limits, disputa diretamente os mesmos milicores com o container principal. O scheduler do Kubernetes não tem consciência semântica sobre quem é “mais importante” dentro de um pod. Ele gerencia recursos por container, e a soma de todos os containers no pod é o que conta para fins de agendamento.
O problema se intensifica em três dimensões:
Competição por I/O de disco. Sidecars que fazem tail de arquivos de log (modo de coleta via tail em /var/log/containers) criam chamadas de sistema read() contínuas. Em ambientes onde a aplicação principal também escreve em disco, seja para checkpointing, cache local ou saída de logs estruturados em arquivo, o sidecar e o container principal passam a disputar o mesmo subsistema de I/O. Em nós com SSDs NVMe dedicados, o impacto é menor. Em ambientes cloud com EBS ou discos de rede, a latência de I/O pode dobrar sob essa pressão conjunta.
Pressão de memória com buffer em RAM. O Fluentd, em sua configuração padrão, utiliza buffering em memória. Em um cluster com 50 pods, cada um carregando um sidecar Fluentd configurado com buffers de 256MB, você está consumindo até 12,8GB de RAM apenas para os buffers de log, antes de qualquer dado de aplicação. Há registros em fóruns da comunidade Kubernetes documentando vazamentos de memória graduais em Fluentd, com consumo crescendo ~100MiB/hora em ambientes de volume médio, chegando a ser encerrado pelo OOM Killer após poucas horas.
Serialização e parsing como CPU tax. Cada linha de log que passa por um pipeline de transformação, parsing JSON, enriquecimento com metadados de pod, filtragem por regex, custa ciclos de CPU. Em volumes de 50.000 linhas de log por segundo, que não é absurdo para uma aplicação de e-commerce em pico de Black Friday, o parser do coletor pode consumir mais CPU do que o próprio handler HTTP da aplicação principal.
O benchmark que ninguém mostra na apresentação de vendas
Tabela 1: Comparativo de Coletores de Log em Ambiente Sidecar (Carga Real)
| Coletor | RAM (idle) | RAM (carga alta) | CPU (carga alta) | Throughput máx. | Overhead no Pod (CPU-bound) |
|---|---|---|---|---|---|
| Fluentd (Ruby) | ~40 MB | 500 MB – 2 GB | 20–40% de 1 vCPU | ~10k eventos/s | Alto |
| Fluent Bit (C) | < 1 MB | 50–100 MB | 5–15% de 1 vCPU | ~200k eventos/s | Baixo-Médio |
| Vector (Rust) | ~30 MB | 100–200 MB | 10–20% de 1 vCPU | ~300k eventos/s | Médio |
| Logstash (JVM) | ~500 MB | 500 MB – 2 GB | 20–40% de 1 vCPU | ~20k eventos/s | Muito Alto |
| eBPF (Coroot/Cilium) | ~5 MB | ~15 MB | 2–4% de 1 vCPU | Kernel-level | Desprezível |
Fontes: Benchmark Onidel 2025, IBM Cloud log collectors benchmarking, Groundcover eBPF analysis, dados de campo consolidados.
A tabela acima revela algo que benchmarks de laboratório tendem a esconder: o Fluentd, que ainda é amplamente adotado em empresas que migraram de um stack legado de ELK, pode consumir o equivalente a um vCPU completo sob carga alta quando implantado como sidecar em um pod com aplicação CPU-intensive. Isso significa que um pod solicitando 2 vCPUs para sua aplicação principal, na prática, está operando com ~1.6 vCPU disponível e isso antes de considerar qualquer outro container no pod.
O Fluent Bit resolve parte do problema, mas não todos. Seu overhead em CPU permanece relevante quando pipelines de transformação complexos estão ativos. A diferença é que o Fluent Bit foi escrito em C com foco explícito em eficiência de memória, enquanto o Fluentd carrega um runtime Ruby que impõe custos fixos independentemente da carga.
Cenário A vs. Cenário B: dois clusters, duas histórias de overhead
- Cenário A — Cluster de Processamento de ML com Sidecar Fluentd em Debug Mode
Uma equipe de dados mantinha um cluster dedicado a jobs de treinamento de modelos, com workloads de 6–8 horas por job, consumindo quase 90% da CPU disponível nos nós durante a execução. O pipeline de CI/CD, ao fazer o deploy de uma nova versão do ambiente, aplicou a variável de ambiente LOG_LEVEL=debug na configuração do Fluentd como parte de um procedimento de troubleshooting que nunca foi revertido.
O resultado: o volume de logs processados pelo sidecar saltou de ~500 linhas/segundo para ~15.000 linhas/segundo. O Fluentd passou a consumir 35% do CPU de cada pod. O tempo de treinamento dos modelos aumentou em 22% sem nenhuma mudança no código de ML. A equipe passou duas semanas investigando regressões no pipeline de dados antes de alguém correlacionar o evento de deploy com a degradação.
Este é um padrão recorrente: habilitar log level DEBUG em produção aumenta o volume de logs em 5–10x, segundo análise da Holori sobre custos de plataformas de observabilidade. Em um ambiente com coleta ativa, isso não é apenas um problema de armazenamento é um multiplicador direto de CPU em cada nó que está coletando.
- Cenário B: cluster de API Gateway com DaemonSet de Fluent Bit e sem Backpressure
Em contraste, uma equipe de plataforma que migrou de Fluentd para Fluent Bit como DaemonSet, em vez de sidecar por pod, conseguiu reduzir o overhead de logging em aproximadamente 60% em termos de RAM por nó. O custo de CPU caiu de ~18% para ~6% do capacity do nó durante picos de tráfego.
O trade-off, porém, apareceu de forma inesperada: sem configuração de backpressure adequada, o Fluent Bit em modo DaemonSet começou a descartar linhas de log silenciosamente durante picos de tráfego acima do threshold configurado. O mem_buf_limit de 64MB foi ultrapassado em 3 segundos durante um spike de 150% no tráfego, e o coletor começou a aceitar a política drop_oldest sem emitir nenhuma métrica de aviso. A equipe de segurança percebeu a falha semanas depois, durante uma auditoria, ao notar gaps de 10–15 minutos nos logs de acesso.
A lição: menor overhead de telemetria não significa telemetria confiável. O desafio não é apenas reduzir o consumo de recursos é garantir que a estratégia de coleta sobrevive a condições de stress sem comprometer a integridade dos dados.
A armadilha da cardinalidade: quando as métricas explodem antes dos logs
Há um segundo vetor de overhead que geralmente passa despercebido até que a fatura de observabilidade SaaS chega no fim do mês: a cardinalidade de métricas.
Em clusters Kubernetes, cada label adicionado a uma série temporal de métrica multiplica o número de séries armazenadas. Um simples contador HTTP com labels de method, status_code, endpoint, pod_name e namespace pode gerar dezenas de milhares de séries temporais distintas em um ambiente com 200 pods e 50 endpoints únicos. O Prometheus, ao tentar fazer scrape dessas séries, começa a consumir RAM de forma não-linear, o fenômeno conhecido como “cardinality explosion”.
Equipes que monitoraram seus custos reportam crescimento de 2–10x no volume de dados de telemetria ao longo de 12 meses, sem mudanças significativas no volume de tráfego de negócios. O crescimento vem de instrumentação acumulativa: cada novo microserviço adicionado ao ambiente traz suas próprias métricas, traces e logs, e nenhuma política de governança garante que séries desnecessárias sejam aposentadas.
O efeito nos clusters autogerenciados é ainda mais severo: um Prometheus sem --storage.tsdb.retention.size configurado pode consumir todos os discos de um nó em 72 horas em ambientes de alto volume. Já foi registrado em postmortems públicos de empresas de médio porte.
Diagrama comparativo: arquiteturas de coleta e seu perfil de risco
ARQUITETURA 1 — Sidecar por Pod (Padrão Legado)
┌──────────────────────────────────┐
│ POD │
│ ┌───────────────┐ ┌──────────┐ │
│ │ App Container│ │ Fluentd │ │
│ │ (CPU: 80%) │ │ Sidecar │ │
│ │ │ │(CPU: 20%)│ │
│ └───────────────┘ └──────────┘ │
│ Compartilham: cgroup, network, │
│ volumes, namespace de processo │
└──────────────────────────────────┘
Risco: Alto — overhead direto no pod, competição por recursos,
amplifica impacto em CPU-bound workloads.
ARQUITETURA 2 — DaemonSet por Nó (Padrão Recomendado)
┌─────────────────────────────────────────────┐
│ NÓ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────────┐ │
│ │ Pod A│ │ Pod B│ │ Pod C│ │ Fluent Bit │ │
│ │ │ │ │ │ │ │ DaemonSet │ │
│ └──────┘ └──────┘ └──────┘ └────────────┘ │
│ Coleta via /var/log/containers/*.log │
└─────────────────────────────────────────────┘
Risco: Médio — overhead isolado, mas sem backpressure pode
perder logs. Falha silenciosa é o principal risco.
ARQUITETURA 3 — eBPF no Kernel (Abordagem Emergente)
┌───────────────────────────────────┐
│ KERNEL LINUX │
│ ┌─────────────────────────────┐ │
│ │ eBPF Probe (kernel space) │ │
│ │ CPU overhead: 2–4% │ │
│ │ Sem modificação do app │ │
│ └─────────────────────────────┘ │
│ ↕ Ringbuffer │
│ ┌────────────────────────────┐ │
│ │ User-space Collector │ │
│ └────────────────────────────┘ │
└───────────────────────────────────┘
Risco: Baixo — overhead mínimo, visibilidade kernel-level,
sem modificação de código. Complexidade de deploy é a barreira.
O problema de instrumentação em Go: um caso específico que a Maioria Ignora
Aplicações Go têm uma característica que as torna particularmente sensíveis a instrumentação mal projetada: o garbage collector. Quando o OpenTelemetry para Go cria spans e records de log em alta frequência, ele adiciona pressão ao GC, cada span.End(), cada logger.Info() com campos estruturados gera alocações no heap.
Em uma aplicação Go processando 50.000 requisições por segundo com tracing a 100%, o benchmark publicado pela Coroot mostrou que a instrumentação via OpenTelemetry SDK pode introduzir overhead de 35% em CPU e aumentar a latência P99 em ~5ms. O mesmo workload com instrumentação via eBPF (sem modificação no código Go) registrou overhead inferior a 3% de CPU e impacto negligenciável em latência.
A diferença não é pequena. Em termos práticos: se você tem um cluster de 20 nós para suportar esse workload, a instrumentação OpenTelemetry nativa pode demandar o equivalente a 7 nós adicionais apenas para absorver o overhead de telemetria. Com eBPF, esse custo cai para menos de 1 nó.
Tabela 2: Impacto de Instrumentação em Aplicação Go (50k req/s)
| Abordagem | Overhead CPU | Latência P99 | Alocações GC extras | Custo de infra proporcional |
|---|---|---|---|---|
| Sem instrumentação | baseline | baseline | 0 | baseline |
| OTel SDK (100% sampling) | +35% | +5ms | Alto | +1,75 nós por 20 nós |
| OTel SDK (1% sampling) | +8% | +0,5ms | Baixo | +0,4 nós por 20 nós |
| eBPF (Coroot/Cilium) | +2–4% | +0,2ms | Nenhuma | +0,1 nós por 20 nós |
Interpretação dos autores com base em dados do benchmark Coroot 2025 e pesquisa ResearchGate 2025.
Sampling inteligente: a decisão que muda o jogo
Uma das intervenções de maior impacto que uma equipe de plataforma pode fazer, sem trocar de coletor, sem reescrever instrumentação é redefinir a estratégia de sampling.
O sampling head-based, onde a decisão de coletar ou descartar um trace é tomada no início da requisição, é simples de implementar e reduz significativamente o volume de dados. O problema é que ele descarta traces de forma probabilística: uma transação rara que resulta em erro pode ser descartada com a mesma probabilidade que uma transação normal.
O sampling tail-based inverte essa lógica: todos os spans são coletados temporariamente em buffer, e a decisão de reter ou descartar é tomada no final, com visibilidade sobre o comportamento completo da requisição. Traces que resultam em erro, alta latência ou comportamento anômalo são retidos; o restante é descartado. O resultado prático é que você retém 100% dos traces que importam para debugging e descarta 90%+ do volume de traces rotineiros.
A armadilha do tail-based está no custo do buffer: para fazer essa decisão, o Collector precisa manter todos os spans em memória por alguns segundos. Em ambientes de alto volume, esse buffer pode consumir gigabytes de RAM se não for adequadamente dimensionado e monitorado. É um overhead diferente, mas ainda é overhead e precisa ser planejado.
Timeline: como um cluster implode por telemetria mal dimensionada
A cronologia abaixo é uma reconstrução baseada em padrões recorrentes de incidentes observados em postmortems de equipes de plataforma e fóruns de SRE:
T+0h Deploy de nova versão com LOG_LEVEL=debug ativo por acidente.
Volume de logs sobe 8x. Ninguém nota imediatamente.
T+2h Buffers de memória do Fluentd sidecar começam a crescer.
CPU de pods CPU-bound sobe ~20% acima do normal.
Alertas de latência começam a disparar (P99 > threshold).
T+4h Primeira evição de pod por OOMKilled — não na aplicação,
mas no próprio sidecar de Fluentd. Pod é reiniciado.
Logs do período da evição são perdidos.
T+6h Com pods de sidecar reiniciando ciclicamente, o scheduler
começa a tentar realocar pods em outros nós.
Nós com menos RAM disponível começam a ser pressionados.
T+8h Um nó com menos recursos entra em memory pressure.
O kubelet começa evições preventivas de pods de menor prioridade.
Dois pods de aplicação crítica são eviccionados.
T+9h Alerta de SLA disparado. Postmortem iniciado.
A causa raiz será identificada apenas 48h depois,
ao correlacionar o evento de deploy com o pico de CPU dos sidecars.
A cronologia acima captura um padrão documentado: o colapso não começa na aplicação, mas na telemetria. E o diagnóstico demora porque ninguém monitora os monitores.
Governança de telemetria: o que equipes maduras fazem diferente
Equipes que operam clusters de alta escala sem surpresas de overhead de telemetria geralmente têm em comum algumas práticas que não aparecem em documentações de ferramentas:
Resource requests e limits explícitos para todo container de telemetria. Sem resources.limits.cpu e resources.limits.memory definidos, o Kubernetes trata o sidecar como Burstable ou BestEffort, permitindo que ele consuma recursos ilimitadamente durante picos. Definir limites conservadores e monitorar quando eles são atingidos é a diferença entre um sidecar comportado e um candidato a OOMKilled.
Separação de criticidade com PriorityClass. Containers de aplicação crítica devem ter PriorityClass mais alta que containers de observabilidade. Isso garante que, sob pressão de recursos, o scheduler evicte primeiro os coletores de log, não os serviços que geram receita.
Alerta sobre os próprios coletores. É comum ter alerta para a aplicação e nenhum alerta para o pipeline de telemetria. Métricas como fluentbit_input_bytes_total, taxa de drops do buffer, e consumo de memória do Prometheus são tão críticas quanto throughput de API.
Política de log level por ambiente. DEBUG em staging, INFO em produção, WARN em ambientes de alta carga. Simples de escrever como política, mas raramente enforçada de forma automatizada. Ferramentas como OPA/Gatekeeper podem validar configurações de log level durante admission review, impedindo que o padrão errado chegue à produção.
Revisão periódica de cardinalidade. Uma métrica com cardinalidade não monitorada pode crescer de forma assimétrica. Ferramentas como mimirtool analyze prometheus ou os dashboards de cardinalidade do Grafana identificam séries temporais candidatas a serem removidas ou agregadas.
O caso do eBPF: promessa real ou hype de conferência?
Desde 2023, o eBPF tem sido apresentado em conferências de cloud-native como a solução definitiva para overhead de observabilidade. A afirmação tem fundamento técnico, mas merece ser qualificada.
O eBPF opera no espaço do kernel Linux, capturando eventos, chamadas de sistema, tráfego de rede, execuções de função, sem modificar o código da aplicação e sem adicionar containers ao pod. Um programa eBPF que coleta métricas de rede e CPU introduz overhead de 2–4% de CPU, segundo análise consolidada da Groundcover e publicação acadêmica da World Journal of Advanced Engineering Technology and Sciences (2025). Para fins práticos, isso é próximo de zero em qualquer cálculo de capacity planning.
A Linux Foundation, em relatório de 2025, documentou que soluções eBPF para observabilidade podem reduzir o consumo de ciclos de CPU em 20% ou mais em comparação com abordagens baseadas em agentes tradicionais, uma redução que, em clusters grandes, se traduz diretamente em nós a menos rodando na conta de cloud.
A barreira, no entanto, é real: desenvolver e manter programas eBPF customizados requer expertise em programação de kernel que a maioria das equipes de plataforma não tem. O caminho prático é usar ferramentas como Cilium, Coroot, Pixie ou Hubble que encapsulam a complexidade do eBPF em abstrações operacionais acessíveis. A adoção cresceu de forma expressiva em 2024–2025, com Linux 6.9 e 6.12 introduzindo BPF tokens, BPF arena e o framework sched_ext, ampliando o que é possível fazer sem modificar código de aplicação.
Para workloads CPU-bound em Go, Rust ou C++, onde cada ciclo de CPU desperdiçado em telemetria é um ciclo subtraído de work real, a transição para eBPF é a decisão de plataforma com maior relação custo-benefício disponível hoje.
A conta que ninguém faz: o custo financeiro da telemetria mal projetada
O relatório da New Relic de 2025 documentou que incidentes de alto impacto custam em média US$ 2 milhões por hora de downtime e que organizações com observabilidade full-stack reduzem esse custo pela metade. O argumento é poderoso para justificar investimento em telemetria.
O que o mesmo relatório não quantifica é o custo da telemetria excessiva ou mal projetada. Se você está rodando um cluster Kubernetes em AWS, GCP ou Azure, cada nó adicional necessário para absorver overhead de instrumentação tem um custo direto. Um cluster de 50 nós em que 15% do capacity computacional está sendo consumido por telemetria representa ~7,5 nós pagos mensalmente para monitorar, não para servir.
Com instâncias de propósito geral em nuvem pública rodando em torno de US$ 300–500/mês por nó de capacidade média, 7,5 nós representam entre US$ 2.250 e US$ 3.750 por mês desperdiçados ou US$ 27.000 a US$ 45.000 anuais em uma única conta de cloud, apenas em overhead de telemetria não otimizada.
Esse número não inclui o custo de ingestão em plataformas SaaS como Datadog ou Splunk, que cobram por volume de dados ingeridos. Habilitar debug logging em produção, como mencionado anteriormente, pode multiplicar o volume de ingestão em 8–10x, o que transforma uma fatura de observabilidade de US$ 20.000/mês em US$ 160.000/mês em semanas.
Conclusão: telemetria é engenharia, não configuração
O erro conceitual que está na raiz da maioria dos problemas descritos neste artigo é tratar telemetria como uma camada de configuração, algo que você “liga” e esquece. Na prática, um pipeline de observabilidade em alta escala é um sistema distribuído dentro de um sistema distribuído, com seus próprios padrões de falha, pressões de recursos e trade-offs de confiabilidade.
Cada decisão de instrumentação, o nível de log, a taxa de sampling, a arquitetura de coleta, os labels de métricas, tem um custo direto e mensurável em CPU, memória, latência e dinheiro. E esses custos se compõem de forma não-linear à medida que o cluster cresce.
A observabilidade que realmente funciona em alta escala não é a que coleta tudo, é a que coleta o que importa, no momento que importa, com o menor custo possível para o sistema que está sendo observado. Alcançar esse equilíbrio não é trivial, mas é inteiramente engenheirado. E, como qualquer problema de engenharia, começa com medir o que você ainda não está medindo: o custo da sua própria telemetria.





