---
title: "Zig e eBPF: Observabilidade e Rede no Linux sem Runtime Pesado"
url: "https://ziglang.com.br/artigos/zig-ebpf-observabilidade-rede-linux/"
markdown_url: "https://ziglang.com.br/artigos/zig-ebpf-observabilidade-rede-linux.MD"
description: "Como pensar em Zig para programas eBPF no Linux: limites do verifier, mapas, eventos, user space, observabilidade, rede e quando C ou Go ainda fazem mais sentido."
date: "2026-05-20"
author: ""
---

# Zig e eBPF: Observabilidade e Rede no Linux sem Runtime Pesado

Como pensar em Zig para programas eBPF no Linux: limites do verifier, mapas, eventos, user space, observabilidade, rede e quando C ou Go ainda fazem mais sentido.


eBPF deixou de ser um detalhe de kernel para virar uma das tecnologias mais importantes em observabilidade, segurança, rede e performance no Linux moderno. Ferramentas como Cilium, bpftrace, Falco, Pixie e muitos agentes de monitoramento usam eBPF para observar syscalls, tráfego, latência, alocações, DNS, conexões TCP e eventos de kernel sem inserir módulos tradicionais nem recompilar o sistema operacional.

Para quem acompanha Zig, a pergunta aparece naturalmente: **faz sentido escrever programas eBPF com Zig?** A resposta curta é: pode fazer sentido em partes específicas, especialmente quando você quer controle fino, binário pequeno, integração direta com C e menos runtime no user space. Mas eBPF tem restrições próprias. Não é apenas “compilar Zig para Linux”. O verifier do kernel, o target BPF, os mapas, os helpers e a divisão entre programa no kernel e controlador em user space mudam a forma de projetar o código.

Este artigo explica onde Zig encaixa nesse cenário, quais cuidados tomar e como estruturar um projeto realista. Ele complementa os guias de [observabilidade em Zig](/artigos/zig-observabilidade/), [networking com sockets TCP/UDP](/artigos/zig-networking-sockets-tcp-udp/), [interoperabilidade com C](/artigos/zig-interoperabilidade-c/) e [debugging/profiling com Tracy, Valgrind e perf](/artigos/zig-depuracao-profiling-tracy-valgrind-perf/).

## O que eBPF muda no desenvolvimento Linux

Um programa eBPF roda dentro de um ambiente controlado pelo kernel. Ele pode ser anexado a pontos como tracepoints, kprobes, uprobes, XDP, TC, cgroups e LSM hooks. Em vez de executar qualquer código arbitrário, ele passa por um verifier que tenta provar que o programa é seguro: não acessa memória inválida, não tem loops perigosos, não estoura limites e não derruba o kernel.

Isso traz uma troca interessante. Você ganha observabilidade e controle muito perto do kernel, mas perde várias liberdades comuns no user space. O programa eBPF normalmente não pode alocar memória dinamicamente como uma aplicação comum, não pode chamar funções arbitrárias, não pode usar syscall diretamente e precisa trabalhar com helpers permitidos pelo kernel.

Na prática, a arquitetura fica dividida:

1. um programa eBPF pequeno, carregado no kernel;
2. mapas eBPF para compartilhar estado;
3. um processo em user space que carrega, configura, lê eventos e exporta métricas.

É nesse terceiro ponto que Zig já é muito confortável hoje. Mesmo que o programa eBPF em si continue exigindo cuidado extremo, o agente em user space pode se beneficiar bastante de Zig.

## Por que Zig combina com esse tipo de problema

Zig tem algumas características que conversam bem com eBPF. A primeira é a ausência de runtime pesado. Agentes de observabilidade muitas vezes rodam em todos os servidores de uma frota. Cada megabyte de memória, cada dependência nativa e cada processo auxiliar importam. Um controlador escrito em Zig pode ser distribuído como binário único, com inicialização rápida e uso previsível de memória.

A segunda é a integração com C. O ecossistema eBPF do Linux ainda é profundamente C: `libbpf`, headers do kernel, estruturas como `bpf_map_def`, helpers, CO-RE e convenções herdadas de ferramentas C. Zig consegue importar headers, linkar bibliotecas C e trabalhar perto dessas APIs sem criar uma barreira artificial.

A terceira é a cultura de explicitude. eBPF pune abstrações que escondem custo. Se você não sabe quando aloca, copia, serializa ou bloqueia, vai sofrer para depurar perda de evento, pressão em ring buffer ou overhead em caminho crítico. Zig obriga o projeto a declarar essas escolhas.

