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 Python, WordPress, 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
- docker run - Comando que inicia um container Docker
- –rm - Indica que o container deve ser removido assim que ele finalizar execução
- -ti - Emula um interface de terminal interativa
- –name my-nginx - Define um nome para um container. Caso não informado, um nome aleatório será gerado
- -p 8080:80 - Define o mapeamento de portas, a porta 80 do Nginx será redirecionada para a porta 8080 da sua máquina
- -v $(pwd)/build:/usr/share/nginx/html - Monta um volume no container, substituindo o conteúdo das pasta html pela pasta build do nosso projeto. ($(pwd) é uma variável de ambiente contendo o endereço do diretório corrente)
- nginx - nome da imagem a ser executada
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"
- docker run –rm - Cria um container temporário (Apagado após finalização)
- -w /app - Define o diretório de trabalho (workdir), local onde o comando pós-inicialização será executado.
- -v $(pwd):/app - Cria um volume no diretório /app do container com todos os arquivos do projeto, ($(pwd) = Diretório corrente)
- node:alpine - Nome da imagem a ser executada, juntamente com a tag (versão da imagem) “alpine”.
- sh -c “yarn && yarn build” - comando executado após inicialização do container
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:
- Golang + Docker Build
- WordPress + MySQL com docker-compose
Que é um assunto para um post futuro.