quarta-feira, 16 de maio de 2007

A saga do /net/bin

Uma das coisas mais divertidas da vida de um administrador de sistemas é resolver um problema difícil. É ainda mais legal quando o problema é antigo e você já tentou resolvê-lo várias vezes sem sucesso. Daí, quando você consegue finalmente vencê-lo, a satisfação é enorme.

Hoje tive um desses momentos memoráveis. Tanto assim que ao invés de simplesmente mandar um email para meus colegas de trabalho resolvi relatar a solução aqui e só mandar o link pra eles. Assim eles vão poder ter razão quando disserem que o Gustavo está "se achando". :-)

Pois bem. O mistério que nos estava deixando encucados há alguns meses eram as mensagens freqüentes que apareciam no /var/log/messages de vários servidores Linux:

May 13 16:05:02 noname automount[7655]: lookup(program): lookup for bin failed
May 13 16:05:02 noname automount[7655]: failed to mount /net/bin

Essas mensagens apareciam uma ou duas vezes por minuto, durante o dia todo, e indicavam que um ou mais processos estavam periodicamente tentando acessar o diretório /net/bin que não existe. Como o /net é a base de um mapa indireto do automounter, as falhas eram reportadas por ele via syslog.

O problema que não conseguíamos resolver era como identificar os processos que estavam acessando o diretório inexistente para podermos corrigi-los. Os logs do automounter não davam nenhuma pista útil, de modo que precisávamos de uma abordagem criativa.

O fato de os logs terem uma freqüência regular sugeria que os processos causadores pudessem estar sendo ativados via crontab. Só que essas máquinas são servidoras e têm dezenas de usuários com crontabs definidos. Os logs do /var/log/cron indicavam que grande parte dos crontabs iniciava comandos a cada minuto, de modo que não dava pra investigar um ou outro script especificamente.

Tentei pegar o culpado rastreando as chamadas de sistema do daemon crond e procurando por referências ao diretório /net/bin.

# strace -f -e trace=file -o/tmp/crond.strace -p `pidof crond`

Infelizmente, não encontrei nada, o que parecia um forte indício de que não se tratava mesmo de um processo de cron, mas eu não fiquei convencido.

Eu já tinha lido alguma coisa a respeito de ferramentas para monitorar o acesso a arquivos no Linux. Com o Google foi fácil encontrar o venerável dnotify e seu colega mais novo inotify. O dnotify permite monitorar o acesso aos arquivos de um diretório e o inotify é bem mais flexível. Só que o inotify não é suportado pela versão do kernel do servidor e eu desisti de instalar o dnotify pois provavelmente o tipo de acesso que os processos estavam fazendo não seria detectável mesmo por ele.

Uma idéia que parecia promissora era procurar por processos que tivessem o diretório /net/bin presente na sua variável de ambiente PATH. Podia ser que estes processos estivessem acessando o /net/bin como resultado do processo de busca de algum comando no PATH. Felizmente, ter a idéia foi mais difícil que implementá-la:

for e in /proc/[0-9]*/environ
do echo -n "$e: "; tr '\0' '\012' <$e | grep PATH
done | grep /net/bin


Procurar nos quase 3.000 processos do servidor demorou alguns segundos mas não devolveu resultado algum. Ou o acesso não era por causa do PATH ou os processos tinham vida curta e eu não consegui acertar em nenhum deles com esse tiro.

A próxima idéia foi importante, pois me fez descobrir algumas pistas e abriu as portas para a idéia seguinte. Se os logs eram gerados porque o /net/bin não existia, talvez eu pudesse ver o resultado do acesso se eu o criasse. Exportei um diretório vazio de outra máquina e configurei o automounter para montá-lo como /net/bin na servidora. Não demorou pra eu ver que ele tinha sido montado e que as mensagens de erro pararam. Já era um avanço.

A vantagem é que agora qualquer acesso ao /net/bin deixaria um rastro de pacotes NFS na rede. Trabalho para um analisador de pacotes como o ethereal. Alguns minutos de captura e eu já tinha uma coleção razoável de pacotes NFS pra investigar. Consegui descobrir duas coisas. A primeira, promissora, foi que havia várias tentativas de acesso ao arquivo /net/bin/gawk. A segunda, ruim, foi que estes acessos eram feitos por vários usuários diferentes. Eu podia identificá-los, pois suas credenciais estavam registradas nos campos RPC dos pacotes NFS.

Daí eu pensei: se eles estão procurando o /net/bin/gawk eu vou criar um que me avise quando for invocado. Criei um script chamado gawk no diretório montado como /net/bin. O script iria registrar várias informações sobre seu chamador num arquivo e dormir por 10 minutos, de modo que eu poderia identificá-lo mais facilmente. Preparei a armadilha e fiquei esperando... Nada aconteceu. Por algum motivo os processos não estavam "chamando" o gawk falso que eu havia criado. Eu não conseguia entender porque é que algum processo procuraria o gawk se não era para chamá-lo.

Mas a idéia de forçar o travamento dos processos que estavam acessando o /net/bin era muito boa. Tinha que haver uma maneira de poder efetivá-la. A ficha caiu quando eu me lembrei da causa de um travamento recente no mesmo servidor: um servidor de NFS fora do ar. Agora que eu já tinha o /net/bin montado de um servidor NFS, bastava eu desativar o serviço remoto de NFS pra que os processos que quisessem acessar o diretório ficassem suspensos num estado de uninterruptible sleep. Tive um certo receio, pois esses travamentos devidos a problemas com servidores NFS costumam deixar os servidores num estado em que só é possível ressuscitá-los com um reboot. Ocorre que os processos que param pelo problema com o NFS podem causar a parada de outros processos que dependam deles e assim sucessivamente. Mas como eu podia restabelecer o serviço de NFS rapidamente deveria ser possível sair de qualquer enrascada.

