Dissecando o estado D no Linux: rastreamento de gargalos de I/O em Kubernetes com eBPF
Aprenda como diagnosticar pods travados no Kubernetes devido a gargalos de storage. Use eBPF para rastrear o estado D (Uninterruptible Sleep) e recupere seus SLOs.
O pager toca às três da manhã de uma terça-feira. O alerta indica uma queima acelerada do orçamento de erro (error budget) devido a picos de latência de cauda no serviço de checkout. Ao abrir os dashboards, a CPU dos nós do Kubernetes está ociosa e a memória tem folga. No entanto, dezenas de pods estão travados no estado Terminating. O instinto de muitos operadores seria culpar a aplicação ou reiniciar o agente do nó. Como Engenheiros de Confiabilidade (SRE), sabemos que o sistema está nos dizendo algo mais profundo. O gargalo não está na computação, mas sim nas profundezas da pilha de armazenamento de dados.
Resumo em 30 segundos
- O estado D (
TASK_UNINTERRUPTIBLE) indica processos travados aguardando respostas do hardware de storage (NVMe, SAN, iSCSI).- Forçar a exclusão de pods no Kubernetes apenas mascara o problema real de saturação nas filas de I/O dos discos.
- O eBPF permite instrumentar a camada de bloco do kernel para identificar exatamente qual volume persistente está destruindo seu SLO de latência.
A queima do orçamento de erro por latência de cauda
Na cultura de SRE, medimos a saúde do sistema através de Indicadores de Nível de Serviço (SLIs). Quando a latência do percentil 99 (p99) ultrapassa nosso limite aceitável, o orçamento de erro começa a queimar. Em arquiteturas baseadas em microsserviços, um único volume persistente (PV) com lentidão pode causar um efeito cascata. Se um banco de dados ou um serviço de mensageria não consegue confirmar a gravação no disco em tempo hábil, as requisições de rede se acumulam.
O problema de investigar latência de storage em ambientes conteinerizados é a abstração. O Kubernetes provisiona volumes dinamicamente, muitas vezes mascarando a infraestrutura física subjacente. Pode ser um array all-flash saturado na rede de área de armazenamento (SAN), um disco EBS na nuvem atingindo seu limite de IOPS ou um SSD NVMe local lidando com amplificação de gravação excessiva.
A falha não é do desenvolvedor que escreveu o código. A falha é sistêmica: nossa plataforma carece de observabilidade granular na camada de bloco. Precisamos descer ao nível do kernel para entender onde os milissegundos estão sendo perdidos.
A mecânica do estado TASK_UNINTERRUPTIBLE e a saturação do storage
No Linux, o ciclo de vida de um processo passa por vários estados. O estado R significa executando, enquanto o S indica um sono passível de interrupção. O verdadeiro vilão da latência de storage é o estado D, formalmente conhecido como TASK_UNINTERRUPTIBLE. Quando um processo entra neste estado, ele está dizendo ao escalonador do kernel que iniciou uma operação crítica de hardware e não pode ser interrompido por nenhum sinal, nem mesmo um SIGKILL.
Na esmagadora maioria das vezes, essa operação crítica é uma requisição de entrada e saída (I/O) na camada de bloco. O processo enviou um comando de leitura ou gravação para o sistema de arquivos, que o repassou para a camada de bloco genérica, que por sua vez o colocou na fila do driver do dispositivo de armazenamento (como o driver NVMe ou SCSI). Se o disco físico ou o storage de rede demorar para responder, o processo fica congelado no estado D.
💡 Dica Pro: Você pode identificar rapidamente processos travados em I/O no seu nó executando o comando
ps aux | awk '{if ($8 ~ /D/) print $0}'. Se a lista for longa, seu storage está pedindo socorro.
Figura: Diagrama do fluxo de I/O no kernel Linux mostrando um pod travado no estado D aguardando a fila do NVMe.
Quando múltiplos pods tentam acessar um volume persistente que está sofrendo de alta latência, as filas de submissão do hardware ficam saturadas. O kernel Linux, projetado para garantir a integridade dos dados, não abortará a operação. Ele esperará pacientemente que a controladora do disco confirme a gravação. Durante essa espera, os recursos do nó são consumidos de forma invisível.
A ilusão do kubectl delete force e o mascaramento de falhas
Durante um incidente, a pressão para restaurar o serviço é imensa. É comum ver operadores executando kubectl delete pod <nome> --force --grace-period=0 ao se depararem com pods travados. Em uma cultura de post-mortem sem culpa (blameless), entendemos por que isso acontece: a ferramenta parece resolver o sintoma imediato na interface do cluster. No entanto, essa ação é uma armadilha perigosa quando o problema raiz é o armazenamento.
Forçar a exclusão de um pod apenas instrui o plano de controle do Kubernetes a remover o registro daquele objeto no banco de dados etcd. O kubelet no nó de trabalho recebe a ordem, mas o processo real do contêiner continua existindo no sistema operacional hospedeiro. Como o processo está no estado D aguardando o disco, o kernel ignora a tentativa de encerramento do kubelet.
⚠️ Perigo: O uso indiscriminado do delete force em cenários de gargalo de I/O cria processos zumbis que continuam segurando descritores de arquivos (file descriptors) e conexões com o storage. Isso pode levar à exaustão completa dos recursos do nó, exigindo um hard reset do servidor físico.
Em vez de mascarar a falha do disco matando pods virtualmente, precisamos diagnosticar a saúde da camada de bloco. É aqui que as ferramentas tradicionais começam a falhar e precisamos de instrumentação moderna.
Rastreando a latência do bloco de armazenamento com eBPF
Historicamente, administradores de sistemas dependiam de ferramentas como o iostat para monitorar discos. Embora útil para ver a média de IOPS e a largura de banda, o iostat é cego para a latência de cauda. Ele agrega dados em intervalos de segundos, escondendo micro-explosões de latência que destroem os SLOs de serviços sensíveis. Para resolver isso, a engenharia de confiabilidade moderna adotou o eBPF (Extended Berkeley Packet Filter).
O eBPF é uma tecnologia revolucionária que permite executar programas em um ambiente de sandbox dentro do kernel do sistema operacional. Ele oferece instrumentação segura e com baixíssimo overhead. Para o ecossistema de storage, isso significa que podemos anexar sondas (probes) exatamente nas funções do kernel que enviam e recebem comandos para os discos SSDs ou arrays de armazenamento.
| Característica | Ferramentas Tradicionais (ex: iostat) | Instrumentação com eBPF (ex: biolatency) |
|---|---|---|
| Granularidade | Agregada por segundos (médias). | Por evento de I/O (microssegundos). |
| Visibilidade de Cauda | Baixa. Esconde picos de latência. | Alta. Gera histogramas precisos do p99. |
| Identificação de Causa | Mostra apenas o disco afetado. | Mapeia o PID, o contêiner e o volume exato. |
| Overhead no Sistema | Baixo. | Extremamente baixo (seguro para produção). |
Utilizando a coleção de ferramentas BCC (BPF Compiler Collection), podemos usar utilitários como o biolatency. Esta ferramenta rastreia o tempo exato desde o momento em que a requisição de bloco é emitida para o driver do dispositivo até a sua conclusão (hardware completion). O resultado é um histograma claro que revela se a maior parte do seu I/O está respondendo em microssegundos (típico de um SSD NVMe saudável) ou se há uma cauda longa na casa dos milissegundos.
Figura: Histograma de latência de bloco gerado por eBPF evidenciando a latência de cauda no storage.
Outra ferramenta inestimável é o biosnoop, que imprime uma linha para cada operação de I/O no disco, detalhando o processo solicitante, o setor do disco e a latência exata. Ao cruzar o PID fornecido pelo biosnoop com os cgroups do Kubernetes, o SRE consegue apontar com precisão cirúrgica qual Persistent Volume Claim (PVC) e qual pod estão sofrendo com o hardware subjacente.
Validando a recuperação do SLO após o ajuste do storage
Uma vez que o eBPF isola o gargalo na camada de bloco, a remediação torna-se baseada em dados. O problema pode exigir o ajuste do escalonador de I/O do Linux. Para discos NVMe modernos, o escalonador none ou kyber costuma ser mais eficiente que o antigo mq-deadline, pois evita enfileiramentos desnecessários em software quando o hardware já possui múltiplas filas de submissão nativas.
Em cenários de nuvem, a análise do eBPF pode provar que o volume de armazenamento em bloco provisionado atingiu seu limite de burst de IOPS. A solução arquitetural passa a ser a migração do banco de dados para uma classe de storage superior (como io2 na AWS ou Ultra Disk no Azure) ou a implementação de um padrão de cache em memória para aliviar a pressão de leitura no disco.
A validação do sucesso não ocorre quando o alerta é silenciado, mas sim quando o SLI de latência retorna aos níveis normais e o orçamento de erro para de queimar. A cultura SRE exige que o aprendizado do incidente seja incorporado ao sistema. Exportar métricas baseadas em eBPF para o Prometheus garante que o próximo gargalo de storage seja detectado antes que os pods comecem a travar no estado D.
O imperativo da observabilidade profunda em storage
A complexidade das infraestruturas de dados continuará crescendo. Com a adoção de tecnologias como CXL (Compute Express Link) para expansão de memória e storage, e a proliferação de drives NVMe PCIe Gen 5, os gargalos mudarão de lugar. O hardware está ficando tão rápido que a própria pilha de software do kernel pode se tornar o fator limitante.
Depender de métricas de superfície ou de reinicializações arbitrárias de pods é uma estratégia insustentável para a confiabilidade em escala. A adoção do eBPF como padrão para telemetria de armazenamento não é mais um luxo de grandes empresas de tecnologia, mas uma necessidade fundamental para qualquer equipe que opere sistemas de dados críticos em Kubernetes. O sistema sempre nos diz onde está doendo, só precisamos usar as ferramentas certas para escutar a camada de bloco.
Referências & Leitura Complementar
BPF Performance Tools (Brendan Gregg): O guia definitivo sobre o uso de eBPF e BCC para análise de performance de I/O e sistemas de arquivos.
NVM Express Base Specification (NVMe.org): Documentação técnica sobre a arquitetura de filas de submissão e conclusão que interagem com o driver de bloco do Linux.
Site Reliability Engineering (Google): Capítulos sobre Service Level Objectives (SLOs) e a importância de medir a latência de cauda em sistemas distribuídos.
O que significa o estado D (Uninterruptible Sleep) no Linux?
É um estado do kernel onde o processo aguarda a liberação de um recurso de hardware, quase sempre operações de I/O em discos (HDD, SSD, NVMe) ou storage de rede (NFS, iSCSI). Neste estado, o processo ignora qualquer sinal do sistema, incluindo o SIGKILL, pois interrompê-lo poderia causar corrupção de dados na camada de bloco.Como o eBPF ajuda a diagnosticar problemas de storage no Kubernetes?
O eBPF permite instrumentar o kernel do Linux de forma segura e com baixíssimo overhead. Ele consegue medir o tempo exato que as requisições de bloco (block I/O) levam na camada de storage, apontando exatamente qual disco físico ou volume persistente está causando a latência de cauda, algo que ferramentas tradicionais como o iostat não conseguem detalhar.Por que o comando 'kubectl delete pod --force' não resolve pods travados em Terminating?
Forçar a exclusão apenas remove o registro do pod no banco de dados etcd do Kubernetes. O processo subjacente no nó do cluster continua travado no estado D aguardando a resposta do hardware de storage. Essa prática cria processos zumbis, o que pode levar à exaustão de recursos (file descriptors, memória) e causar uma falha em cascata no nó físico.
Rafael Junqueira
Engenheiro de Confiabilidade (SRE)
"Transformo caos em estabilidade via observabilidade. Defensor da cultura blameless e focado em SLIs e SLOs. Se algo falhou, revisamos o sistema, nunca a pessoa."