O ponto importante: Zig não transforma eBPF em algo simples. Ele apenas pode ser uma boa linguagem para escrever partes do sistema com menos dependência operacional e mais controle.

## Programa eBPF não é aplicação normal

Ao escrever o trecho que roda no kernel, pense em código mínimo. O objetivo não é colocar regra de negócio ali. O objetivo é coletar sinal, fazer filtragem barata e enviar dados para user space.

Um programa de tracepoint para observar abertura de arquivos, por exemplo, deveria fazer algo como:

1. ler os campos mínimos do contexto;
2. aplicar filtro simples, se existir;
3. escrever um evento compacto em ring buffer;
4. retornar rápido.

Evite parser complexo, strings longas, estrutura dinâmica e lógica que muda toda semana. Quanto mais comportamento você coloca no programa eBPF, mais difícil fica passar no verifier, manter compatibilidade entre kernels e explicar bugs em produção.

Em Zig, essa disciplina continua valendo. Mesmo que o compilador permita expressar tipos ricos, o target BPF e o verifier impõem um subconjunto mental. Use tipos simples, layout previsível e estruturas compatíveis com C quando os dados cruzam a fronteira kernel/user space.

## Mapas: o contrato entre kernel e user space

Mapas eBPF são o principal mecanismo de comunicação e estado. Eles podem guardar contadores, configurações, allowlists, histogramas, eventos e referências por CPU. Para um agente em Zig, o desenho dos mapas é tão importante quanto o código de coleta.

Alguns padrões comuns:

- **hash map** para guardar contagem por chave, como PID, porta ou endereço;
- **array map** para configuração pequena e indexada;
- **per-CPU map** para reduzir contenção em contadores de alta frequência;
- **ring buffer** para enviar eventos estruturados ao user space;
- **LRU hash** quando o cardinality pode crescer, mas você aceita expulsão.

O erro clássico é mandar evento demais. Se você registra cada pacote, syscall ou chamada de função sem agregação, o user space vira gargalo. Uma boa estratégia é filtrar cedo e agregar quando possível. Para latência HTTP, talvez faça sentido medir apenas começo/fim de requisições relevantes. Para rede, talvez o programa conte fluxos por porta em vez de emitir um evento por pacote.

Zig ajuda no lado user space porque você pode modelar o layout dos eventos com `extern struct`, controlar buffers e serializar para JSON, Prometheus text format ou logs estruturados sem puxar um framework inteiro.

## User space em Zig: o agente que faz o trabalho chato

O controlador em user space normalmente precisa fazer várias tarefas pouco glamourosas:

1. carregar o objeto eBPF;
2. escolher onde anexar cada programa;
3. configurar mapas;
4. ler eventos de ring buffer;
5. resolver símbolos, PIDs, containers ou nomes de processo;
6. expor métricas por HTTP;
7. lidar com shutdown limpo.

Esse é um bom território para Zig. Você pode usar `libbpf` via C interop, manter uma arquitetura pequena e produzir um binário operacionalmente simples. Se o agente precisa expor métricas, o guia de [servidor HTTP em produção](/artigos/zig-http-server-producao/) ajuda a pensar em health check, limites e logs. Se precisa empacotar para várias distros, o material de [GitHub Actions para releases multiplataforma](/artigos/zig-github-actions-release-multiplataforma/) encaixa bem.

Um esqueleto de projeto poderia ficar assim:

```text
zig-ebpf-agent/
  build.zig
  src/
    main.zig
    bpf_loader.zig
    events.zig
    metrics.zig
  bpf/
    tcp_connect.bpf.c
```

Mesmo que o programa BPF inicial esteja em C, Zig ainda agrega valor como controlador. Essa abordagem híbrida é pragmática: use C onde o ecossistema eBPF está mais maduro e Zig onde você quer distribuição, controle e integração com o resto da ferramenta.

## Zig compilando para BPF: promessa e cautela

Zig conversa com targets de baixo nível e pode emitir código para arquiteturas suportadas pela cadeia LLVM, incluindo BPF em ambientes adequados. Isso abre espaço para escrever programas eBPF diretamente em Zig. Mas essa área exige cautela maior do que um tutorial comum.

