Há alguns meses tive o privilégio de assistir à uma sessão de palestras de um dos meus autores favoritos: Martin Fowler. O tema era sobre padrões arquiteturais para desenvolvimento, distribuição e operação de aplicações, alguns pouco conhecidos, mas muito úteis.

Vou escrever uma série de 3 artigos falando sobre alguns dos padrões que considero mais interessantes. Vamos ao primeiro!

Infrastructure as Code

Infraestrutura como código é um padrão de projeto que consiste em tratar as configurações do ambiente como parte do código.

Iron Age

O autor fala da Era de Ferro, onde a implantação e atualização de um sistema consiste em acessar o servidor fisicamente ou via SSH e realizar manualmente as configurações e alterações necessárias de acordo com os pré-requisitos daquela versão.

Você já viu um documento de várias páginas com instruções sobre como configurar um ambiente? Alguém sempre vai fazer algo errado! Por melhores que sejam as instruções, dificilmente existem dois ambientes exatamente iguais (mesmo instalações “frescas” de sistemas podem mudar por causa de atualizações) e o fator humano sempre vai trazer problemas.

Nesta era, uma característica muito comum é a fragilidade do ambiente. Se alguém fizer algo errado, pode não conseguir restaurar o ambiente original ou demorar muito para que isso aconteça. As empresas tentam diminuir a fragilidade adotando processos rígidos e burocráticos para acesso aos ambientes, o que por sua vez causa um atraso considerável na velocidade do desenvolvimento e na entrega de funcionalidades e correções para o usuário.

Outro efeito comum é a dificuldade ara replicar os incidentes de produção em um ambiente de desenvolvimento. Quem já tentou reproduzir um problema causado por configurações de ambiente num sistema razoavelmente complexo sabe do que estou falando. Em sistemas implantados em servidores configurados manualmente, é praticamente impossível reproduzir um ambiente fiel para testes ou desenvolvimento. E, mesmo com toda a burocracia de segurança do mundo, frequentemente isto acarreta em incidentes em produção devido a cenários não testados propriamente. Mesmo quando alguém consegue reproduzir um ambiente de produção de forma relativamente fiel, fica limitado a um teste por vez. Além disso, frequentemente o ambiente precisa ser movido entre diferentes versões, aumentando a dificuldade para testar diversos cenários.

A realidade é que construir e reconstituir ambientes manualmente – mesmo com instruções precisas – faz com que muitas vezes se tome a decisão de entregar código não testado, seja devido ao custo, à escassez de tempo ou mesmo preguiça. 😀

Cloud Age

Por outro lado, na Era da Nuvem, onde não é incomum novas versões do sistema serem entregues todos os dias, ou até mais de uma vez por dia, não se pode depender de um ambiente tão frágil. É preciso ser capaz de criar e recriar um ambiente quantas vezes for necessário.

Se um sistema precisa escalar para mais servidores num cluster, por exemplo, o trabalho manual de configurar novas máquinas é um impeditivo para um sistema em larga escala. Isso deve ser feito automaticamente.

Um sistema em nuvem não pode esperar o administrador de sistemas instalar mais um servidor para atender picos de demanda!

Além disso, como você testa um sistema em nuvem? Como você reproduz um ambiente que muda a todo momento?

Uma abordagem para resolver os problemas citados acima e dar mais flexibilidade ao desenvolvimento é a Infraestrutura como Código. Veremos a seguir os princípios de como isso funciona.

Princípios

Nenhuma alteração é feita diretamente num servidor. Todas as configurações, incluindo programas necessários, variáveis de ambientes, scripts e outros pré-requisitos devem estar em arquivos de configuração versionados num repositório como Git ou similar, como qualquer outro código-fonte.

Da mesma forma que você gera versões do sistema, o seu ambiente também possui versões definidas que vão evoluindo gradualmente.

