Dockerizando aplicações – Base de código

Standard

Estamos evoluindo continuamente para entregar aplicações cada vez melhores, em menor tempo, replicáveis e escaláveis. Porém os esforços e aprendizados para atingir esse nível de maturidade muitas vezes não são simples de se alcançar.

Atualmente notamos o surgimento de várias opções de plataformas para facilitar a implantação, configuração e escalabilidade das aplicações que desenvolvemos. Porém, para aumentar nosso grau de maturidade não podemos apenas depender da plataforma, precisamos construir nossa aplicação seguindo boas práticas.

Visando sugerir uma série de boas práticas comuns a aplicações web modernas, alguns desenvolvedores do Heroku escreveram o 12Factor app, baseado em uma larga experiência em desenvolvimento desse tipo de aplicação.

12factor (1)

“The Twelve-Factor app” (12factor) é um manifesto com uma série de boas práticas para construção de software utilizando formatos declarativos de automação, maximizando portabilidade e minimizando divergências entre ambientes de execução, permitindo a implantação em plataformas de nuvem modernas e facilitando a escalabilidade. Assim, aplicações são construídas sem manter estado (stateless) e conectadas a qualquer combinação de serviços de infraestrutura para retenção de dados (Banco de dados, fila, memória cache e afins).

Nesse capítulo falaremos sobre criação de aplicações como imagens docker com base no 12factor app, ou seja, a ideia é demonstrar as melhores práticas de como se realizar a criação de uma infraestrutura para suportar, empacotar e disponibilizar a sua aplicação com alto nível de maturidade e agilidade.

O uso do 12factor com Docker é uma combinação perfeita, pois muitos dos recursos do Docker são melhores aproveitados caso sua aplicação tenha sido pensada para tal. Dessa forma, nesse texto daremos uma ideia de como aproveitar todo potencial da sua solução.

Como exemplo, usaremos a aplicação abaixo:

from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
   redis.incr('hits')
   return 'Hello World! %s times.' % redis.get('hits')
if __name__ == "__main__":
   app.run(host="0.0.0.0", debug=True)

É uma aplicação simples, que será usada apenas como exemplo. Através de um serviço HTTP, ela exibe quantas vezes foi acessada. Essa informação é armazenada através de um contador numa instância Redis.

Agora vamos às boas práticas!

Base de código

Com o objetivo de facilitar o controle das mudanças de código, viabilizando a rastreabilidade das alterações, essa boa prática indica que cada aplicação deve ter apenas uma base de código e, a partir do mesmo, uma aplicação pode ser implantada em distintos ambientes, mas a base precisa ser a mesma. Vale salientar que essa boa prática é necessária caso pretenda praticar o Continuous Integration (CI), ou seja, o mesmo código deverá ser consumido pelo processo de integração contínua e posteriormente implantado em desenvolvimento, teste e produção, mudando apenas parâmetros específicos ao ambiente em questão.

Para essa explicação, criei um repositório de exemplo.

Perceba que todo nosso código está dentro desse repositório, organizado por prática em cada pasta, para facilitar a reprodução. Lembre de entrar na pasta correspondente a cada boa prática apresentada.

O Docker tem uma infraestrutura que permite a utilização de variável de ambiente para parametrização da infraestrutura, sendo assim o mesmo código terá um comportamento distinto com base no valor das variáveis de ambiente.

Aqui usaremos o Docker Compose para realizar a composição de todo ambiente, fazendo com que a configuração dos distintos serviços e sua comunicação seja facilitada.

codebase-deploys

Posteriormente, mais precisamente na terceira boa prática Configuração desse compêndio de sugestões, trataremos mais detalhadamente sobre parametrização da aplicação, por hora apenas aplicaremos opções via variável de ambiente para a arquitetura ao invés de utilizar internamente no código da aplicação.

Para configurar o ambiente de desenvolvimento para o exemplo apresentado temos esse arquivo docker-compose.yml:

version: '2'
services:
  web:
    build: .
    ports:
     - "5000:5000"
    volumes:
     - .:/code
    labels:
     - 'app.environment=${ENV_APP}'
  redis:
    image: redis
    volumes:
     - dados_${ENV_APP}:/data
    labels:
     - 'app.environment=${ENV_APP}'

O serviço redis será utilizado a partir da imagem oficial redis, sem modificação no momento e o serviço web será gerado a partir da construção de uma imagem Docker, que tem como base a imagem oficial do python 2.7. Criaremos nossa imagem através do seguinte Dockerfile:

FROM python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD python app.py

Com posse de todos os arquivos na mesma pasta, iniciaremos o ambiente com o seguinte comando:

export ENV_APP=devel ; docker-compose -p $ENV_APP up -d

Como podemos perceber na configuração que acabamos de construir, a variável “ENV_APP” definirá qual volume será usado para persistir os dados que serão enviados pela aplicação web, ou seja, com base na mudança dessa opção teremos o serviço rodando com um comportamento diferente, mas sempre a partir do mesmo código, dessa forma seguindo perfeitamente a ideia dessa primeira boa prática.