Parei o NFS e fiquei analisando o número de processos que iam parando no estado "D":

# ps aux | awk '$8 ~ /D/'

Pelo que me lembro, o número de processos assim chegou perto dos 400 em poucos minutos. Joguei o resultado num arquivo e reativei o NFS. Os processos travados voltaram a rodar e eu pude analisar o arquivo com calma. O que eu vi nele não fez muito sentido a princípio. Todos os processos parados ou eram instâncias de uma shell invocando o /net/gnu/bin/gawk ou o /net/gnu/bin/awk:

/bin/bash /net/gnu/bin/gawk ...

Preciso explicar que o /net/gnu/bin é onde, historicamente, costumamos instalar as ferramentas livres do Projeto GNU nas nossas máquinas Solaris. Quando começamos a instalar servidores Linux há quase 10 anos o modelo do /net/gnu/bin não era necessário em princípio. Afinal, os sistemas Linux já vêm com todas as ferramentas do Projeto GNU instaladas nos diretórios padrão /usr/bin e /bin. Mas como o diretório /net/gnu/bin já era referenciado por uma quantidade enorme de scripts e binários antigos, pra evitar o trabalho de mexer em tudo isso alguém teve a brilhante idéia de configurar o automounter das máquinas Linux para montar o diretório local /usr/bin das máquinas como /net/gnu/bin sempre que algum processo fizesse referência explícita ao último. (Nessas horas não adianta muito ser purista. Uma solução porca e torta que "resolva o problema" é considerada boa o suficiente. Ahem.)

Tudo bem. Que o /net/gnu/bin/gawk era mesmo o /usr/bin/gawk eu sabia, mas o que é que ele tinha a ver com o inexistente /net/bin/gawk? Por que é que ao chamar o /net/gnu/bin/gawk a shell travaria se o /net/bin estivesse com o servidor NFS fora?

Isso não fazia nenhum sentido até que eu dei uma olhada de perto no dito cujo:

# ls -l /usr/bin/gawk
lrwxrwxrwx 1 root root 14 Dez 15 2005 /usr/bin/gawk -> ../../bin/gawk

Um link simbólico!?!? Pra que raios o gawk é um link simbólico? Ah... o /usr/bin/gawk está apontando para o /bin/gawk, que é onde o binário do comando está efetivamente instalado.

Cáspita! Matei a charada. Como o link simbólico é relativo, o /usr/bin/gawk aponta para o /bin/gawk mas o /net/gnu/bin/gawk acaba apontando para o /net/bin/gawk!!!

Isso explicava o fato de eu não ter encontrado nenhuma referência direta ao /net/bin com o strace: o PATH da shell tinha uma referência ao /net/gnu/bin e não ao /net/bin. O travamento ocorreu dentro do sistema operacional, na chamada ao exec, quando ele foi seguir o link simbólico.

Eu sabia que um dia eu teria uma boa razão pra dizer "bem que eu avisei" quando reclamei da gambiarra que foi o /net/gnu/bin no Linux. Agora eu tenho. :-)

Bom, a causa do problema foi descoberta. Resta agora resolvê-lo. Idealmente, os processos que estão tentando acessar o /net/gnu/bin deveriam ser reescritos para não tentarem. Mas algo me diz que a solução vai acabar sendo criar mais uma gambiarra. Que desta vez deverá ser montar o /bin local dos servidores Linux no /net/bin.

6 comentários:

  1. Elementar meu caro Gustavo. Que saga hein? O próximo mistério é evitar que processos entrem no uninterruptible sleep? ;-)

    ResponderExcluir
  2. Não acho que seja possível. Este é um estado em que o kernel coloca o processo quando ele espera algum evento específico e o kernel não está preparado para interrompê-lo antes que o evento ocorra. Não é um erro. É uma característica do kernel que provavelmente seja difícil de ser alterada.

    Mais em http://linuxgazette.net/issue83/tag/6.html.

    ResponderExcluir
  3. Gustavo, você está se achando mesmo! :-)

    Mas pode se achar porque esse problema não era para qualquer um.

    ResponderExcluir
  4. Oi Gugs

    Cheguei aqui graças ao podcaster João Gandara:-)
    Esse lance do /net e da migração Solaris->Linux foi um problemâo que eu enfrentei tempos atrás quando estava na UNICAMP.
    Resolvi reescrevendo os scripts mesmo.

    ResponderExcluir
  5. Caraca, tive que ler umas 6 vezes pra entender tudo que vc disse !!! hehehehe.

    Mais uma vez voltamos com aquela frase, que já virou um jargão aqui na TI: O Gustavão é foda !!!

    Não é pra qq um mesmo.

    []'s

    ResponderExcluir
  6. Oi, Benes, quanto tempo, não?

    Pois é... você tomou a atitude mais correta e mais segura a longo prazo.

    Sobre o Papotech, os caras estão indo muito bem, não? O Vinicius me contou da apresentação do trabalho sobre podcasts em que o professor já era ouvinte. Quando eu soube que o tal professor era você ficou ainda mais engraçado. :-)

    ResponderExcluir