A qualquer momento, à partir da definição de um versão, um ambiente pode ser criado automaticamente do zero com exatamente as mesmas características de outros ambientes da mesma versão.

A qualquer momento, alguém pode olhar o histórico dos arquivos de definição e analisar as configurações do ambiente numa determina versão.

Não deve ser permitido que as pessoas acessem o servidor diretamente ou via SSH e façam alterações manual. Caro que num ambiente criado exclusivamente para desenvolvimento alguém pode precisar de acesso, mas sempre deve contar que em breve quaisquer alterações serão definitivamente destruídas. Portanto, se o desenvolvedor sentir necessidade de alguma alteração no ambiente, ele pode testar numa instância exclusiva, mas depois terá que atualizar a configuração do repositório com as definições do ambiente.

Criar e destruir ambientes frequentemente é uma boa prática. Um dos motivos é garantir que o ambiente vai estar sempre consistente com a configuração. Outro é reforçar a cultura de que o ambiente de execução é descartável e não deve ser considerado uma “fonte de verdade” (source of truth).

Testes unitários e de integração devem ser realizados também com frequência de modo a garantir que a cada atualização tudo continua funcionando.

Como isso é feito na prática

Hoje existem diversas ferramentas que permitem gerar ambientes a partir de configurações em arquivos. Vou citar algumas dentre as mais comuns.

Para desenvolvimento, temos o Vagrant, que é uma ferramenta muito comum usada para evitar o trabalho de cada desenvolvedor configurar um ambiente próprio manualmente.

Ele funciona usando máquinas virtuais do Virtual Box. Basicamente você define uma imagem base e os softwares que você precisa para desenvolver. Depois digita alguns comandos para inicializar e em alguns minutos você tem um ambiente consistente e pronto para desenvolver. Um membro da equipe realiza a configuração e todos os demais aproveitam o trabalho.

Você pode incluir na sua imagem sistemas ou mocks dos sistemas dos quais você depende para desenvolver, de forma que se possa reproduzir facilmente um ambiente para testes.

Na mesma linha temos o Docker, que também funciona usando virtualização, porém num nível diferente. O Docker é capaz de usar recursos do Kernel do Linux numa forma bem leve de virtualização que não necessita de grandes imagens e não consome tanta memória.

Além do desenvolvimento, outra ferramenta geralmente usada por administradores de sistemas chama-se Chef. A ferramenta é escrita em Ruby e permite executar diversas ações usando scripts nesta linguagem. Ela permite definir scripts que configuram todo um ambiente operacional. Um dos exemplos básicos é de um ambiente com o servidor HTTP Apache e uma página básica.

A arquitetura do Chef consiste num servidor principal (Chef Server) e os servidores clientes (Chef Clients), que podem ser máquinas físicas, virtuais, na nuvem ou até dispositivos de rede.

Através do servidor, você pode orquestrar os servidores clientes, criando e destruindo instâncias baseando-se em receitas, ou seja, scripts com a definição dos ambientes.

Outra alternativa na mesma linha é o Puppet. A ideia é a mesma do Chef, sendo a principal diferença o uso de uma abordagem declarativa em contraste com os scripts Ruby do Chef. As configurações são chamadas de manifestos, o que seria o equivalente às receitas do Chef.

Nem todas as ferramentas descritas acima são necessariamente exclusivas. Muitas empresas usam mais de uma para cenários diferentes. Por exemplo, o Vagrant pode ser usado quando o sistema operacional onde o programa é executado é diferente do sistema operacional usado pelos desenvolvedores. O Docker, por outro lado, é uma forma bem interessante de executar testes de integração de forma isolada e eficiente. Já entre Chef e Puppet, geralmente você escolhe um para ser a fonte oficial dos seus ambientes.

