Docker Compose: Como criar vários containers de forma automatizada

Exemplo prático com uma API + MongoDB + Redis sendo provisionados

Publicado por Lohan Bodevan 13 de Junho de 2017 às 09:04

Introdução

Como já vimos aqui antes, o Docker é uma ferramenta muito útil para isolar ambientes e rodar serviços, sejam eles banco de dados, servidores e softwares. 

Vimos também como criar containers e fazer com que eles se comuniquem porém de forma manual, subindo os containers um a um. Neste post vamos ver uma forma mais fácil, pratica e automatizada de fazer isto, utilizando o Docker Compose.

Docker Compose é uma parte do conjunto de ferramentas Docker, ela é muito útil para criar ambientes “multi-containers” facilitando provisionar todos esses serviços com um único comando.

O que você precisa saber antes de começar

Recomendo que, caso ainda não o tenha feito, leia este post primeiro. Ele lhe dará uma boa visão sobre o Docker e seus conceitos.

Preparando o ambiente

Se você tem a versão mais atual do Docker instalada então você tem o Docker Compose instalado. Para verificar a instalação você pode tentar:

$ docker-compose --version

Ação

Para o nosso caso de estudos vamos imaginar a seguinte arquitetura:

  • Uma API simples que contém 2 endpoints. Um para criação dos dados e outro para leitura desses dados;

  • Um banco, MongoDB, para salvar os recursos;

  • E um banco de dados em memória, Redis, fará o cache de leitura dos recursos; 

Bem simples não é mesmo?

O objetivo do Docker Compose é provisionar cada serviço desses em um container isolado e fazer a conexão entre aqueles e somente aqueles que devem se comunicar.

Para facilitar, o código da API (em GO) já está pronto e não entraremos em detalhes sobre ela pois o foco aqui é como provisionar esse ambiente e não a aplicação em si. Caso queira ver mais sobre o código, você pode ver aqui.

Para provisionar esse ambiente nós precisaremos de um arquivo YAML chamado docker-compose.yml. Ele é o responsável por declara como os containers dos serviços que falamos acima (API GO, MongoDB e Redis) deverão ser criados. Este aquivo deve ser versionado junto com o código fonte do ambiente que ele irá provisionar, isso permite você distribuir seu sistema e garantir que todos que estão "levantando" ele, estão fazendo de forma única.

Vamos dar uma olhada no arquivo e logo baixo ver no detalhe cada instrução utilizada:

version: '3'
services:
 web:
   image: golang:1.7
   ports:
     - "8080:8080"
   environment:
     - PORT=8080
     - DB_HOST=mongo_db
     - DB_NAME=gomonred
     - CACHE_HOST=cache
     - CACHE_PORT=6379
     - CACHE_DEFAULT_TTL=2m
   volumes:
     - .:/go/src/github.com/lohanbodevan/gomonred
   working_dir: /go/src/github.com/lohanbodevan/gomonred
   command: make run
   links:
     - cache
     - db:mongo_db
 cache:
   image: redis
 db:
   image: mongo

version

Esta primeira chave diz respeito a versão do docker-compose que estamos utilizando. Quando esse post foi escrito, a versão mais atual era a versão 3 mas também foi testado na versão 2.

services

O bloco services, como o nome sugere, deve conter a configuração dos serviços que irão ser provisionados. Pegaremos como exemplo o primeiro serviço nomeado web.

Este nome foi escolhido por mim, porém poderia ser qualquer outro. É importante que os nomes façam sentido para você e você saiba facilmente o objetivo dele. A proposta dos arquivos YAML é ser human-readable, então capriche.

image

Uma das chaves mais importantes de um item do bloco services é a chave image. Ela é responsável por informar, com base em qual imagem Docker nosso container será construído. No exemplo do serviço web estamos utilizando a imagem golang:1.7. É possível também na chave image informar o caminho de um arquivo Dockerfile. 

ports

Este bloco ou lista, que também pertencente ao bloco web dirá quais portas o container da API irá exportar. Esta chave é equivalente ao argumento -p no comando run do Docker, a porta a esquerda é a externa, ou seja, a que iremos utilizar para fazer requests para o container. E a porta a direita é a porta que a API, internamente no container, irá utilizar. No nosso exemplo será a mesma, mas poderíamos rodar a API na porta 80 internamente no container e externamente utilizar a 8080. Isso é o que chamamos de forward de portas.

environment

Esta chave permite setarmos variáveis de ambiente no container a ser criado.

volumes

Equivalente ao argumento -v do comando run do Docker, este bloco ou lista é responsável por declarar as pastas locais que serão montadas dentro do container. Em geral configuramos nesse trecho onde iremos montar o código fonte da aplicação, dentro do container.

working_dir

Esta chave diz qual é pasta default que o container irá se basear quando for executar os comandos que passarmos para ele.

command

Muito importante para o funcionamento do nosso container, esta chave diz qual comando ele deve executar após ser criado, equivalente a instrução CMD que utilizamos no arquivo Dockefile. Em geral colocamos aí comando de inicialização de nossa aplicação.

links

Por último e também um dos mais importantes blocos dentro do nosso item web, ele vai dizer quais as conexões que este container terá. Ou seja, com quais outros containers o container web irá se comunicar.

E aqui no nosso exemplo, tem algo interessante. Temos um primeiro item chamado cache e um segundo com algo que podemos chamar de mapeamento (db:mongo_db).

Utilizei essa prática propositalmente para esclarecer que o nome de um serviço declarado no docker-compose.yml, pode ter um mapeamento diferente dentro do container que o utiliza como link.

Deixando mais claro, dentro do container do serviço web, o serviço cache se chama cache mesmo mas, o serviço db será acessível pelo hostname mongo_db.

Acredito que você tenha reparado que os nomes cache e db no bloco links são referentes aos dois outros serviços configurados logo abaixo do docker-compose.yml

Testando o Ambiente

Faça um teste, clone o repositório deste post, e da raiz do projeto execute o seguinte comando:

$ docker-compose up

Isto irá disparar a criação de todos os serviços listados no docker-compose.yml. Ao final de todo o provisionamento, você poderá fazer chamadas a API e verificar que ela consegue se comunicar com os 2 outros containers do Redis e MongoDB:

Comando cURL para criação de dados na API:

curl -X POST -H "Content-Type: application/json" -d '{
"name": "Uno",
"brand": "Fiat"
}' "http://localhost:8080/cars"

Comando cURL para buscar dados na API:

curl -X GET "http://localhost:8080/cars"

Ao executar esses dois comandos você deve conseguir criar e buscar dados da API. Se executar uma segunda vez o comando para buscar dados da API você verá nos logs que a API irá encontrar os dados apartir do cache.

Veja print abaixo:

Requests para o container

 Conclusão

Bom, espero que tenha ficado claro neste post, que faz uma simples introdução ao Docker Compose, como é fácil provisionar ambientes com ele. Com apenas um comando nós levantamos 3 containers, com imagens e propósitos diferentes, que rodam serviços diferentes.

Você vai reparar também que apesar de conectado, esse ambiente continua isolado, o container do MongoDB não tem acesso ao container do Redis, pois de fato não faz sentido ter. No entanto o container da API, tem acesso tanto ao container do MongoDB quanto do Redis.

Qualquer dúvida deixe um comentário.

Abraço!


Comentários