O problema não é só “gerar bytecode”. O bytecode precisa passar no verifier, preservar layout esperado pelo kernel, usar seções e símbolos reconhecidos pelo loader, chamar helpers corretamente e manter compatibilidade com a forma como `libbpf` espera encontrar mapas e programas. Pequenas diferenças de ABI, seção ou otimização podem quebrar o carregamento.

Por isso, para times que precisam entregar algo em produção hoje, o caminho mais seguro costuma ser incremental:

1. comece com programa eBPF em C usando `clang`/`libbpf`;
2. escreva o agente em Zig;
3. estabilize mapas, eventos e métricas;
4. só depois experimente mover trechos BPF para Zig;
5. mantenha testes de carregamento em kernels reais.

Essa sequência reduz risco. Você aprende o domínio eBPF antes de trocar todas as peças ao mesmo tempo.

## Casos de uso bons para Zig + eBPF

Zig faz mais sentido quando o agente precisa ser pequeno, portável e previsível. Alguns casos interessantes:

- coletor de conexões TCP para serviço interno;
- agente de latência para chamadas HTTP ou banco;
- auditoria de execução de processos em servidores Linux;
- medidor de DNS por container;
- contador de erros de syscall por binário;
- ferramenta local de diagnóstico para times de infraestrutura;
- experimento de XDP para filtrar tráfego simples.

Em todos eles, a parte eBPF deve ser enxuta. O valor do produto fica no user space: enriquecer evento, correlacionar processo com container, agregar métrica, escrever log útil, expor endpoint, configurar filtros e entregar uma instalação simples.

Se o seu objetivo é construir uma plataforma enorme de rede ou segurança, talvez o ecossistema existente em C, Go e Rust ofereça mais atalhos. Para comparar com outra cultura operacional, vale olhar o material de <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go no Brasil</a>: muitas ferramentas cloud native e de observabilidade usam Go justamente pela combinação de biblioteca madura, deployment simples e boa concorrência. Zig entra quando você quer ainda menos runtime e mais controle manual.

## Armadilhas práticas

A primeira armadilha é ignorar versão de kernel. Nem todo recurso eBPF está disponível em todo ambiente. Se você roda em frota heterogênea, precisa detectar capacidades, degradar funcionalidade ou documentar requisitos mínimos.

A segunda é cardinality. Uma métrica por PID, container, rota, usuário e IP pode explodir em minutos. eBPF dá acesso a muito dado; isso não significa que todo dado deve virar série temporal.

A terceira é permissão. Carregar programas eBPF normalmente exige privilégios específicos, capacidades Linux e configuração do sistema. Um agente fácil de distribuir ainda precisa ser honesto sobre requisitos de segurança.

A quarta é depuração. Quando algo falha, pode ser erro de compilação, verifier, loader, permissão, incompatibilidade de kernel, layout de struct ou perda no ring buffer. Registre mensagens claras e tenha um modo diagnóstico que imprime versão do kernel, recursos detectados e pontos de attach.

## Como começar sem se perder

Um bom primeiro projeto é observar conexões TCP de saída e agregar por processo. O programa eBPF captura evento de connect, manda PID, porta e endereço. O agente Zig resolve nome do processo, agrupa contadores e expõe `/metrics` em formato simples.

Esse projeto força as decisões certas sem virar uma plataforma inteira:

- layout de evento entre BPF e Zig;
- leitura de ring buffer;
- agregação com limite de cardinality;
- endpoint HTTP pequeno;
- logs de carregamento;
- shutdown com limpeza de recursos.

Depois disso, você pode evoluir para latência por syscall, DNS, erros de rede ou XDP. O importante é manter a fronteira limpa: kernel coleta sinal mínimo; Zig em user space transforma sinal em informação útil.

## Conclusão

Zig e eBPF combinam melhor quando você evita hype e pensa como operador. eBPF dá acesso a sinais profundos do Linux. Zig dá controle, distribuição simples e integração direta com C. Juntos, eles podem gerar agentes pequenos, previsíveis e fáceis de instalar para observabilidade e rede.

O caminho mais seguro hoje é pragmático: use o ecossistema eBPF maduro onde ele já é forte, escreva o controlador em Zig, valide em kernels reais e só mova mais código para Zig quando o ganho compensar o risco. Para times que trabalham com infraestrutura, rede, segurança ou performance, esse é um campo excelente para aprender Zig de forma aplicada: perto do kernel, perto do problema e longe de abstrações decorativas.