Claro que a configuração de um ciclo de desenvolvimento eficiente e flexível, que envolve analisar e implantar o uso dessas e possivelmente outras ferramentas, não é trivial. Cabe a cada líder de projeto analisar os benefícios que a automação traz. Ter pelo menos um desenvolvedor para fazer o papel de administrador desses ambientes (DevOps) ou, se possível, um administrador de sistemas (SysOps) que conheça as ferramentas é uma grande vantagem, mesmo que seja para fazer um uso mais básico dessas ferramentas.

Enquanto muitas empresas menosprezam a automação, deve-se ter um equilíbrio e não gastar tempo demais com isso. Por exemplo, projetos pequenos com uma pessoa ou de curta duração. Não imite o Google!

Benefícios

A Infraestrutura como Código facilita imensamente a implementação de Integração Contínua e a leva a um nível completamente diferente, pois permite a execução automática, isolada, concorrente de testes de integração em ambientes idênticas à produção.

Ela também permite escalar automaticamente uma aplicação em cluster para atender picos de demanda.

Também possibilita automatizar a recuperação em caso de falhas num ambiente, pois basta “matar” o nó problemático e redirecionar os clientes outro.

Ambientes podem ser replicados sem esforço e sem possibilidade de erro humano no processo. Replicar o ambiente de produção é agora trivial.

É infinitamente mais seguro realizar mudanças nos ambientes através dos arquivos de configuração versionados. Pode-se executar todos os testes de integração na nova versão do ambiente antes de colocá-la em produção. Em casos de problema, basta restaurar a versão anterior. Ao longo do tempo, as alterações no ambiente são gradualmente testadas, verificando-se a cada versão se o ambiente continua consistente e, assim, encontrar pontos problemáticos e identificar riscos.

A abordagem abre as portas para padrões arquiteturais como microservices.

Desvantagens

Versionar a configuração exige um investimento muito maior durante a definição da infraestrutura utilizada. A diferença, basicamente, é que configurar um servidor manualmente uma vez é muito mais rápido e simples do que gerenciar arquivos de configuração e toda a infraestrutura para criar e destruir servidores na nuvem, especialmente se não há um expert no assunto envolvido do projeto. Entretanto, vale lembrar que, em projetos de tamanho razoável, isto se paga rapidamente com o tempo.

Mesmo usando virtualização e configurações, na prática ainda existe um certo nível de instabilidade. Em suma, ocorre por vezes que dois ambientes criados a partir da mesma definição apresentem comportamentos diferentes. A causa varia, mas pode ser porque ele depende de uma fonte externa que muda, tal como um artefato Maven ou qualquer programa que fica num repositório externo. Outro motivo comum é a instabilidade do próprio código, por exemplo, se implementações concorrentes apresentam condições de corrida difíceis que raramente vêm à tona. Enfim, é necessário implementar mecanismos para executar frequentemente testes e verificar a “saúde” dos nós, de modo a recuperar o estado do sistema em caso de falhas. De qualquer forma, é muito mais fácil analisar estatisticamente os resultados dos testes realizados na integração contínua à procura de problemas em potencial, do que aplicando testes ad hoc em ambientes manualmente criados.

Considerações

Independente da forma como é implementado, o padrão Infraestrutura como Código é uma necessidade para qualquer produto moderno que esteja em desenvolvimento ativo e constante, principalmente os que fornecem serviços diretamente na web.

Isso não significa que produtos utilizados na intranet não se beneficiem, muito pelo contrário. A única dificuldade é se o produto é instalado em servidores de terceiros, pois então você perde a garantia do ambiente. Mesmo assim, uma alternativa que muitas empresas têm adotado é disponibilizar o produto num ambiente pré-configurado, uma imagem para Virtual Box, VM Ware, etc., onde o cliente precisa apenas initializar a máquina virtual para ter o produto funcionando.

Dificilmente você encontrará hoje uma empresa sério fornecendo algum produto como Software as a Service que não utilize este padrão.

Felizmente, as principais ferramentas são de código aberto e possuem vasta documentação e material de estudo, estando assim disponível para quem estiver disposto. 🙂