Olá, no post de hoje iremos falar um pouco sobre docker, e como utilizá-lo em seu ambiente de desenvolvimento. Se você está iniciando sua carreira, talvez não tenha ainda presenciado a treta discussão de um SysAdmin VS Desenvolvedor por conta do: ‘Na minha máquina funciona’, referente à uma aplicação ou patch que foi desenvolvida(o) localmente, mas quando aplicado ao ambiente de produção não apresentou o comportamento esperado.

pa

Resolvi falar sobre o assunto após vivenciar o seguinte cenário: Estava trabalhando em um projeto e desenvolvendo-o localmente, tudo lindo e maravilhoso, até eu precisar disponibilizar um MVP para a equipe. Diante deste quadro, resolvi subir uma máquina de testes/dev (máquina esta, que mais tarde viria a ser com certeza a maquina de produção!) e fazer o deploy da aplicação. Em resumo:

  1. Criei uma máquina virtual no nosso ambiente
  2. Fiz um scp do código fonte para este servidor
  3. Coloquei a APP para rodar
  4. A galera curtiu 0/
  5. Continuei todo o desenvolvimento neste servidor
  6. Larguei mão de versionamento, CI e qualquer outra boa prática.

deploy

E isto se manteve até eu conversar com um colega de trabalho com maior experiência, onde naquele momento, fui levado a refletir sobre algumas questões como:

  • Eu conseguiria em caso de um desastre, remontar o servidor exatamente como ele está agora?
  • A maquina precisa realmente de todas as libs para buildar código?
  • Quando o sistema estiver em uso, e eu precisar implementar novas features, onde vou desenvolver e testar? Visto que não posso simplesmente tirar o sistema do ar
  • Como vai ser quando tiver mais de uma pessoa trabalhando no projeto?

Tks @pothix! Felizmente, antes que o pior pudesse acontecer, eu me dei conta de tudo isso e fui atrás de uma solução. Visto que eu não queria na minha máquina local (no meu SO propriamente dito) ter a mesma stack de tecnologias que o projeto necessita e que eu já havia montado no servidor, lembrei de um cara chamado Docker. Mas porque o docker?

  • Dada a sua capacidade de isolar ambientes, eu não precisaria me preocupar em ter de gerenciar mais de um projeto em paralelo. Por exemplo: Ao mesmo tempo que em um container eu estou tocando minha aplicação atual com todas as suas dependências na ultima versão, e afins, eu poderia tocar também um projeto legado, com versões inferiores e sem ter de me preocupar com compatibilidade ou conflitos entre versões.
  • Tendo em vista que minha app quando publicada irá rodar um servidor Linux, mas para desenvolvimento eu utilizo Mac/OSX. Com o docker eu consigo aproximar muito o meu ambiente de produção do meu ambiente de desenvolvimento.
  • Por que eu queria por em pratica o que aprendi no treinamento “Descomplicando o Dokcer”.

Sem mais delongas, diante do problema apresentado acima eis minha solução.

Obs: Os passos abaixo, terão como exemplo um projeto Django/Python e utilizando um banco de dados mysql, desta forma, teremos 2 containers que formaram nossa stack, sendo 1 para cada serviço mencionado acima (django, mysql) . Utilizarei então o compose para gerenciar e executar este ambiente. No final deste artigo, deixarei alguns links de apoio, links que também utilizei durante o processo de criação.

Bora lá:

  • Primeiro, iniciei um novo projeto no gitlab aqui da Locaweb e “commitei” todo o código fonte do meu projeto, iniciando mesmo que tardio o meu versionamento.
  • Feito isso, iniciei a confecção do meu Dockerfile para criar a imagem com as libs que eu precisava para a aplicação web (django), que ficou da seguinte forma:

[code]
FROM python:2.7
LABEL description=“Lerolero"
MAINTAINER Adinan Paiva
WORKDIR /app
ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt
[/code]

