ProHoster > Blog > administração > Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud
Uma história sobre a falta de pacotes DNS do suporte técnico do Google Cloud
Do Editor de blogs do Google: Você já se perguntou como os engenheiros de soluções técnicas do Google Cloud (TSE) lidam com suas solicitações de suporte? Os engenheiros de suporte técnico da TSE são responsáveis por identificar e corrigir fontes de problemas relatadas pelos usuários. Alguns desses problemas são bastante simples, mas às vezes você se depara com um ticket que requer a atenção de vários engenheiros ao mesmo tempo. Neste artigo, um dos funcionários do TSE nos contará sobre um problema muito complicado de sua prática recente - caso de pacotes DNS perdidos. Nesta história, veremos como os engenheiros conseguiram resolver a situação e que coisas novas aprenderam ao corrigir o erro. Esperamos que esta história não apenas eduque você sobre um bug profundo, mas também forneça informações sobre os processos necessários para preencher um tíquete de suporte no Google Cloud.
A solução de problemas é uma ciência e uma arte. Tudo começa com a construção de uma hipótese sobre o motivo do comportamento atípico do sistema, após a qual sua resistência é testada. No entanto, antes de formularmos uma hipótese, devemos definir claramente e formular com precisão o problema. Se a pergunta parecer muito vaga, você terá que analisar tudo com cuidado; Esta é a “arte” da solução de problemas.
No Google Cloud, esses processos se tornam exponencialmente mais complexos, à medida que o Google Cloud faz o possível para garantir a privacidade de seus usuários. Por causa disso, os engenheiros do TSE não têm acesso para editar seus sistemas, nem a capacidade de visualizar as configurações de forma tão ampla quanto os usuários. Portanto, para testar qualquer uma de nossas hipóteses, nós (engenheiros) não podemos modificar rapidamente o sistema.
Alguns usuários acreditam que vamos consertar tudo como um mecânico em um serviço de carro, e simplesmente nos enviar o id de uma máquina virtual, quando na realidade o processo ocorre em formato conversacional: coleta de informações, formação e confirmação (ou refutação) de hipóteses, e, no final, a decisão dos problemas é baseada na comunicação com o cliente.
Problema em questão
Hoje temos uma história com um bom final. Uma das razões para o sucesso da resolução do caso proposto é uma descrição muito detalhada e precisa do problema. Abaixo você confere uma cópia do primeiro ticket (editado para ocultar informações confidenciais):
Esta mensagem contém muitas informações úteis para nós:
VM específica especificada
O problema em si é indicado - DNS não funciona
É indicado onde o problema se manifesta – VM e container
As etapas que o usuário seguiu para identificar o problema são indicadas.
A solicitação foi registrada como “P1: Impacto Crítico - Serviço Inutilizável em produção”, o que significa monitoramento constante da situação 24 horas por dia, 7 dias por semana de acordo com o esquema “Follow the Sun” (você pode ler mais sobre prioridades de solicitações do usuário), com sua transferência de uma equipe de suporte técnico para outra a cada mudança de fuso horário. Na verdade, quando o problema chegou à nossa equipa em Zurique, já tinha circulado pelo mundo. A essa altura, o usuário já havia tomado medidas de mitigação, mas temia que a situação se repetisse na produção, pois a causa raiz ainda não havia sido descoberta.
Quando a passagem chegou a Zurique, já tínhamos em mãos as seguintes informações:
Com esses dados, estávamos prontos para iniciar a fase de “investigação” e solução de problemas.
Nossos primeiros passos
Em primeiro lugar, verificamos os logs e o status do servidor de metadados e nos certificamos de que estava funcionando corretamente. O servidor de metadados responde ao endereço IP 169.254.169.254 e, entre outras coisas, é responsável por controlar os nomes de domínio. Também verificamos se o firewall funciona corretamente com a VM e não bloqueia pacotes.
Foi algum tipo de problema estranho: a verificação do nmap refutou nossa hipótese principal sobre a perda de pacotes UDP, então criamos mentalmente mais algumas opções e maneiras de verificá-los:
Os pacotes são descartados seletivamente? => Verifique as regras do iptables
Não é muito pequeno? MTU? => Verifique a saída ip a show
O problema afeta apenas pacotes UDP ou TCP também? => Vá embora dig +tcp
Os pacotes gerados pelo dig são retornados? => Vá embora tcpdump
O libdns está funcionando corretamente? => Vá embora strace para verificar a transmissão de pacotes em ambas as direções
Aqui decidimos ligar para o usuário para solucionar problemas ao vivo.
Durante a ligação podemos verificar várias coisas:
Após várias verificações, excluímos as regras do iptables da lista de motivos
Verificamos interfaces de rede e tabelas de roteamento e verificamos novamente se o MTU está correto
Nós descobrimos que dig +tcp google.com (TCP) funciona como deveria, mas dig google.com (UDP) não funciona
Tendo ido embora tcpdump enquanto trabalho dig, descobrimos que os pacotes UDP estão sendo retornados
Nós vamos embora strace dig google.com e vemos como dig chama corretamente sendmsg() и recvms(), porém o segundo é interrompido por um timeout
Infelizmente, chega o fim do turno e somos obrigados a escalar o problema para o próximo fuso horário. A solicitação, porém, despertou interesse em nossa equipe, e um colega sugeriu a criação do pacote DNS inicial usando o módulo scrapy Python.
from scapy.all import *
answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())
Este fragmento cria um pacote DNS e envia a solicitação ao servidor de metadados.
O usuário executa o código, a resposta do DNS é retornada e a aplicação a recebe, confirmando que não há problema no nível da rede.
Depois de mais uma “viagem ao redor do mundo”, a solicitação retorna para nossa equipe, e eu a transfiro completamente para mim, pensando que será mais conveniente para o usuário se a solicitação parar de circular de um lugar para outro.
Enquanto isso, o usuário concorda em fornecer um instantâneo da imagem do sistema. Esta é uma notícia muito boa: a capacidade de testar o sistema sozinho torna a solução de problemas muito mais rápida, pois não preciso mais pedir ao usuário para executar comandos, enviar-me os resultados e analisá-los, posso fazer tudo sozinho!
Meus colegas estão começando a me invejar um pouco. Durante o almoço discutimos a conversão, mas ninguém tem ideia do que está acontecendo. Felizmente, o próprio usuário já tomou medidas para mitigar as consequências e não tem pressa, então temos tempo para dissecar o problema. E como temos uma imagem, podemos fazer os testes que nos interessam. Ótimo!
Dando um passo para trás
Uma das perguntas de entrevista mais populares para cargos de engenheiro de sistemas é: “O que acontece quando você faz ping www.google.com? A questão é ótima, pois o candidato precisa descrever tudo, desde o shell até o espaço do usuário, passando pelo kernel do sistema e depois pela rede. Eu sorrio: às vezes as perguntas da entrevista acabam sendo úteis na vida real...
Decido aplicar esta questão de RH a um problema atual. Grosso modo, quando você tenta determinar um nome DNS, acontece o seguinte:
O aplicativo chama uma biblioteca de sistema como libdns
libdns verifica a configuração do sistema com qual servidor DNS ele deve entrar em contato (no diagrama é 169.254.169.254, servidor de metadados)
libdns usa chamadas de sistema para criar um soquete UDP (SOKET_DGRAM) e enviar pacotes UDP com uma consulta DNS em ambas as direções
Através da interface sysctl você pode configurar a pilha UDP no nível do kernel
O kernel interage com o hardware para transmitir pacotes pela rede através da interface de rede
O hipervisor captura e transmite o pacote para o servidor de metadados ao entrar em contato com ele
O servidor de metadados, por sua magia, determina o nome DNS e retorna uma resposta usando o mesmo método
Deixe-me lembrá-lo de quais hipóteses já consideramos:
Hipótese: bibliotecas quebradas
Teste 1: execute strace no sistema, verifique se dig chama as chamadas de sistema corretas
Resultado: chamadas de sistema corretas são chamadas
Teste 2: usando srapy para verificar se podemos determinar nomes ignorando as bibliotecas do sistema
Resultado: podemos
Teste 3: execute rpm –V no pacote libdns e nos arquivos da biblioteca md5sum
Resultado: o código da biblioteca é completamente idêntico ao código do sistema operacional em funcionamento
Teste 4: monte a imagem do sistema raiz do usuário em uma VM sem esse comportamento, execute chroot, veja se o DNS funciona
Resultado: DNS funciona corretamente
Conclusão baseada em testes: o problema não está nas bibliotecas
Hipótese: Há um erro nas configurações de DNS
Teste 1: verifique o tcpdump e veja se os pacotes DNS são enviados e retornados corretamente após executar o dig
Resultado: os pacotes são transmitidos corretamente
Teste 2: verificação dupla no servidor /etc/nsswitch.conf и /etc/resolv.conf
Resultado: está tudo correto
Conclusão baseada em testes: o problema não está na configuração do DNS
Hipótese: núcleo danificado
Teste: instale o novo kernel, verifique a assinatura, reinicie
Resultado: comportamento semelhante
Conclusão baseada em testes: o kernel não está danificado
Hipótese: comportamento incorreto da rede do usuário (ou interface de rede do hipervisor)
Teste 1: verifique as configurações do firewall
Resultado: o firewall passa pacotes DNS no host e no GCP
Teste 2: interceptar o tráfego e monitorar a exatidão da transmissão e retorno das solicitações DNS
Resultado: tcpdump confirma que o host recebeu pacotes de retorno
Conclusão baseada em testes: o problema não está na rede
Hipótese: o servidor de metadados não está funcionando
Teste 1: verifique se há anomalias nos logs do servidor de metadados
Resultado: não há anomalias nos logs
Teste 2: Ignore o servidor de metadados via dig @8.8.8.8
Resultado: a resolução é quebrada mesmo sem usar um servidor de metadados
Conclusão baseada em testes: o problema não está no servidor de metadados
A linha inferior: testamos todos os subsistemas, exceto configurações de tempo de execução!
Mergulhando nas configurações de tempo de execução do kernel
Para configurar o ambiente de execução do kernel, você pode usar opções de linha de comando (grub) ou a interface sysctl. Eu olhei para dentro /etc/sysctl.conf e pense só, descobri várias configurações personalizadas. Sentindo-me como se tivesse agarrado algo, descartei todas as configurações que não fossem de rede ou não TCP, permanecendo com as configurações de montanha net.core. Depois fui até onde estavam as permissões do host na VM e comecei a aplicar as configurações uma por uma, uma após a outra, com a VM quebrada, até encontrar o culpado:
net.core.rmem_default = 2147483647
Aqui está, uma configuração de quebra de DNS! Encontrei a arma do crime. Mas por que isso está acontecendo? Eu ainda precisava de um motivo.
O tamanho básico do buffer do pacote DNS é configurado via net.core.rmem_default. Um valor típico é algo em torno de 200 KiB, mas se o seu servidor receber muitos pacotes DNS, você pode querer aumentar o tamanho do buffer. Se o buffer estiver cheio quando um novo pacote chegar, por exemplo, porque o aplicativo não o está processando rápido o suficiente, você começará a perder pacotes. Nosso cliente aumentou corretamente o tamanho do buffer porque tinha medo de perda de dados, pois estava utilizando um aplicativo para coleta de métricas por meio de pacotes DNS. O valor que ele definiu foi o máximo possível: 231-1 (se definido como 231, o kernel retornará “ARGUMENTO INVÁLIDO”).
De repente, percebi porque o nmap e o scapy funcionavam corretamente: eles estavam usando soquetes brutos! Os soquetes brutos são diferentes dos soquetes normais: eles ignoram o iptables e não são armazenados em buffer!
Mas por que “buffer muito grande” causa problemas? Claramente não funciona como pretendido.
Neste ponto eu poderia reproduzir o problema em vários kernels e múltiplas distribuições. O problema já apareceu no kernel 3.x e agora apareceu também no kernel 5.x.
Na verdade, na inicialização
sysctl -w net.core.rmem_default=$((2**31-1))
O DNS parou de funcionar.
Comecei a procurar valores funcionais por meio de um algoritmo de busca binária simples e descobri que o sistema funcionava com 2147481343, mas esse número era um conjunto de números sem sentido para mim. Sugeri ao cliente que tentasse esse número e ele respondeu que o sistema funcionava com google.com, mas ainda apresentava erro com outros domínios, então continuei minha investigação.
eu instalei dropwatch, uma ferramenta que deveria ter sido usada anteriormente: ela mostra exatamente onde um pacote termina no kernel. O culpado foi a função udp_queue_rcv_skb. Eu baixei os fontes do kernel e adicionei alguns funçõesprintk para rastrear exatamente onde o pacote termina. Eu rapidamente encontrei a condição certa if, e simplesmente fiquei olhando para ele por algum tempo, porque foi então que tudo finalmente se juntou em uma imagem completa: 231-1, um número sem sentido, um domínio que não funciona... Era um pedaço de código em __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;
Por favor, note:
rmem é do tipo int
size é do tipo u16 (int não assinado de dezesseis bits) e armazena o tamanho do pacote
sk->sk_rcybuf é do tipo int e armazena o tamanho do buffer que, por definição, é igual ao valor em net.core.rmem_default
Quando sk_rcvbuf se aproxima de 231, somar o tamanho do pacote pode resultar em estouro de número inteiro. E como é um int, seu valor se torna negativo, então a condição se torna verdadeira quando deveria ser falsa (você pode ler mais sobre isso em link).
O erro pode ser corrigido de uma forma trivial: lançando unsigned int. Apliquei a correção e reiniciei o sistema e o DNS funcionou novamente.
Sabor da vitória
Encaminhei minhas descobertas ao cliente e enviei LKML patch do kernel. Estou satisfeito: cada peça do quebra-cabeça se encaixa, posso explicar exatamente por que observamos o que observamos e, o mais importante, conseguimos encontrar uma solução para o problema graças ao nosso trabalho em equipe!
É importante reconhecer que o caso acabou sendo raro e, felizmente, raramente recebemos solicitações tão complexas dos usuários.