Precisamos falar sobre teste automatizado de infraestrutura

Standard

Acredito que seja notável para maioria dos profissionais de TI que estamos passando por uma grande revolução em nossa área. A automação de infraestrutura é uma realidade e não gerenciamos mais serviços e servidores da mesma forma que fazíamos há 10 anos atrás.

Não é mais tolerável ter o prazo de uma semana para entregar um novo servidor para equipe de desenvolvimento, as solicitações normalmente são para o mesmo dia, isso quando os clientes já não conseguem criar seus próprios hosts sem necessidade de abertura de ticket para os sysadmins.

Infraestrutura virou código

A automação é possível através da parametrização das definições de infraestrutura, ou seja, transformamos a criação de serviços e servidores em código. Que são especificações descritivas de como desejamos que aquele serviço/servidor seja instalado e configurado.

Ao transformar a infraestrutura em código podemos nos aproveitar de todo conhecimento adquirido e já validado pela engenharia de software por anos de experimentação no assunto. E seguindo essa linha, um assunto muito importante é que todo código precisa ser testado de forma automatizada, ou seja, se infra virou software, precisamos testá-la também.

Como testávamos infra no passado?

Após instalar, configurar e parametrizar um ambiente recém criado, a equipe de sysadmin normalmente executava uma bateria de testes no serviço/servidor disponibilizado. Essa analise era normalmente manual, o que demandava muito tempo do sysadmin, ou seja, tínhamos uma pessoa altamente capacitada, executando uma tarefa repetitiva, que normalmente agregava pouco para o seu nível intelectual e era bem tedioso, para dizer a verdade.

Pelo fato do teste ser manual, era quase impossível garantir que os mesmo testes seriam repetidos após uma modificação no ambiente, ou seja, caso os testes manuais não falhassem, a garantia que aquele ambiente estava pronto para uso era bem baixa.

Um outro problema com relação a modificações e testes manuais era a tendência que os ambiente de desenvolvimentotesteprodução não fossem idênticos, dessa forma havia pouca confiança entre esses estágios e assim todos os testes manuais eram executados em cada etapa desse processo.

Se você levar em consideração que o serviço/servidor foi testado na fase de teste ele poderia ser colocado em produção sem qualquer dúvida, certo? Errado! A maioria dos sysadmins não tinha confiança o suficiente para tal e o motivo era bem nobre, já que várias pessoas fazendo intervenções manuais nos ambientes em momentos distintos impactavam negativamente na padronização dos ambientes.

Todos esses problemas resultavam numa alta resistência à modificação de um ambiente em produção, pois além de demandar um alto esforço pra sua modificação, a garantia de sucesso normalmente era bem baixa, com alto impacto na disponibilidade, já que o plano de retorno normalmente era restaurar backup ou snapshot de toda instância e normalmente esses serviços não tinha contingência.

Era bem comum se deparar com servidores que não recebiam atualização nem mesmo de patch de segurança e além de estarem defasados no que tange a funcionalidade, normalmente estavam também vulneráveis a acessos indesejados e manipulação dos seus dados.

Testando código de infra

Sempre que falamos de engenharia de software aplicada à automação de infraestrutura gera um certo “frio na barriga” em alguns sysadmin, e então brota uma certa “resistência” com relação a usar as melhores práticas oriundas dessa disciplina. E não é por mal, pois para muitos sysadmin iniciar nesse caminho é partir do zero em um mundo inteiro a ser explorado e nem todos tem tempo e disposição pra isso. Por conta disso, vamos com calma.

A notícia boa no que tange a testes de infraestrutura é que as ferramentas geralmente tem um bom nível de abstração, ou seja, abstração do uso de teste para infraestrutra, as ferramentas disponível são de fácil uso, mesmo para quem não tem um grande histórico de desenvolvimento de software.

Vejam um exemplo de como testar se você tem o pacote nginx instalado e se ele está na versão 1.2.x:

def test_nginx_is_installed(Package):
 nginx = Package("nginx")
 assert nginx.is_installed
 assert nginx.version.startswith("1.2")

O assert é o parâmetro onde é informado o que se deseja testar. De “nginx está instalado?”, é traduzido para o inglês que basicamente é assert nginx.is_installed. O segundo é “A versão do nginx começa com 1.2?” Caso as respostas para esses perguntas sejam negativas, a ferramenta de teste emitirá uma falha e assim você pode saber que tem um problema.

E pra usar esse exemplo basta executar os comandos abaixo:

pip install testinfra
testinfra --sudo --connection=ssh --hosts=servidor_a_ser_testado test_mytest.py