Onde:

  • FROM – É a imagem base que vou utilizar a partir do docker hub neste caso
  • LABEL – Metadatas que serão inseridas na imagem
  • MAINTAINER – Autor/Mantedor da imagem
  • WORKDIR – Define um diretório de trabalho, que pode ser utilizado na sequencia pelo RUN e que será o diretório corrente para todo container inicializado a partir desta imagem.
  • ADD – Adiciona um  arquivo a partir da máquina host ou URL, ao filesystem da image que esta sendo criada.
  • RUN – Comando que será executado em tempo de build da imagem.
Na sequencia criei meu docker-compose.yml, que ficou da seguinte forma:

[code]
version: ‘2’

services:
db:
image: mysql:5.7
volumes:
– /Users/adinanpaiva/mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: senhainicial12
MYSQL_DATABASE: nome_da_base
MYSQL_USER: usuario
MYSQL_PASSWORD: senha

web:
build: .
env_file: .env
command: python manage.py runserver 0.0.0.0:8000

volumes:
– .:/app
ports:
– "8000:8000"
extra_hosts:
– “lala.locaweb.com.br:127.0.0.1:8000"
depends_on:
– db
[/code]

Onde:

  • version – Versão do formato do “compose-file”
  • services – Definição de cada serviço a ser executado, neste caso web, db e celery
  • image – Define a partir de qual imagem aquele container será inicializado.
  • build – Cria a imagem a partir do path especificado, path este que deve ter um “build context”, ou seja,  um Dockerfile ou a URL para um repositório git.
  • volumes – Monta um diretório do host no container. No service web por exemplo, foi montado o diretório corrente do docker-composer.yml apontando para o /app do container, isso para que eu pudesse editar meu código a partir da maquina host e, não precisasse de um novo build a cada alteração no código para vê-la em ação. Já no service db a finalidade é outra, fiz este mapeamento para que os dados do mysql fossem persistentes, ou seja, em caso de pane ou necessidade de desligar o ambiente eu não perderei os dados do meu banco.
  • environment – Adiciona variáveis de ambiente, que no caso do serviço db por exemplo, são usadas pela imagem do mysql para um setup inicial.
  •  env_file – Também adiciona variáveis de ambiente, porém, a partir de um arquivo onde seu conteúdo deve seguir o formato “chave=valor”. Cuidado, as variáveis definidas a partir de “environment” mencionado acima, sobrepõe os valores das variáveis de mesmo nome se configuradas também no env_file e, se você estiver utilizando as duas opções no mesmo service.
  • ports – Portas que serão expostas/’bindadas’ do host  para o containe
  • depends_on – Expressa/relaciona dependências entre os serviços. Sendo assim, no nosso exemplo docker-compose up vai primeiro iniciar o serviço db, e na sequência o serviço web. Mesmo assim, é válido notar que: Iniciar não é o mesmo que estar pronto para o uso. 

Seguindo, aqui vai nosso requirements.txt de exemplo:

[code]
Django==1.9.2
MySQL-python==1.2.5
gunicorn==19.3.0
[/code]

E aqui, nosso .env utilizado no serviço web.

[code]
DB_NAME= nome_da_base
DB_USER=usuario
DB_PASS=senha
DB_SERVICE=db
DB_PORT=3306
[/code]

Antes de seguirmos, cabe uma explicação para as variáveis definidas no arquivo .env. O Django, utiliza um arquivo chamado settings.py para configurar o ‘core’ do seu projeto, neste arquivo são definidas as apps que fazem parte do projeto, as conexões com bancos de dados, configurações de timezone dentre outras tantas informações. No entanto, entre o ambiente de dev e produção coisas como bancos de dados, backends de autenticação por exemplo, iram variar, e como lidar com estas diferenças de uma forma menos dolorosa?

Inicialmente eu criei um novo arquivo “dev_settings.py” alterei o que precisava, como conexão com o banco e no final do arquivo “settings.py” eu importei este arquivo, sobrescrevendo então os valores.

[code lang=”python”]
try:
from dev_settings import *
except:
pass
[/code]

