Docker: Ambiente de desenvolvimento portátil

TL;DR

Containers Docker podem ser utilizados para encapsular suas dependências, evitar conflitos de versão e criar ambientes de desenvolvimento portáteis, alinhando o ambiente de produção com o de desenvolvimento. Através deste repositório no GitHub, você pode acessar todos os exemplos desenvolvidos neste artigo.

Motivação

Toda vez que iniciamos um projeto, dezenas de bibliotecas e dependências precisam ser instaladas em nossas máquinas, muitas vezes ocasionando conflitos de versões ou estruturas complexas de se manter. Em empresas que utilizam metodologias tradicionais, novos funcionários chegam a levar várias horas ou até mesmo dias para configurar seus ambientes e começar a produzir efetivamente.

Outro problema crítico é o desalinhamento entre ambientes, diferentes sistemas operacionais e versões de ferramentas acabam criando a famosa frase: “Na minha máquina funciona” (mas em nenhuma outra funciona). Não podendo deixar de fora problemas com ambientes de produção, que por sua vez acabam possuindo algumas restrições que nem sempre podem ser reproduzidas localmente.

Diante disso, soluções como o Docker, que organizam a aplicação através de containers, começaram a ser adotadas para simplificar o ambiente de desenvolvimento e alinhar os diversos fatores que podem atrapalhar a execução do projeto. As imagens Docker permitem que o desenvolvedor versione e crie ambientes de maneira isolada, simplificada e portátil.

Docker

O Docker é uma aplicação desenvolvida em Go que provê diversas ferramentas para a virtualização de aplicações através de recursos nativos do Linux como o  LXC e cgroups. O LXC é uma tecnologia de virtualização que permite que múltiplos sistemas sejam executados de forma isolada sob um único Kernel Linux. De forma geral, o Docker proporciona uma camada de abstração que facilita o gerenciamento da virtualização de sistemas Linux, as aplicações são mantidas através de containers que por sua vez podem ser combinados entre si para compor aplicações complexas.

Não instale bibliotecas nunca mais!

Sim, isso foi meio exagerado, mas através do Docker você pode criar imagens personalizadas contendo todas as dependências necessárias, não precisando instalar nada localmente, basta ter o Docker instalado, tornado-o uma ótima ferramenta para gerir ambientes de desenvolvimento.

As imagens que geram os containers podem ser criadas por você ou baixados da internet. Através do Docker Hub, você pode encontrar milhares de ambientes previamente configurados com diversas tecnologias do mercado, como PythonWordPress, Nginx, Golang, Java, entre outros.

As configurações são extremamente simples. Através de um arquivo textual você poderá criar imagens com todos os recursos necessários, poderá controlar as portas que a sua aplicação disponibilizará, indicar quais arquivos você deseja sobrescrever no container, entre outras coisas.

Instalando o Docker

Não irei aprofundar nessa etapa, basta acessar a página de download do Docker e baixar a versão adequada para o seu sistema operacional. Para ambiente linux , um bom guia pode ser encontrado aqui. No linux o Docker utilizará o kernel nativo do sistema como base de virtualização, já para sistemas Windows e Mac uma virtualização do Kernel será instalada, ambos funcionam muito bem. Mas claro, no Linux será um foguete.

Criando meu primeiro container

Nosso primeiro exemplo será um container Nginx. Um server HTTP muito utilizado para criação de proxy e controle de serviços web. Existem inúmeras imagens prontas na internet, através do Docker Hub você poderá encontrar dezenas de variações, mas tome muito cuidado com a procedência das imagens, pois qualquer pessoa pode personalizar e enviar imagens para o repositório. Utilizarei a imagem oficial do Nginx, para baixá-la basta executar o comando:

docker pull nginx

As imagens são baixadas automaticamente ao criar containers, porém o comando pull pode ser útil para baixar atualizações e verificar a disponibilidade de imagens. Logo abaixo irei mostrar um simples exemplo de utilização do Nginx com um site desenvolvido em React. Criei um repositório no GitHub com os arquivos utilizados nesse artigo, acesse através do seu terminal o diretório 1-react-nginx, e execute o comando:

docker run --rm -ti --name my-nginx -p 8080:80 -v $(pwd)/build:/usr/share/nginx/html nginx
Aplicação React rodando no navegador

Após executar o comando, você pode acessar o seu server HTTP pelo endereço http://localhost:8080 e conferir a página de boas vindas do React. Você também irá observar que os logs de acesso serão impressos no mesmo terminal que o comando foi executado. Para finalizar, basta apertar CTRL+C que o serviço será finalizado e apagado. Caso desejar que o container permaneça salvo após a finalização, basta remover a flag -rm do comando de inicialização.

Entretanto, ainda precisamos de possuir o NPM / Yarn instalados na máquina para realizar o build da aplicação React. Essa dependência também pode ser resolvida com o Docker. Através de um container Node podemos “buildar” o Javascript e evitar que o desenvolvedor necessite de uma versão específica do Node ou Yarn instalado em sua máquina. O build pode ser feito através do seguinte comando:

docker run --rm -w /app -v $(pwd):/app node:alpine sh -c "yarn && yarn build"

Podemos notar algumas diferenças em relação ao primeiro comando. O workdir (-w) é necessário para que o comando sh -c “yarn && yarn build” seja executado no diretório correto. A imagem node:alpine utiliza uma tag que identifica uma versão específica da imagem, a tag alpine costuma ser a versão mais leve e otimizada para utilização. Por fim, foi especificado um comando pós inicialização, **yarn && yarn build, **que é responsável por baixar as dependências do react e fazer o build da aplicação.

Repare também que o diretório do projeto não é copiado e sim compartilhado, ou seja, as mudanças feitas pelo container também serão efetivadas nos arquivos do projeto. Desta forma, as dependências baixadas e o build do projeto serão salvos no mesmo diretório.

Simplificando a execução

Para facilitar a execução, todos os comandos podem ser organizados em um arquivo Makefile, permitindo que o desenvolvedor possa utilizar atalhos para buildar e servir os arquivos estáticos. Inclusive criar outras variações para executar testes ou monitorar mudanças de arquivos e assim automatizar todo o processo de desenvolvimento através do Docker. As tasks apresentadas abaaixo podem ser executadas com o comando make flag, ex: make dev, make test ou make prod.

Makefile:

yarn:
	docker run --rm -w /app -v $(PWD):/app node:alpine yarn
build: yarn
	docker run --rm -w /app -v $(PWD):/app node:alpine yarn build
dev: yarn
	docker run -ti --rm -w /app -p 8080:3000 \
		-v $(PWD):/app node:alpine yarn start
test: yarn
	docker run -ti --rm -w /app -v $(PWD):/app node:alpine yarn test
prod: build
	docker run -ti --rm --name my-nginx -p 8080:80 \
		-v $(PWD)/build:/usr/share/nginx/html nginx

Caso um novo desenvolvedor ingresse no time de desenvolvimento,  basta possuir o docker instalado e executar o comando make dev na raiz do projeto que as dependências serão baixadas e um servidor de desenvolvimento no endereço http://localhost:8080 será iniciado. Da mesma forma, o seu servidor de produção não precisará ter nenhuma biblioteca instalada, apenas o Docker, utilizado o comando **make prod **uma versão otimizada será gerada e servida através do Nginx, totalmente alinhado com o ambiente dos desenvolvedores.

Composições complexas

O exemplo apresentado no post mostra o gerenciamento de ambientes através de imagens Docker já existentes, no entanto é possível criar suas próprias imagens e hospedá-las gratuitamente no Docker Hub, tornando-as acessíveis publicamente. Você encontra mais detalhes aqui.

Existem outras ferramentas como o docker-compose, que permite a integração de vários containers através de um único arquivo na qual são definidos algumas propriedades como volumes, portas, variáveis de ambiente, entre outras coisas, simplificando ainda mais o gerenciamento de serviços.

Juntamente com exemplo apresentado (Nginx + React), o repositório deste post no Github apresenta outros exemplos:

Que é um assunto para um post futuro.

comments powered by Disqus