O arquivo de exemplo tem o nome de test_mytest.py. Com os comandos informados acima será instalado o testinfra (um exemplo de ferramenta de teste) e o aplicativo recém instalado conectará via ssh e realizará os testes que estão definidos dentro do arquivo test_mytest.py.

Soluções para teste

Não pretendo me aprofundar nas ferramentas para teste agora, vou apenas indicar as suas respectivas documentações e talvez eu me aprofunde em uma ou outra nos próximos artigos:

  • Serverspec (Mais usada e escrita em ruby, usa rspec como base)
  • Testinfra (Escrita em python, usa Pytest como base e parece muito com serverspec)
  • Inspec (Escrita em ruby, mantida pela mesma empresa do Chef. Usa rspec como base)
  • Beaker (Escrita em ruby, mantida pela mesma empresa do Puppet. Usa rspec como base)

Meu time preferiu usar testinfra, mas explico isso em outro artigo.

Como construir os testes de infraestrutura

Para testes automatizados de software, de acordo com a engenharia de software, existe o conceito de pirâmide de teste, onde os testes são categorizados por velocidade na execução e o seu feedback de sucesso ou falha. Resumindo, quanto mais rápidos os testes, em maior quantidade eles devem existir em sua estrutura, pois eles custam pouco poder computacional. Esse conceito para software já foi exaustivamente explicado nesse link, por uma pessoa bem mais relevante do que eu.

Seguindo a ideia da pirâmide usada para software, segue abaixo uma proposta para pirâmide de testes no contexto de infraestrutura como código:

Vou detalhar cada camada, começando de baixo pra cima:

Rápido

São aqueles que seriam equivalentes aos testes unitários de software, mas no nosso caso normalmente são realizadas checagem no código com relação a estilo e erro de sintaxe. Caso você use chef, é possível utilizar o chefspec e construir testes unitários para suas receitas. Esses testes não necessitam de uma instancia funcionando, ele de fato testa uma simulação do código. Os outros produtos (ansible, puppet e afins) não tem solução parecida com essa.

No ansible você pode usar o parâmetro –check para checar suas definições:

ansible-playbook site.yml --check

Obs: Esse comando não é muito útil pra nosso time, pois temos muitas condicionais e registro de variáveis no código e o –check do ansible não funciona muito bem nessas situações. No seu caso isso pode ser útil.

Nesse nível, nos testamos as definições do cloudformation da amazon com os seguintes comandos:

npm install cfn-check
cfn-check <arquivo de definição do cloudformation>

O comando acima retornará falha caso encontre algum problema de sintaxe e argumentos necessários em determinados recursos, tudo isso antes que você envie esse arquivo para AWS tentar criar os recursos e falhar no processo.

Normal

São responsáveis por testarem o estado esperado de uma instancia provisionada de forma automatizada, ou seja, você tem uma definição para instalar, configurar e iniciar um servidor web, o teste nesse caso seria acessar o servidor e verificar se o que foi solicitado está de fato refletido no servidor. Nesse momento seria basicamente testar o conjunto de definição enviada (role pra ansible, cookbook pra chef e módulo para puppet).

No exemplo do servidor web, o teste seria verificar se a porta 80 está escutando requisição e se o processo do serviço web está em execução. No testinfra seria:

def test_nginx_is_listening(Socket):
 nginx = Socket("tcp://0.0.0.0:80")
 assert nginx.is_listening

def test_nginx_running_and_enabled(Service):
 nginx = Service("nginx")
 assert nginx.is_running
 assert nginx.is_enabled

Cuidado com o anti-padrão do teste reflexivo, que é quando você acaba escrevendo um teste da mesma maneira que você escreve a definição da tarefa, um bom exemplo seria você ter uma definição para criar um usuário e ter um teste da mesma maneira que solicita sua criação, ou seja, não faz muito sentido esse tipo de teste em seu ambiente.

Moderado

São responsáveis por testar a integração entre múltiplos serviços/servidores, ou seja, a ideia aqui é testar a relação entre os conjuntos de definições (role pra ansible, cookbook pra chef e módulo para puppet) no mesmo ativo ou entre hosts distintos.

Se você tem um servidor web e um proxy reverso, é nesse momento que você testará a comunicação esperada entre eles.

Pesado

São responsáveis por simular o uso do serviço, se portando o máximo possível com o consumidor do serviço/servidor em questão.

Se você tem um servidor web, hospedando uma determinada aplicação que tem formulário de usuário e senha, um teste esperado para esse nível é efetuar login com um usuário e senha de teste.

Próximos passos

Nos próximos artigos trataremos como podemos colocar os testes automatizados em nosso pipeline e como utilizar o docker pra facilitar o processo de testes automatizados e assim abstrair o host que fará o teste.