One place for hosting & domains

      Como instalar e utilizar a ferramenta Radamsa para fazer teste de fuzzing em programas e serviços de rede no Ubuntu 18.04


      O autor selecionou a Electronic Frontier Foundation, Inc para receber uma doação como parte do programa Write for DOnations.

      Introdução

      As ameaças de segurança estão se tornando cada vez mais sofisticadas, de modo que os desenvolvedores e administradores de sistemas precisam adotar uma abordagem proativa na defesa e teste da segurança de seus aplicativos.

      Um método comum para testar a segurança dos aplicativos cliente ou serviços de rede é o fuzzing, que envolve a transmissão de dados inválidos ou malformados repetidamente para o aplicativo e analisando sua resposta. Isso é útil para ajudar a testar a resiliência e a robustez do aplicativo a entradas inesperadas, que podem incluir dados corrompidos ou ataques reais.

      A Radamsa é uma ferramenta de fuzzing de código aberto que pode gerar casos de teste baseados em dados de entrada específicos do usuário. A Radamsa é totalmente passível de script e, até agora, tem sido bem-sucedida em encontrar vulnerabilidades em aplicativos reais, como o Gzip.

      Neste tutorial, você instalará e usará a Radamsa para fazer o teste de fuzzing da linha de comando e dos aplicativos baseados em rede, usando seus próprios casos de teste.

      Aviso: a Radamsa é uma ferramenta de teste de penetração que pode permitir que você identifique as vulnerabilidades ou pontos fracos em certos sistemas ou aplicativos. Você não deve usar as vulnerabilidades encontradas com a Radamsa para qualquer forma de comportamento inconsequente, exploração prejudicial ou mal-intencionada. As vulnerabilidades devem ser relatadas para o mantenedor do aplicativo afetado, de maneira ética e não ser divulgadas publicamente, sem permissão expressa.

      Pré-requisitos

      Antes de iniciar este guia, será necessário o seguinte:

      • Um servidor Ubuntu 18.04 configurado de acordo com o tutorial de Configuração Inicial do servidor com o Ubuntu 18.04, incluindo um usuário não raiz com privilégios sudo e firewall habilitado para bloquear portas não essenciais.
      • Um aplicativo de linha de comando ou baseado em rede que deseja testar, como o Gzip, Tcpdumb, Bind, Apache, jq ou qualquer outro aplicativo de sua escolha. A título de exemplo para este tutorial,usaremos o jq.

      Aviso: a Radamsa pode provocar a instabilidade ou a falha dos aplicativos ou sistemas em execução. Assim, somente execute a Radamsa em um ambiente em que você esteja preparado para essa situação, como num servidor dedicado, por exemplo. Além disso, assegure-se de que possui a devida permissão escrita do proprietário de um sistema, antes de realizar o teste de fuzzing em tal sistema.

      Assim que tiver tudo pronto, logue no seu servidor como usuário não raiz para começar.

      Passo 1 — Instalando a Radamsa

      Primeiro, você irá baixar e compilar a Radamsa para começar a usá-la no seu sistema. O código fonte da Radamsa está disponível no repositório oficial no GitLab.

      Comece atualizando o índice de pacotes local para refletir quaisquer novas alterações dos autores do código:

      Em seguida, instale os pacotes gcc, git, make e wget. Eles são necessários para compilar o código fonte em um binário executável:

      • sudo apt install gcc git make wget

      Após confirmar a instalação, a ferramenta apt irá baixar e instalar os pacotes especificados e todas as dependências necessárias.

      Em seguida, você irá baixar uma cópia do código fonte da Radamsa, clonando-o a partir do repositório hospedado no GitLab:

      • git clone https://gitlab.com/akihe/radamsa.git

      Isso criará um diretório chamado radamsa, que contém o código fonte do aplicativo. Vá para o diretório para começar a compilar o código:

      Em seguida, inicie o processo de compilação usando o make:

      Por fim, instale o binário da Radamsa compilado para o seu $PATH:

      Assim que terminar, verifique a versão instalada para garantir que tudo está funcionando:

      Sua saída será semelhante à seguinte:

      Output

      Radamsa 0.6

      Caso veja um erro radamsa: command not found (comando não encontrado), verifique novamente se todas as dependências necessárias foram instaladas e se não houve erros durante a compilação.

      Agora que instalou a Radamsa, comece a gerar alguns exemplos de casos para teste, a fim de entender como a Radamsa funciona e para o que ela pode ser usada.

      Passo 2 — Gerando casos para o teste de fuzzing

      Agora que a Radamsa foi instalada, utilize-a para gerar alguns casos para o teste de fuzzing.

      Um caso de teste é uma fração de dados que será usada como entrada para o programa que você estiver testando. Por exemplo, se estiver fazendo o teste de fuzzing em um programa de arquivamento como o Gzip. Como caso para teste, você pode usar um arquivo morto que estiver tentando descompactar.

      Nota: a Radamsa manipulará os dados de entrada de diversas formas inesperadas, inclusive através de repetição extrema, bit flips [manipulação de bits], controle de injeção de caractere e assim por diante. Isso pode provocar a interrupção ou a instabilidade de sua sessão no terminal; portanto, esteja ciente disso, antes de continuar.

      Primeiro, envie uma mensagem de texto simples para a Radamsa para ver o que acontece:

      • echo "Hello, world!" | radamsa

      Isso manipulará (ou confundirá) os dados inseridos e dará como resultado um caso de teste, por exemplo:

      Output

      Hello,, world!

      Neste caso, a Radamsa adicionou uma vírgula extra entre o Hello e o world. Pode não parecer uma alteração significativa, mas, em alguns aplicativos, pode levar à interpretação incorreta dos dados.

      Vamos tentar novamente, executando o mesmo comando. Você verá um outro resultado:

      Output

      Hello, '''''''wor'd!

      Dessa vez, várias aspas (') foram inseridas na string, incluindo uma que sobrescreveu-e à letra l da palavra world. Esse caso de teste em particular traz maior probabilidade de gerar problemas para um aplicativo, uma vez que as aspas simples/duplas são usadas com frequência para separar diferentes frações de dados em uma lista.

      Vamos testar mais uma vez:

      Output

      Hello, $+$PATHu0000`xcalc`world!

      Nesse caso, a Radamsa inseriu uma string de injeção do shell, o que será útil para testar as vulnerabilidades da injeção de comandos no aplicativo que você está testando.

      Você usou a Radamsa para fazer fuzz com uma string de entrada e produzir uma série de casos de teste. Em seguida, você usará a Radamsa para fazer o fuzz em um aplicativo de linha de comando.

      Passo 3 — Fazendo o fuzzing em um aplicativo de linha de comando

      Neste passo, você utilizará o Radamsa para fazer o fuzz em um aplicativo de linha de comando e informará sobre quaisquer falhas que ocorrerem.

      A técnica exata para o fuzzing em cada programa varia significativamente e métodos distintos serão mais eficazes para programas distintos. No entanto, neste tutorial utilizaremos o exemplo do jq, que é um programa de linha de comando para o processamento de dados JSON.

      Você pode usar qualquer outro programa similar, desde que ele siga o princípio geral de tomar alguma forma de dados estruturados ou não estruturados, fazendo algo com eles e em seguida, exibindo um resultado. Por exemplo, esse exemplo também funcionaria com o Gzip, Grep, bc, tr e assim por diante.

      Se ainda não tiver o jq instalado, instale-o usando a apt:

      O jq será instalado agora.

      Para começar o fuzzing, crie um arquivo de exemplo que você usará como a entrada para a Radamsa:

      Então, adicione os seguintes dados de exemplo em JSON ao arquivo:

      test.json

      {
        "test": "test",
        "array": [
          "item1: foo",
          "item2: bar"
        ]
      }
      

      Você pode analisar esse arquivo usando o jq caso queria verificar se a sintaxe JSON é válida:

      Se o JSON for válido, o jq exibirá o arquivo. Caso contrário, ele exibirá um erro, que você pode usar para corrigir a sintaxe, onde for necessário.

      Em seguida, faça o fuzz no arquivo de teste em JSON usando a Radamsa e, em seguida, envie-o para o jq. Isso fará com que o jq leia o caso de teste de fuzzing/para manipulação, em vez de dados originais JSON válidos:

      Se a Radamsa fizer o fuzzing dos dados JSON, de modo que ainda fiquem válidos – do ponto de vista sintático, o jq exibirá os dados, mas com as alterações que a Radamsa tiver feito neles.

      Como alternativa, se a Radamsa fizer com que os dados JSON se tornem inválidos, o jq exibirá um erro relevante. Por exemplo:

      Output

      parse error: Expected separator between values at line 5, column 16

      O resultado alternativo seria que o jq não conseguiria lidar corretamente com os dados que passaram por fuzzing, fazendo com que ele falhe ou que se comporte indevidamente. É precisamente isso o que você estará buscando alcançar com o fuzzing, uma vez que isso pode ser indicação de uma vulnerabilidade de segurança, tais como um estouro de buffer ou uma injeção de comando.

      Para testar as vulnerabilidades de maneira mais eficaz dessa maneira, um script Bash pode ser usado para automatizar o processo de fuzzing, incluindo a geração de casos de teste, passando-os para o programa alvo e capturando qualquer resultado relevante.

      Crie um arquivo chamado jq-fuzz.sh:

      O conteúdo exato do script irá variar, dependendo do tipo de programa em que você estiver fazendo o fuzzing e os dados de entrada. No caso do jq e de outros programas semelhantes, porém, o script a seguir será o suficiente.

      Copie o script em seu arquivo jq-fuzz.sh:

      jq-fuzz.sh

      #!/bin/bash
      while true; do
        radamsa test.json > input.txt
        jq . input.txt > /dev/null 2>&1
        if [ $? -gt 127 ]; then
          cp input.txt crash-`date +s%.%N`.txt
          echo "Crash found!"
        fi
      done
      

      Esse script contém um while para fazer o conteúdo entrar em um loop, repetidas vezes. A cada vez que o script entrar em loop, a Radamsa gerará um caso de teste, com base no test.json e o salvará no arquivo input.txt.

      Na sequência, o caso de teste, input.txt, será executado com o programa jq e todo o resultado – padrão e de erros – será redirecionado para o diretório /dev/null, a fim de evitar que a tela do terminal fique cheia.

      Por fim, o valor de saída do jq é verificado. Se o valor de saída for maior que 127, isso indica que houve um encerramento fatal (uma falha); em seguida, os dados de entrada são salvos – para revisão posterior – em um arquivo chamado crash-, seguido de carimbo de data do Unix, com a data atual em segundos e nanossegundos.

      Marque o script como executável e o coloque em funcionamento, de modo a iniciar o teste de fuzzing do jq, automaticamente:

      • chmod +x jq-fuzz.sh
      • ./jq-fuzz.sh

      Você pode teclar o CTRL+C a qualquer momento para terminar o script. Então, você pode verificar se foram encontradas falhas com o ls, a fim de exibir uma listagem de diretório, contendo quaisquer arquivos de falha que tiverem sido criados.

      Você pode querer melhorar seus dados de entrada JSON, uma vez que o uso de um arquivo de entrada mais complexo provavelmente melhoraria a qualidade de seus resultados de fuzzing. Evite utilizar um arquivo grande ou que contenha muitos dados repetidos—um arquivo pequeno seria a entrada ideal, mas que contenha o máximo de elementos ‘complexos’ possível. Por exemplo, um bom arquivo de entrada conterá amostras de dados armazenados em todos formatos, que incluem strings, números inteiros, booleanos, listas e objetos, bem como dados aninhados, sempre que possível.

      Você usou a Radamsa para fazer o fuzzing em um aplicativo de linha de comando. Em seguida, você usará a Radamsa para fazer o fuzz em serviços de rede.

      Passo 4 — Pedidos de fuzzing em serviços de rede

      A Radamsa também pode ser usada para fazer o fuzzing em serviços de rede, seja atuando como um cliente ou servidor de rede. Neste passo, você usará a Radamsa para fazer o fuzzing em um serviço de rede, com a Radamsa atuando como cliente.

      O objetivo do fuzzing em serviços de rede é testar o quão resiliente um determinado serviço de rede é para os clientes, enviando-lhe dados malformados ou mal-intencionados. Muitos serviços de rede – como servidores Web ou DNS – são geralmente expostos à Internet, o que significa que eles são um alvo comum para invasores. Um serviço de rede que não seja resistente o suficiente para receber dados malformados pode falhar ou, pior ainda, falhar em um estado aberto, o que permite que invasores leiam dados confidenciais, como chaves de criptografia ou dados do usuário.

      A técnica específica para o fuzzing em serviços de rede varia bastante, dependendo do serviço de rede em questão. No entanto, neste exemplo usaremos a Radamsa para fazer o fuzz em um servidor Web básico, servindo conteúdo de HTML estático.

      Primeiro, você precisa configurar o servidor Web para testes. Você pode fazer isso usando o servidor de desenvolvimento integrado que vem com o pacote php-cli. Você também precisará do curl para testar seu servidor Web.

      Se não tiver o php-cli e/ou o curl instalados, instale-os usando a ferramenta apt:

      • sudo apt install php-cli curl

      Em seguida, crie um diretório para armazenar seus arquivos do servidor Web e vá até ele:

      Então, crie um arquivo HTML que contenha uma amostra de texto:

      Adicione o que vem a seguir ao arquivo:

      index.html

      <h1>Hello, world!</h1>
      

      Agora, você pode executar seu servidor Web do PHP. Você precisará ser capaz de visualizar o registro do servidor Web, ao mesmo tempo que ainda continuará a usar outra sessão de terminal. Dessa maneira, abra outra sessão de terminal e de SSH para o seu servidor para este fim:

      • cd ~/www
      • php -S localhost:8080

      Isso irá gerar algo parecido com o seguinte:

      Output

      PHP 7.2.24-0ubuntu0.18.04.1 Development Server started at Wed Jan 1 16:06:41 2020 Listening on http://localhost:8080 Document root is /home/user/www Press Ctrl-C to quit.

      Agora, você pode retornar para a sessão de terminal original e testar se o servidor Web está funcionando, usando o curl:

      Isso exibirá o arquivo de exemplo index.html que você criou anteriormente:

      Output

      <h1>Hello, world!</h1>

      Seu servidor Web precisa estar acessível apenas localmente. Assim, você não deve abrir quaisquer portas no seu firewall para ele.

      Agora que você configurou seu servidor Web de teste, você pode iniciar o teste de fuzzing nele, usando a Radamsa.

      Primeiro, será necessário criar um exemplo de pedido de HTTP para usar como dados de entrada para a Radamsa. Crie um novo arquivo para armazenar isso em:

      Então, copie a seguinte amostra de pedido HTTP para o arquivo:

      http-request.txt

      GET / HTTP/1.1
      Host: localhost:8080
      User-Agent: test
      Accept: */*
      

      Em seguida, utilize a Radamsa para enviar esse pedido HTTP para o seu servidor Web local. Para fazer isso, você terá que usar a Radamsa como cliente TCP, o que pode ser feito especificando-se um endereço IP e porta à qual se conectar:

      • radamsa -o 127.0.0.1:8080 http-request.txt

      Nota: esteja ciente que usar a Radamsa como um cliente TCP possivelmente fará com que dados malformados/mal-intencionados sejam transmitidos pela rede. Isso pode interromper as coisas. Assim, tenha o cuidado de acessar somente as redes que você está autorizado a testar ou, de preferência, continue usando o endereço do localhost (127.0.0.1).

      Por fim, caso visualize os registros produzidos em relação ao seu servidor Web local, verá que ele recebeu os pedidos, mas provavelmente não os processou, já que eles eram inválidos/malformados.

      Os registros produzidos estarão visíveis em sua segunda janela de terminal:

      Output

      [Wed Jan 1 16:26:49 2020] 127.0.0.1:49334 Invalid request (Unexpected EOF) [Wed Jan 1 16:28:04 2020] 127.0.0.1:49336 Invalid request (Malformed HTTP request) [Wed Jan 1 16:28:05 2020] 127.0.0.1:49338 Invalid request (Malformed HTTP request) [Wed Jan 1 16:28:07 2020] 127.0.0.1:49340 Invalid request (Unexpected EOF) [Wed Jan 1 16:28:08 2020] 127.0.0.1:49342 Invalid request (Malformed HTTP request)

      Para obter resultados ideais e garantir que as falhas fiquem registradas, talvez você queira escrever um script de automação semelhante ao usado no Passo 3. Você deve considerar, ainda, o uso de um arquivo de entrada mais complexo, que possa conter adições, como a função de cabeçalhos HTTP extras.

      Você fez o fuzzing em um serviço de rede usando a Radamsa, que agiu como um cliente TCP. Em seguida você fará o fuzzing em um cliente de rede com a Radamsa atuando como um servidor.

      Passo 5 — Fazendo o fuzzing em aplicativos clientes de rede

      Neste passo, você usará a Radamsa para fazer o teste de fuzzing em um aplicativo cliente de rede. Isso pode ser feito através da interceptação das respostas de um serviço de rede e do fuzzing delas, antes de serem recebidas pelo cliente.

      O objetivo deste tipo de fuzzing é testar o quão resilientes os aplicativos cliente de rede são para receberem dados malformados ou mal-intencionados, vindos de serviços de rede. Por exemplo, testar um navegador Web (cliente) recebendo um HTML malformado de um servidor Web (serviço de rede), ou testar um cliente DNS recebendo respostas de DNS malformadas de um servidor DNS.

      Tal como aconteceu com o fuzzing de aplicativos de linha de comando ou de serviços de rede, a técnica exata para o fuzzing em cada aplicativo cliente de rede varia consideravelmente. No entanto, neste exemplo, você usará o whois, que é um aplicativo de envio/recebimento, baseado no protocolo TCP.

      O aplicativo whois é utilizado para fazer pedidos aos servidores WHOIS e receber registros WHOIS como resposta. O WHOIS opera pela porta TCP 43 em texto não criptografado, o que o torna um bom candidato para testes de fuzzing baseados em rede.

      Se ainda não tiver o whois instalado, instale-o usando a ferramenta apt:

      Primeiro, você precisará adquirir uma amostra de resposta do whois para usar como dados de entrada. Para tanto, você pode fazer um pedido whois e salvar o resultado em um arquivo. Aqui, você pode usar qualquer domínio que quiser, uma vez que estará testando o programa whois localmente, usando dados de amostra:

      • whois example.com > whois.txt

      Em seguida, você precisará configurar a Radamsa como um servidor que atenda versões dessa resposta do whois que já passaram pelo fuzzing. Uma vez que a Radamsa está executando em modo de servidor, você terá que conseguir continuar usando o seu terminal. Assim, é recomendável abrir outra sessão de terminal e outra conexão via protocolo SSH com o seu servidor para esse fim:

      • radamsa -o :4343 whois.txt -n inf

      Agora, a Radamsa estará executando em modo de servidor TCP e atenderá uma versão de whois.txt – que já passou por fuzzing – toda vez que uma conexão for feita no servidor, independentemente de quais dados da solicitação forem recebidos.

      Agora, você já pode prosseguir com o teste do aplicativo cliente whois. Você vai precisar fazer uma solicitação whois normal para um domínio de sua escolha (não precisa ser o mesmo para os quais os dados da amostra se destinam), mas com o whois apontado para o seu servidor local da Radamsa:

      • whois -h localhost:4343 example.com

      A resposta consistirá nos dados da sua amostra, mas que já passaram pelo processo de fuzzing da Radamsa. Desde que a Radamsa esteja em execução, você pode continuar a fazer pedidos para o servidor local, pois toda vez ele irá fornecer uma resposta diferente que já passou pelo fuzzing.

      Assim como ocorre no fuzzing em serviços de rede, para melhorar a eficiência esse teste de fuzzing do cliente de rede e assegurar que eventuais falhas sejam capturadas, talvez você queira escrever um script de automação semelhante ao usado no Passo 3.

      Neste passo final, você usou a Radamsa para realizar o teste de fuzzing de um aplicativo cliente de rede.

      Conclusão

      Neste artigo, você configurou a Radamsa e a usou para fazer o fuzzing em um aplicativo de linha de comando, um serviço de rede e um cliente de rede. Portanto, você já tem o conhecimento básico necessário para fazer o teste de fuzzing em seus próprios aplicativos, com a expectativa de aprimorar a robustez e a resistência deles a ataques.

      Se quiser explorar a Radamsa ainda mais, faça uma revisão detalhada do arquivo README da Radamsa, pois ele traz outras informações técnicas e exemplos de como a ferramenta pode ser usada:

      Confira também algumas outras ferramentas de fuzzing, como a American Fuzzy Lop (AFL), que é uma ferramenta de fuzzing avançada, desenvolvida para testar aplicativos binários em velocidade e com precisão extremamente elevadas.



      Source link

      Como Construir uma Rede Neural para Reconhecer Dígitos Manuscritos com o TensorFlow


      Introdução

      Redes neurais são usadas como um método de deep learning ou aprendizado profundo, um dos vários subcampos da inteligência artificial. Elas foram propostas pela primeira vez há cerca de 70 anos como uma tentativa de simular a maneira como o cérebro humano funciona, embora de uma forma muito mais simplificada. Os “neurônios” individuais são conectados em camadas, com pesos designados para determinar como o neurônio responde quando os sinais são propagados pela rede. Anteriormente, as redes neurais eram limitadas no número de neurônios que elas eram capazes de simular e, portanto, a complexidade do aprendizado que podiam alcançar. Mas nos últimos anos, devido aos avanços no desenvolvimento de hardware, pudemos construir redes muito profundas e treiná-las em enormes datasets ou conjuntos de dados para obter avanços na inteligência de máquinas.

      Essas inovações permitiram que as máquinas correspondessem e excedessem as capacidades dos humanos em realizar certas tarefas. Uma dessas tarefas é o reconhecimento de objetos. Embora as máquinas tenham sido historicamente incapazes de corresponder à visão humana, avanços recentes em deep learning tornaram possível construir redes neurais capazes de reconhecer objetos, rostos, textos e até mesmo emoções.

      Neste tutorial, você implementará uma pequena subseção de reconhecimento de objeto recognition—digit. Utilizando o TensorFlow, uma biblioteca Python open-source desenvolvida pelos laboratórios do Google Brain para pesquisa em deep learning, você pegará imagens desenhadas à mão dos números de 0 a 9 e construirá e treinará uma rede neural para reconhecer e prever o rótulo correto para o dígito exibido.

      Embora você não precise de experiência prévia em deep learning prático ou de uso do TensorFlow para acompanhar este tutorial, vamos assumir alguma familiaridade com termos e conceitos de machine learning, como treinamento e testes, recursos e rótulos, otimização e avaliação. Você pode aprender mais sobre esses conceitos em Uma Introdução ao Machine Learning.

      Pré-requisitos

      Para completar esse tutorial, você vai precisar de:

      Passo 1 — Configurando o Projeto

      Antes de desenvolver o programa de reconhecimento, você precisará instalar algumas dependências e criar um espaço de trabalho para armazenar seus arquivos.

      Usaremos um ambiente virtual do Python 3 para gerenciar as dependências do nosso projeto. Crie um novo diretório para o seu projeto e navegue até o novo diretório:

      • mkdir tensorflow-demo
      • cd tensorflow-demo

      Execute os seguintes comandos para configurar o ambiente virtual para este tutorial:

      • python3 -m venv tensorflow-demo
      • source tensorflow-demo/bin/activate

      Em seguida, instale as bibliotecas que você usará neste tutorial. Usaremos versões específicas dessas bibliotecas criando um arquivo requirements.txt no diretório do projeto, que especifica o requisito e a versão que precisamos. Crie o arquivo requirements.txt:

      Abra o arquivo em seu editor de textos e adicione as seguintes linhas para especificar as bibliotecas Image, NumPy, e TensorFlow e suas versões:

      requirements.txt

      image==1.5.20
      numpy==1.14.3
      tensorflow==1.4.0
      

      Salve o arquivo e saia do editor. Em seguida instale estas bibliotecas com o seguinte comando:

      • pip install -r requirements.txt

      Com as dependências instaladas, podemos começar a trabalhar no nosso projeto.

      Passo 2 — Importando o Dataset MNIST

      O dataset que estaremos utilizando neste tutorial é chamado de dataset MNIST, e ele é um clássico na comunidade de machine learning. Este dataset é composto de imagens de dígitos manuscritos, com 28x28 pixels de tamanho. Aqui estão alguns exemplos dos dígitos incluídos no dataset:

      Vamos criar um programa Python para trabalhar com este dataset. Usaremos um arquivo para todo o nosso trabalho neste tutorial. Crie um novo arquivo chamado main.py:

      Agora abra este arquivo no editor de textos de sua preferência e adicione esta linha de código ao arquivo para importar a biblioteca do TensorFlow:

      main.py

      import tensorflow as tf
      

      Adicione as seguintes linhas de código ao seu arquivo para importar o dataset MNIST e armazenar os dados da imagem na variável mnist:

      main.py

      from tensorflow.examples.tutorials.mnist import input_data
      mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) # y labels are oh-encoded
      

      Ao ler os dados, estamos usando one-hot-encoding para representar os rótulos (o dígito real desenhado, por exemplo "3") das imagens. O one-hot-encoding utiliza um vetor de valores binários para representar valores numéricos ou categóricos. Como nossos rótulos são para os dígitos de 0 a 9, o vetor contém dez valores, um para cada dígito possível. Um desses valores é definido como 1, para representar o dígito nesse índice do vetor, e o restante é difinido como 0. Por exemplo, o dígito 3 é representado usando o vetor [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]. Como o valor no índice 3 está armazenado como 1, o vetor representa o dígito 3.

      Para representar as imagens, os 28x28 pixels são achatados em um vetor 1D com 784 pixels de tamanho. Cada um dos 784 pixels que compõem a imagem é armazenado como um valor entre 0 e 255. Isso determina a escala de cinza do pixel, pois nossas imagens são apresentadas apenas em preto e branco. Portanto, um pixel preto é representado por 255 e um pixel branco por 0, com os vários tons de cinza em algum lugar entre eles.

      Podemos usar a variável mnist para descobrir o tamanho do dataset que acabamos de importar. Observando os num_examples para cada um dos três subconjuntos, podemos determinar que o dataset foi dividido em 55.000 imagens para treinamento, 5000 para validação e 10.000 para teste. Adicione as seguintes linhas ao seu arquivo:

      main.py

      
      n_train = mnist.train.num_examples # 55,000
      n_validation = mnist.validation.num_examples # 5000
      n_test = mnist.test.num_examples # 10,000
      

      Agora que temos nossos dados importados, é hora de pensar sobre a rede neural.

      Passo 3 — Definindo a Arquitetura da Rede Neural

      A arquitetura da rede neural refere-se a elementos como o número de camadas na rede, o número de unidades em cada camada e como as unidades são conectadas entre as camadas. Como as redes neurais são vagamente inspiradas no funcionamento do cérebro humano, aqui o termo unidade é usado para representar o que seria biologicamente um neurônio. Assim como os neurônios transmitem sinais pelo cérebro, as unidades tomam alguns valores das unidades anteriores como entrada, realizam uma computação e, em seguida, transmitem o novo valor como saída para outras unidades. Essas unidades são colocadas em camadas para formar a rede, iniciando no mínimo com uma camada para entrada de valores e uma camada para valores de saída. O termo hidden layer ou camada oculta é usado para todas as camadas entre as camadas de entrada e saída, ou seja, aquelas "ocultas" do mundo real.

      Arquiteturas diferentes podem produzir resultados drasticamente diferentes, já que o desempenho pode ser pensado como uma função da arquitetura entre outras coisas, como os parâmetros, os dados e a duração do treinamento.

      Adicione as seguintes linhas de código ao seu arquivo para armazenar o número de unidades por camada nas variáveis globais. Isso nos permite alterar a arquitetura de rede em um único lugar e, no final do tutorial, você pode testar por si mesmo como diferentes números de camadas e unidades afetarão os resultados de nosso modelo:

      main.py

      
      n_input = 784   # input layer (28x28 pixels)
      n_hidden1 = 512 # 1st hidden layer
      n_hidden2 = 256 # 2nd hidden layer
      n_hidden3 = 128 # 3rd hidden layer
      n_output = 10   # output layer (0-9 digits)
      

      O diagrama a seguir mostra uma visualização da arquitetura que projetamos, com cada camada totalmente conectada às camadas adjacentes:

      O termo "deep neural network" ou rede neural profunda se relaciona com o número de camadas ocultas, com "superficial" geralmente significando apenas uma camada oculta e "profunda", referindo-se a várias camadas ocultas. Fornecidos dados de treinamento suficientes, uma rede neural superficial com um número suficiente de unidades deve teoricamente ser capaz de representar qualquer função que uma rede neural profunda possa. Mas é mais eficiente computacionalmente usar uma rede neural profunda menor para realizar a mesma tarefa que exigiria uma rede superficial com exponencialmente mais unidades ocultas. Redes neurais superficiais também freqüentemente encontram overfitting, onde a rede essencialmente memoriza os dados de treinamento que viu e não é capaz de generalizar o conhecimento para novos dados. É por isso que as redes neurais profundas são mais comumente usadas: as várias camadas entre os dados brutos de entrada e o rótulo de saída permitem que a rede aprenda recursos em vários níveis de abstração, tornando a própria rede mais capaz de generalizar.

      Outros elementos da rede neural que precisam ser definidos aqui são os hiperparâmetros. Ao contrário dos parâmetros que serão atualizados durante o treinamento, esses valores são definidos inicialmente e permanecem constantes durante todo o processo. No seu arquivo, defina as seguintes variáveis e valores:

      main.py

      
      learning_rate = 1e-4
      n_iterations = 1000
      batch_size = 128
      dropout = 0.5
      

      A taxa de aprendizado, learningrate, representa o quanto os parâmetros serão ajustados em cada etapa do processo de aprendizado. Esses ajustes são um componente-chave do treinamento: depois de cada passagem pela rede, ajustamos os pesos ligeiramente para tentar reduzir a perda. Taxas de aprendizado maiores podem convergir mais rapidamente, mas também têm o potencial de ultrapassar os valores ideais à medida que são atualizados. O número de iterações, niterations, refere-se a quantas vezes passamos pela etapa de treinamento e o tamanho do lote ou batch_size se refere a quantos exemplos de treinamento estamos usando em cada etapa. A variável dropout representa um limiar no qual eliminamos algumas unidades aleatoriamente. Estaremos usando dropout em nossa última camada oculta para dar a cada unidade 50% de chance de ser eliminada em cada etapa de treinamento. Isso ajuda a evitar o overfitting.

      Agora já definimos a arquitetura de nossa rede neural e os hiperparâmetros que impactam o processo de aprendizagem. O próximo passo é construir a rede como um gráfico do TensorFlow.

      Passo 4 — Construindo o Gráfico do TensorFlow

      Para construir nossa rede, vamos configurará-la como um gráfico computacional para o TensorFlow executar. O conceito central do TensorFlow é o tensor, uma estrutura de dados semelhante a uma matriz ou lista inicializada, manipulada à medida que passa pelo gráfico e atualizada através do processo de aprendizado.

      Começaremos definindo três tensores como placeholders ou marcadores de posição, que são tensores nos quais alimentaremos os valores posteriormente. Adicione o seguinte ao seu arquivo:

      main.py

      
      X = tf.placeholder("float", [None, n_input])
      Y = tf.placeholder("float", [None, n_output])
      keep_prob = tf.placeholder(tf.float32) ^
      

      O único parâmetro que precisa ser especificado em sua declaração é o tamanho dos dados os quais estaremos alimentando. Para X usamos um formato [None, 784], onde None representa qualquer quantidade, pois estaremos alimentando em um número indefinido de imagens de 784 pixels. O formato de Y é [None, 10] pois iremos usá-lo para um número indefinido de saídas de rótulo, com 10 classes possíveis. O tensor keep_prob é usado para controlar a taxa de dropout, e nós o inicializamos como um placeholder ao invés de uma variável imutável porque queremos usar o mesmo tensor tanto para treinamento (quando dropout é definido para 0.5) quanto para testes (quando dropout é definido como 1.0).

      Os parâmetros que a rede atualizará no processo de treinamento são os valores weight e bias, portanto, precisamos definir um valor inicial em vez de um placeholder vazio. Esses valores são essencialmente onde a rede faz seu aprendizado, pois são utilizados nas funções de ativação dos neurônios, representando a força das conexões entre as unidades.

      Como os valores são otimizados durante o treinamento, podemos defini-los para zero por enquanto. Mas o valor inicial realmente tem um impacto significativo na precisão final do modelo. Usaremos valores aleatórios de uma distribuição normal truncada para os pesos. Queremos que eles estejam próximos de zero, para que possam se ajustar em uma direção positiva ou negativa, e um pouco diferente, para que gerem erros diferentes. Isso garantirá que o modelo aprenda algo útil. Adicione estas linhas:

      main.py

      
      weights = {
          'w1': tf.Variable(tf.truncated_normal([n_input, n_hidden1], stddev=0.1)),
          'w2': tf.Variable(tf.truncated_normal([n_hidden1, n_hidden2], stddev=0.1)),
          'w3': tf.Variable(tf.truncated_normal([n_hidden2, n_hidden3], stddev=0.1)),
          'out': tf.Variable(tf.truncated_normal([n_hidden3, n_output], stddev=0.1)),
      }
      

      Para o bias ou tendência, usamos um pequeno valor constante para garantir que os tensores se ativem nos estágios iniciais e, portanto, contribuam para a propagação. Os pesos e tensores de bias são armazenados em objetos de dicionário para facilitar o acesso. Adicione este código ao seu arquivo para definir cada bias:

      main.py

      
      biases = {
          'b1': tf.Variable(tf.constant(0.1, shape=[n_hidden1])),
          'b2': tf.Variable(tf.constant(0.1, shape=[n_hidden2])),
          'b3': tf.Variable(tf.constant(0.1, shape=[n_hidden3])),
          'out': tf.Variable(tf.constant(0.1, shape=[n_output]))
      }
      

      Em seguida, configure as camadas da rede definindo as operações que manipularão os tensores. Adicione estas linhas ao seu arquivo:

      main.py

      
      layer_1 = tf.add(tf.matmul(X, weights['w1']), biases['b1'])
      layer_2 = tf.add(tf.matmul(layer_1, weights['w2']), biases['b2'])
      layer_3 = tf.add(tf.matmul(layer_2, weights['w3']), biases['b3'])
      layer_drop = tf.nn.dropout(layer_3, keep_prob)
      output_layer = tf.matmul(layer_3, weights['out']) + biases['out']
      

      Cada camada oculta executará a multiplicação da matriz nas saídas da camada anterior e os pesos da camada atual e adicionará o bias a esses valores. Na última camada oculta, aplicaremos uma operação de eliminação usando nosso valor keep_prob de 0.5.

      O passo final na construção do gráfico é definir a função de perda que queremos otimizar. Uma escolha popular da função de perda nos programas do TensorFlow é a cross-entropy ou entropia cruzada, também conhecida como log-loss, que quantifica a diferença entre duas distribuições de probabilidade (as predições e os rótulos). Uma classificação perfeita resultaria em uma entropia cruzada de 0, com a perda completamente minimizada.

      Também precisamos escolher o algoritmo de otimização que será usado para minimizar a função de perda. Um processo denominado otimização gradiente descendente é um método comum para encontrar o mínimo (local) de uma função, tomando etapas iterativas ao longo do gradiente em uma direção negativa (descendente). Existem várias opções de algoritmos de otimização de gradiente descendente já implementados no TensorFlow, e neste tutorial vamos usar o otimizador Adam. Isso se estende à otimização de gradiente descendente usando o momento para acelerar o processo através do cálculo de uma média exponencialmente ponderada dos gradientes e usando isso nos ajustes. Adicione o seguinte código ao seu arquivo:

      main.py

      
      cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=output_layer))
      train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
      

      Agora acabamos de definir a rede e a construímos com o TensorFlow. O próximo passo é alimentar os dados através do gráfico para treiná-los e, em seguida, testar se realmente aprendeu alguma coisa.

      Passo 5 — Treinando e Testando

      O processo de treinamento envolve alimentar o dataset de treinamento através do gráfico e otimizar a função de perda. Toda vez que a rede itera um lote de mais imagens de treinamento, ela atualiza os parâmetros para reduzir a perda, a fim de prever com mais precisão os dígitos exibidos. O processo de teste envolve a execução do nosso dataset de teste através do gráfico treinado e o acompanhamento do número de imagens que são corretamente previstas, para que possamos calcular a precisão.

      Antes de iniciar o processo de treinamento, definiremos nosso método de avaliação da precisão para que possamos imprimi-lo em mini-lotes de dados enquanto treinamos. Estas declarações impressas nos permitem verificar que, da primeira iteração até a última, a perda diminui e a precisão aumenta; elas também nos permitem rastrear se executamos ou não repetições suficientes para alcançar um resultado consistente e ideal:

      main.py

      correct_pred = tf.equal(tf.argmax(output_layer, 1), tf.argmax(Y, 1))
      accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
      

      Em correct_pred, usamos a função arg_max para comparar quais imagens estão sendo previstas corretamente observando output_layer (predições) e Y (labels), e usamos a função equal para retornar isso como uma lista de Booleanos. Podemos, então, converter essa lista em floats e calcular a média para obter uma pontuação total da precisão.

      Agora estamos prontos para inicializar uma sessão para executar o gráfico. Nesta sessão, vamos alimentar a rede com nossos exemplos de treinamento e, uma vez treinados, alimentamos o mesmo gráfico com novos exemplos de teste para determinar a precisão do modelo. Adicione as seguintes linhas de código ao seu arquivo:

      main.py

      
      init = tf.global_variables_initializer()
      sess = tf.Session()
      sess.run(init)
      

      A essência do processo de treinamento em deep learning é otimizar a função de perda. Aqui, pretendemos minimizar a diferença entre os rótulos previstos das imagens e os rótulos verdadeiros das imagens. O processo envolve quatro etapas que são repetidas para um número definido de iterações:

      Em cada etapa de treinamento, os parâmetros são ligeiramente ajustados para tentar reduzir a perda para a próxima etapa. À medida que o aprendizado avança, devemos ver uma redução na perda e, eventualmente, podemos parar de treinar e usar a rede como um modelo para testar nossos novos dados.

      Adicione este código ao arquivo:

      main.py

      
      # train on mini batches
      for i in range(n_iterations):
          batch_x, batch_y = mnist.train.next_batch(batch_size)
          sess.run(train_step, feed_dict={X: batch_x, Y: batch_y, keep_prob:dropout})
      
          # print loss and accuracy (per minibatch)
          if i%100==0:
              minibatch_loss, minibatch_accuracy = sess.run([cross_entropy, accuracy], feed_dict={X: batch_x, Y: batch_y, keep_prob:1.0})
              print("Iteration", str(i), "t| Loss =", str(minibatch_loss), "t| Accuracy =", str(minibatch_accuracy))
      

      Após 100 iterações de cada etapa de treinamento em que alimentamos um mini-lote de imagens através da rede, imprimimos a perda e a precisão desse lote. Observe que não devemos esperar uma perda decrescente e uma precisão crescente aqui, pois os valores são por lote, não para o modelo inteiro. Usamos mini-lotes de imagens em vez de alimentá-las individualmente para acelerar o processo de treinamento e permitir que a rede veja vários exemplos diferentes antes de atualizar os parâmetros.

      Quando o treinamento estiver concluído, podemos executar a sessão nas imagens de teste. Desta vez estamos usando uma taxa de dropout keep_prob de 1.0 para garantir que todas as unidades estejam ativas no processo de teste.

      Adicione este código ao arquivo:

      main.py

      
      test_accuracy = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels, keep_prob:1.0})
      print("nAccuracy on test set:", test_accuracy)
      

      Agora é hora de executar nosso programa e ver com que precisão nossa rede neural pode reconhecer esses dígitos manuscritos. Salve o arquivo main.py e execute o seguinte comando no terminal para executar o script:

      Você verá uma saída semelhante à seguinte, embora os resultados individuais de perda e precisão possam variar um pouco:

      Output

      Iteration 0 | Loss = 3.67079 | Accuracy = 0.140625 Iteration 100 | Loss = 0.492122 | Accuracy = 0.84375 Iteration 200 | Loss = 0.421595 | Accuracy = 0.882812 Iteration 300 | Loss = 0.307726 | Accuracy = 0.921875 Iteration 400 | Loss = 0.392948 | Accuracy = 0.882812 Iteration 500 | Loss = 0.371461 | Accuracy = 0.90625 Iteration 600 | Loss = 0.378425 | Accuracy = 0.882812 Iteration 700 | Loss = 0.338605 | Accuracy = 0.914062 Iteration 800 | Loss = 0.379697 | Accuracy = 0.875 Iteration 900 | Loss = 0.444303 | Accuracy = 0.90625 Accuracy on test set: 0.9206

      Para tentar melhorar a precisão do nosso modelo, ou para saber mais sobre o impacto dos hiperparâmetros de ajuste, podemos testar o efeito de alterar a taxa de aprendizado, o limite de dropout, o tamanho do lote e o número de iterações. Também podemos alterar o número de unidades em nossas camadas ocultas e alterar a quantidade das próprias camadas ocultas, para ver como diferentes arquiteturas aumentam ou diminuem a precisão do modelo.

      Para demonstrar que a rede está realmente reconhecendo as imagens desenhadas à mão, vamos testá-la em uma única imagem nossa.

      Primeiro, faça o download dessa amostra de imagem de teste ou abra um editor gráfico e crie sua própria imagem de 28x28 pixels de um dígito.

      Abra o arquivo main.py no seu editor e adicione as seguintes linhas de código ao topo do arquivo para importar duas bibliotecas necessárias para a manipulação de imagens.

      main.py

      
      import numpy as np
      from PIL import Image
      ...
      

      Em seguida, no final do arquivo, adicione a seguinte linha de código para carregar a imagem de teste do dígito manuscrito:

      main.py

      
      img = np.invert(Image.open("test_img.png").convert('L')).ravel()
      

      A função open da bibliotecaImage carrega a imagem de teste como um array 4D contendo os três canais de cores RGB e a transparência Alpha. Esta não é a mesma representação que usamos anteriormente ao ler o dataset com o TensorFlow, portanto, precisamos fazer algum trabalho extra para corresponder ao formato.

      Primeiro, usamos a função convert com o parâmetro L para reduzir a representação 4D RGBA para um canal de cor em escala de cinza. Aarmazenamos isso como um array numpy e o invertemos usando np.invert, porque a matriz atual representa o preto como 0 e o branco como 255, porém, precisamos do oposto. Finalmente, chamamos ravel para achatar o array.

      Agora que os dados da imagem estão estruturados corretamente, podemos executar uma sessão da mesma forma que anteriormente, mas desta vez apenas alimentando uma imagem única para teste. Adicione o seguinte código ao seu arquivo para testar a imagem e imprimir o rótulo de saída.

      [labe main.py]
      prediction = sess.run(tf.argmax(output_layer,1), feed_dict={X: [img]})
      print ("Prediction for test image:", np.squeeze(prediction))
      

      A função np.squeeze é chamada na predição para retornar o único inteiro da matriz (ou seja, para ir de [2] para 2). A saída resultante demonstra que a rede reconheceu essa imagem como o dígito 2.

      Output

      Prediction for test image: 2

      Você pode tentar testar a rede com imagens mais complexas - dígitos que se parecem com outros dígitos, por exemplo, ou dígitos que foram mal desenhados ou desenhados incorretamente - para ver como ela se sai.

      Conclusão

      Neste tutorial você treinou com sucesso uma rede neural para classificar o dataset MNIST com cerca de 92% de precisão e testou em uma imagem sua. O estado da arte em pesquisa atual alcança cerca de 99% neste mesmo problema, usando arquiteturas de rede mais complexas envolvendo camadas convolucionais. Elas usam a estrutura 2D da imagem para melhor representar o conteúdo, ao contrário do nosso método que achata todos os pixels em um vetor de 784 unidades. Você pode ler mais sobre esse tópico no website do TensorFlow, e ver os documentos de pesquisa detalhando os resultados mais precisos no wesite do MNIST.

      Agora que você sabe como construir e treinar uma rede neural, pode tentar usar essa implementação em seus próprios dados ou testá-la em outros datasets populares, como o Google StreetView House Numbers, ou o dataset CIFAR-10 para um reconhecimento de imagem mais genérico.

      Por Ellie Birbeck



      Source link

      Como Inspecionar a Rede do Kubernetes


      Introducão

      O Kubernetes é um sistema de orquestração de container que pode gerenciar aplicações containerizadas em um cluster de nodes de servidores. A manutenção da conectividade de rede entre todos os containers em um cluster requer algumas técnicas avançadas de rede. Neste artigo vamos cobrir brevemente algumas ferramentas e técnicas para inspecionar essa configuração de rede.

      Estas ferramentas podem ser úteis se você estiver debugando problemas de conectividade, investigando problemas de taxa de transferência de rede, ou explorando o Kubernetes para aprender como ele funciona.

      Se você quiser aprender mais sobre o Kubernetes em geral, nosso guia An Introduction to Kubernetes cobre o básico. Para uma visão específica de rede do Kubernetes, por favor leia Kubernetes Networking Under the Hood.

      Começando

      Este tutorial irá assumir que você tem um cluster Kubernetes, com o kubectl instalado localmente e configurado para se conectar ao cluster.

      As seções seguintes contém muitos comandos que se destinam a serem executados em um node do Kubernetes. Eles se parecerão com isso:

      • echo 'este é um comando de node'

      Comandos que devem ser executados em sua máquina local terão a seguinte aparência:

      • echo 'este é um comando local'

      Nota: A maioria dos comandos neste tutorial precisará ser executada como usuário root. Se em vez disso você usar um usuário habilitado para o sudo em seus nodes de Kubernetes, por favor adicione sudo para executar comandos quando necessário.

      Encontrando o IP do Cluster de um Pod

      Para encontrar o endereço IP de um pod do Kubermetes, utilize o comando kubectl get pod em sua máquina local, com a opção -o wide. Esta oção irá listar mais informações, incluindo o node onde o pod reside, e o IP do cluster do pod.

      Output

      NAME READY STATUS RESTARTS AGE IP NODE hello-world-5b446dd74b-7c7pk 1/1 Running 0 22m 10.244.18.4 node-one hello-world-5b446dd74b-pxtzt 1/1 Running 0 22m 10.244.3.4 node-two

      A coluna IP irá conter o endereço IP local do cluster para cada pod.

      Se você não vir o pod que está procurando, certifique-se de que você está no namespace certo. Você pode listar todos os pods em todos os namespaces adicionando o flag --all-namespaces.

      Encontrando o IP de um Serviço

      Você pode também encontrar o IP de um serviço utilizando o kubectl. Neste caso iremos listar todos os serviços em todos os namespaces:

      • kubectl get service --all-namespaces

      Output

      Output NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes ClusterIP 10.32.0.1 <none> 443/TCP 6d kube-system csi-attacher-doplugin ClusterIP 10.32.159.128 <none> 12345/TCP 6d kube-system csi-provisioner-doplugin ClusterIP 10.32.61.61 <none> 12345/TCP 6d kube-system kube-dns ClusterIP 10.32.0.10 <none> 53/UDP,53/TCP 6d kube-system kubernetes-dashboard ClusterIP 10.32.226.209 <none> 443/TCP 6d

      O IP do serviço pode ser encontrado na coluna CLUSTER-IP.

      Encontrando e Inserindo Namespaces de Rede do Pod

      Cada pod do Kubernetes é atribuído ao seu próprio namespace de rede. Namespaces de rede (ou netns) são primitivas de rede do Linux que fornecem isolação entre dispositivos de rede.

      Isto pode ser útil para executar comandos a partir do netns do pod, para verificar resolução de DNS ou conectividade geral de rede. Para fazer isto, precisamos primeiro olhar para o ID de processo de um dos containers em um pod. Para o Docker, podemos fazer isto com uma série de dois comandos. Primeiro, liste os containers que estão executando em um node:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 173ee46a3926 gcr.io/google-samples/node-hello "/bin/sh -c 'node se…" 9 days ago Up 9 days k8s_hello-world_hello-world-5b446dd74b-pxtzt_default_386a9073-7e35-11e8-8a3d-bae97d2c1afd_0 11ad51cb72df k8s.gcr.io/pause-amd64:3.1 "/pause" 9 days ago Up 9 days k8s_POD_hello-world-5b446dd74b-pxtzt_default_386a9073-7e35-11e8-8a3d-bae97d2c1afd_0 . . .

      Encontre o container ID ou name de qualquer container no pod que você está interessado. Na saída acima estamos mostrando dois containers:

      • O primeiro container é o app hello-world executando no pod hello-world
      • O segundo é um container pause executando no pod hello-world. Este container existe apenas para manter o namespace de rede do pod

      Para obter o ID de processo de um dos containers, tome nota do container ID ou name, e utilize-o no seguinte comando docker:

      • docker inspect --format '{{ .State.Pid }}' container-id-or-name

      Output

      14552

      Um ID de processo (ou PID) será a saída. Agora podemos utilizar o programa nsenter para executar um comando no namespace de rede do processo:

      • nsenter -t your-container-pid -n ip addr

      Certifique-se de utilizar seu próprio PID, e substitua ip addr pelo comando que você gostaria de executar dentro do namespace de rede do pod.

      Nota: Uma vantagem de se utilizar nsenter para executar comandos no namespace do pod – versus a utilização de algo como docker exec – é que você tem acesso a todos os comandos disponíveis no node, em vez do conjunto de comandos gralmente limitados instalados em containers.

      Encontrando a Interface Ethernet Virtual de um Pod

      Cada namespace de rede do pod comunica-se com o netns raiz do node através de um pipe ethernet virtual. No lado do node, este pipe aparece como um dispositivo que geralmente começa com veth e termina em um identificador único, tal como veth77f2275 ou veth01. Dentro do pod este pipe aparece como eth0.

      Pode ser útil correlacionar qual dispositivo veth está emparelhado com um pod em particular. Para fazer isto, vamos listar todos os dispositivos de rede no node, em seguida listar os dispositivos no namespace de rede do pod. Podemos correlacionar os números dos dispositivos entre as duas listas para fazer a conexão.

      Primeiro, execute ip addr no namespace de rede do pod utilizando o nsenter. Consulte a seção anterior Encontrando e Inserindo Namespaces de Rede do Pod para detlahes de como fazer isto:

      • nsenter -t pid-do-seu-container -n ip addr

      Output

      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:f4:03:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.3.4/24 brd 10.244.3.255 scope global eth0 valid_lft forever preferred_lft forever

      O comando mostrará uma lista das interfaces do pod. Observe o número if11 depois de eth0@ na saída do exemplo. Isso significa que essa eth0 do pod está ligada à décima primeira interface do node. Agora execute ip addr no namespace padrão do node para listar suas interfaces:

      Output

      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever . . . 7: veth77f2275@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker0 state UP group default link/ether 26:05:99:58:0d:b9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::2405:99ff:fe58:db9/64 scope link valid_lft forever preferred_lft forever 9: vethd36cef3@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker0 state UP group default link/ether ae:05:21:a2:9a:2b brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::ac05:21ff:fea2:9a2b/64 scope link valid_lft forever preferred_lft forever 11: veth4f7342d@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker0 state UP group default link/ether e6:4d:7b:6f:56:4c brd ff:ff:ff:ff:ff:ff link-netnsid 2 inet6 fe80::e44d:7bff:fe6f:564c/64 scope link valid_lft forever preferred_lft forever

      A décima primeira interface é a veth4f7342d nessa saída do exemplo. Este é o pipe ethernet virtual para o pod que estamos inevstigando.

      Inspeção do Rastreamento de Conexão do Conntrack

      Antes da versão 1.11, o Kubernetes usava o iptables NAT e o módulo conntrack do kernel para rastrear conexões. Para listar todas as conexões sendo rastreadas atualmente, utilize o comando conntrack:

      Para assitir continuamente por novas conexões, utilize o flag -E:

      Para listar conexões controladas pelo conntrack a um endereço de destino específico, utilize o flag -d:

      • conntrack -L -d 10.32.0.1

      Se os seus nodes estão tendo problemas para fazer conexões confiáveis aos serviços, é possível que sua tabela de rastreamento de conexões esteja cheia e que novas conexões estejam sendo descartadas. Se é esse o caso você pode ver mensagens como as seguintes em seus logs de sistema:

      /var/log/syslog

      
      Jul 12 15:32:11 worker-528 kernel: nf_conntrack: table full, dropping packet.
      
      

      Há uma configuração do sysctl para o número máximo de conexões a serem rastreadas. Você pode listar o valor atual com o seguinte comando:

      • sysctl net.netfilter.nf_conntrack_max

      Output

      net.netfilter.nf_conntrack_max = 131072

      Para definir um novo valor, utilize o flag -w:

      • sysctl -w net.netfilter.nf_conntrack_max=198000

      Para tornar essa configuração permanente, adicione-a ao arquivo sysctl.conf:

      /etc/sysctl.conf

      
      . . .
      net.ipv4.netfilter.ip_conntrack_max = 198000
      

      Inspecionando as Regras do Iptables

      Antes da versão 1.11, o Kubernetes usou o iptables NAT para implementar tradução de IP virtual e o balanceamento de carga para IPs de Serviço.

      Para fazer um dump de todas as regras iptables em um node, utilize o comando iptables-save:

      Como a saída pode ser longa, você pode querer redirecionar para um arquivo (iptables-save > output.txt) ou um paginador (iptables-save | less) para avaliar suas regras mais facilmente.

      Para listar apenas as regras NAT do Serviço do Kubernetes, utilize o comando iptables e o flag -L para especificar o canal correto:

      • iptables -t nat -L KUBE-SERVICES

      Output

      Chain KUBE-SERVICES (2 references) target prot opt source destination KUBE-SVC-TCOU7JCQXEZGVUNU udp -- anywhere 10.32.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:domain KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- anywhere 10.32.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:domain KUBE-SVC-XGLOHA7QRQ3V22RZ tcp -- anywhere 10.32.226.209 /* kube-system/kubernetes-dashboard: cluster IP */ tcp dpt:https . . .

      Consultando o DNS do Cluster

      Uma maneira de fazer o debug da resolução de DNS do cluster é fazer o deploy de um container para debug com todas as feramentas que você precisa, em seguida utilize kubectl para executar nslookup nele. Isso é descrito na documentação oficial do Kubernetes.

      Outra maneira de consultar o DNS do cluster é a utilização do dig e nsenter a partir do node. Se o dig não está instalado, pode-se instalar com o apt em distribuições Linux baseadas em Debian.

      Primeiro, encontre o IP do cluster do serviço kube-dns:

      • kubectl get service -n kube-system kube-dns

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.32.0.10 <none> 53/UDP,53/TCP 15d

      O IP do cluster está destacado acima. Em seguida, vamos utilizar nsenter para executar o dig no namespace do container. Veja a seção Encontrando e Inserindo Namespaces de Rede do Pod para mais informações sobre isso.

      • nsenter -t 14346 -n dig kubernetes.default.svc.cluster.local @10.32.0.10

      Este comando dig procura o nome de domínio completo do Serviço de service-name.namespace.svc.cluster.local e especifica o IP do serviço DNS do cluster (@10.32.0.10).

      Olhando para os Detalhes do IPVS

      A partir do Kubernetes 1.11, o kube-proxy pode configurar o IPVS para lidar com a tradução de IPs de serviços virtuais para IPs de pods. Você pode listar a tabela de tradução com ipvsadm:

      Output

      IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 100.64.0.1:443 rr -> 178.128.226.86:443 Masq 1 0 0 TCP 100.64.0.10:53 rr -> 100.96.1.3:53 Masq 1 0 0 -> 100.96.1.4:53 Masq 1 0 0 UDP 100.64.0.10:53 rr -> 100.96.1.3:53 Masq 1 0 0 -> 100.96.1.4:53 Masq 1 0 0

      Para mostrar um único IP de serviço, utilize a opção -t e especifique o IP desejado:

      • ipvsadm -Ln -t 100.64.0.10:53

      Output

      Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 100.64.0.10:53 rr -> 100.96.1.3:53 Masq 1 0 0 -> 100.96.1.4:53 Masq 1 0 0

      Conclusão

      Neste artigo, analisamos alguns comandos e técnicas para explorar e inspecionar os detalhes da rede do cluster do Kubernetes. Para mais informações sobre Kubernetes, dê uma olhada na nossa tag de tutoriais de Kubernetes e na documentação oficial do Kubernetes.



      Source link