Mantive este arquivo fora do meu controle de versão, e zaz.. Mesmo assim, eu não estava satisfeito em manter dois arquivos, então mais uma vez seguindo algumas dicas espertas (tks @pothix), eu passei a utilizar variáveis de ambiente para “alternar” entre estas diferenças de ambiente. Encontrei este artigo, que mesmo sendo um pouco antigo me convenceu de que isso é uma boa, além deste, também considerei as dicas do 12factor.net, o capitulo de configuração diz: Store config in the environment

Então na raiz do seu projeto para ‘buildar’ as imagens e criar o site django de exemplo (caso não tenha o seu já iniciado), execute:

[code lang=”bash”]
$ docker-compose run web django-admin.py startproject example .
[/code]

OK, vamos rodar isso de uma vez por todas. Antes, verifique se a esta altura sua estrutura de diretório se parece com isso (caso você esteja o caso de exemplo acima, claro!):

.
├── .env
├── Dockerfile
├── docker-compose.yml
├── example
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── manage.py
│ ├── settings.py
│ ├── settings.pyc
│ ├── urls.py
│ ├── wsgi.py
│ └── wsgi.pyc
└── requirements.txt

Agora, suba o ambiente:

[code lang=”bash”]
$ docker-compose up -d
[/code]

Verifique o status do seus containers:

[code lang=”bash”]
$ docker-compose ps
[/code]

Opa, da pra ver os logs também com:

[code lang=”bash”]
$ docker-compose logs
[/code]

Ou por service, assim:

[code lang=”bash”]
$ docker-compose logs
[/code]

O Django no primeiro startup, precisa executar uma migração para configurar o banco de dados, mas antes disso, certifique-se de que já configurou o seu settings.py para usar as variáveis de ambiente que você configurou no .env. Por exemplo:

[code lang=”python”]
DEBUG = bool(os.environ.get(‘DEBUG’, False))
ALLOWED_HOSTS = os.environ.get(‘ALLOWED_HOSTS’, "*").split()

DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’,
‘NAME’: os.environ[‘DB_NAME’],
‘USER’: os.environ[‘DB_USER’],
‘PASSWORD’: os.environ[‘DB_PASS’],
‘HOST’: os.environ[‘DB_SERVICE’],
‘PORT’: os.environ[‘DB_PORT’]
}
}
[/code]

Agora sim:

[code lang=”bash”]
$ docker-compose run web python manage.py migrate
[/code]

Que tal ver o resultado no browser?

Se tudo deu certo seguindo os passos anteriores, você verá a pagina de boas vindas do django, que é exibida graças ao modo de debug estar ativado 😀

Caso tenha absolvido as ideias e implementado em seu projeto já existente, teste, acredito que terá sucesso também.

Quando desejar, pode parar o ambiente da seguinte forma:

[code lang=”bash”]
$ docker-compose down
[/code]

Isto irá remover os containers criados, mas as imagens permaneceram, bem como os dados dos volumes mapeados.

Conclusão:

Com o setup acima, que a primeira vista pode parecer complexo mas, se for levado em consideração o fator de que será feito uma única vez, deixa de parecer, eu consegui me manter organizado, organizar o projeto, proporcionar a novos DEVs que por ventura venham a participar do desenvolvimento, um setup seguro, confiável e prático, onde em poucos minutos o cara que chegou ontem já estará ‘codando’ e contribuindo com o crescimento da aplicação.

Espero que esse relato possa ser útil para quem esta iniciando não cometer os erros aqui mencionados, bem como, para quem já tem um tempo nesta jornada, mas assim como eu foi “relaxado” possa corrigir isto.

Queria deixar claro também, que tudo que eu disse aqui é apenas questão de opiniões que pude formar conversando com a galera, lendo documentações e artigos da internet e que não é uma verdade absoluta. Mais uma vez, espero ter ajudado alguém de alguma forma.

Links de apoio:

Tutorias:
Documentação Oficial: