DEV Community

Cover image for Docker
Isaac Alves Pinheiro
Isaac Alves Pinheiro

Posted on

Docker

O Docker é um software/ plataforma open source para construir, armazenar, distribuir e rodar contêineres (containers são ambientes virtualizados, isolados e voláteis), o qual permite a equipe emular ambientes de desenvolvimento e que empacota um todo sistema operacional, como também a sua aplicação para dentro de um container.

Em 2013, foi criado o Docker, escrito na linguagem Go (Golang), por Solomon Hykes, onde o Docker começou como um projeto interno, inicialmente desenvolvido pelo dotCloud engineers, essa empresa é uma PaaS (Plataform as a Service), uma empresa que você manda o código da sua aplicação feita em Java, PHP, Node ou Python etc e a empresa toma todo o cuidado possível para você hospedar sua aplicação. Um exemplo de empresas PaaS são as empresas:

  • Heroku;
  • AWS - Amazon Web Services;
  • GCP - Google Cloud Plataform;
  • AZ - Microsoft Azure;

Quando o Docker nasceu, a dotCloud utilizava a AWS - Amazon Web Services, que é um serviço que provê VMs ou máquinas físicas, então ela queria hospedar na VM a aplicação dela, porém ela usou seu sistema de containers para subir sua aplicação, e assim nasceu o Docker, um intermediário entre um sistema operacional e seus containers contendo aplicações em uma mesma máquina.

De desktop a cloud, o Docker é considerado padrão e a ferramenta mais popular: é usado por milhões de desenvolvedores para construir e compartilhar aplicações conteinerizadas. Para termos uma ideia de volume, 30% das empresas usavam Docker em ambientes AWS, em 2019.

Embora seja uma das ferramentas de conteinerização mais usadas e conhecidas, o Docker não está sozinho. Há outros fornecedores no ecossistema: ConteinerD, CoreOS, Canonical, LXC Linux Conteiners, CRIO-D, OpenShift e Mesos Containerizer.

Além da empresa dotCloud ser focada em manter o Docker, ela também liberou o Docker para Open Source, exatamente isso, o Docker possui também código-livre para desenvolvimento!

Está tudo lá, as bibliotecas, as contribuições para o desenvolvimento de tecnologias do Docker.

Vantagens de um container Docker


Por não possuir um sistema operacional, o container é muito mais leve e não possui o custo de manter múltiplos sistemas operacionais, já que só teremos um sistema operacional, que será dividido entre os containers.

Além disso, por ser mais leve, o container é muito rápido de subir, subindo em questão de segundos. Logo, o container é uma solução para suprir o problema de múltiplas máquinas virtuais em um hardware físico, já que com o container, nós dividimos o sistema operacional entre as nossas aplicações.

Volátil, ou seja, sobe e mata um container, é da natureza do container sumir e subir rapidamente.

Necessidade do uso de containers Docker

Mas por que precisamos dos containers, não podemos simplesmente instalar as aplicações no nosso próprio sistema operacional? Até por que já fazemos isso, já que no nosso sistema operacional temos um editor de texto, terminal, navegador, etc.

No caso das nossas aplicações, essa abordagem pode ter alguns problemas. Por exemplo, se dois aplicativos precisarem utilizar a mesma porta de rede? Precisaremos de algo para isolar uma aplicação da outra. E se uma aplicação consumir toda a CPU, a ponto de prejudicar o funcionamento das outras aplicações? Isso acontece se não limitarmos a aplicação. Outro problema que pode ocorrer é cada aplicação precisar de uma versão específica de uma linguagem, por exemplo, uma aplicação precisa do Java 7 e outra do Java 8. Além disso, uma aplicação pode acabar congelando todo o sistema. Por isso é bom ter essa separação das aplicações, isolar uma da outra, e isso pode ser feito com os containers.

Com os containers, conseguimos limitar o consumo de CPU das aplicações, melhorando o controle sobre o uso de cada recurso do nosso sistema (CPU, rede, etc). Também temos uma facilidade maior em trabalhar com versões específicas de linguagens/bibliotecas, além de ter uma agilidade maior na hora de criar e subir containers, já que eles são mais leves que as máquinas virtuais.

Vamos agora ver as diversas tecnologias presentes no Docker e como são incríveis as possibilidades de se trabalhar em cada uma delas!

O ecossistema/ambiente do container Docker


O ambiente do container é todo um ecossistema de desenvolvimento, ou seja, toda uma infraestrutura com:

  • Os servidores web em NGINX ou Apache;
  • Banco de dados como PostgreSQL, MySQL, MongoDB, Cassandra, etc;
  • Os interpretadores de linguagem de programação como Python, Node.js, PHP, GO, Ruby, etc;
  • As bibliotecas e frameworks utilizadas para desenvolvimento, como o React.js, Django, Spring, etc.;
  • As scripts e plugins são arquivos de bash-script ou shell-script responsáveis por executar uma determinada ação no container, como: Automatizar alguma ação manual, Instalar programas adicionais, criar ou mudar configurações, preencher ambiente, etc.
  • As variáveis de ambiente são arquivos responsáveis por guardar segredos relacionados a qualquer informação sigilosa.

Então, é basicamente tudo o que a gente instala no nosso sistema operacional (Windows, Mac OS ou Linux) para conseguir fazer o desenvolvimento de software e rodar uma aplicação nela.

Com o Docker é possível resolver aquele velho problema dos desenvolvedores:

> Como assim!? Na minha máquina funciona, na sua não?

Etapas e ferramentas/modos do Docker


Além disso, existem algumas "etapas" com terminologias para a criação desses containers, pois afinal são uma evolução de VMs e não são instaladas por padrão na nossa máquina pessoal. São elas:

  • Image: Pacote com todas as dependências para criar o nosso container (nossa aplicação rodando no container);
  • Dockerfile: Arquivo de texto que contém todas as instruções para iniciar o processo de Build na nossa imagem do container;
  • Build: É uma ação que cria a imagem a partir do Dockerfile (lê as instruções e cria uma imagem);
  • Container: Feito a imagem pelo Build é instanciado o container (execução de uma aplicação, ou processo ou um serviço);
  • Volumes: Armazena dados em disco, ou seja, se o container morrer é o volume que salva os dados do container (os dados do container não serão perdidos dentro do volume);
  • Tags: Ajuda no versionamento das nossas imagens;
  • Multi-stage Build: Múltiplos estágios de Build onde usamos uma imagem para compilar a nossa aplicação, e essa chama outra imagem (Roy na Aplicação);
  • Repository: Nada mais é do que uma coleção de imagens, então é como se fosse uma caixa cheia de imagens;
  • Registry: É um serviço que provê acesso do Docker ao repositório, como o Docker Hub (repositório remoto de imagens de containers públicas e privadas);
  • Compose: É uma metadata que você pode executar múltiplos containers com um simples comando.

Docker Engine

As tecnologias de containers para prover ferramentas modernas para deployar e rodar aplicações.


Docker Compose

O Docker Compose é um modo do Docker onde podemos escalar e diminuir o número de containers necessário para nossa aplicação rodar com melhor aproveitamento, ou seja, é um jeito simples de definir múltiplos containers, o que é ideal para uma stack de tecnologia (.NET, Spring, LAMP, LEMP, MEAN, MERN, MENV, RoR, FReMP, etc), utilizar um balanceador de carga, banco de dados e até adotar a arquitetura de micro serviços.

No entanto, para aplicações e arquiteturas mais robustas é um pouco limitado em utilidades, se comparado ao modo Docker Swarm.

Docker Swarm

O Docker Swarm é um modo do Docker com uma ferramenta para orquestrar múltiplos docker engines para trabalharem juntos em um cluster com dezenas ou centenas de containers. As versões atuais do Docker incluem o modo swarm para gerenciar nativamente um cluster de Docker Engines chamado swarm (enxame). Use a CLI do Docker para criar um swarm, implantar serviços de aplicativo em um swarm e gerenciar o comportamento do swarm.

O modo Docker Swarm é integrado ao Docker Engine. E possui algumas utilidades:

  • Gerenciamento de cluster integrado ao Docker Engine: Use a CLI do Docker Engine para criar um enxame de Docker Engines onde você pode implantar serviços de aplicativos. Você não precisa de software de orquestração adicional para criar ou gerenciar um enxame.

  • Design descentralizado: em vez de lidar com a diferenciação entre as funções do nó no momento da implantação, o Docker Engine lida com qualquer especialização no tempo de execução. Você pode implantar os dois tipos de nós, gerentes e trabalhadores, usando o Docker Engine. Isso significa que você pode criar um enxame inteiro a partir de uma única imagem de disco.

  • Modelo de serviço declarativo: o Docker Engine usa uma abordagem declarativa para permitir que você defina o estado desejado dos vários serviços em sua pilha de aplicativos. Por exemplo, você pode descrever um aplicativo composto por um serviço de front-end da Web com serviços de enfileiramento de mensagens e um back-end de banco de dados.

  • Dimensionamento: Para cada serviço, você pode declarar o número de tarefas que deseja executar. Quando você aumenta ou diminui, o gerenciador de enxame se adapta automaticamente adicionando ou removendo tarefas para manter o estado desejado.

  • Reconciliação do estado desejado: O nó do gerenciador de swarm monitora constantemente o estado do cluster e reconcilia quaisquer diferenças entre o estado real e o estado desejado expresso. Por exemplo, se você configurar um serviço para executar 10 réplicas de um contêiner e uma máquina de trabalho que hospeda duas dessas réplicas falhar, o gerente criará duas novas réplicas para substituir as réplicas que falharam. O gerenciador de swarm atribui as novas réplicas aos trabalhadores que estão em execução e disponíveis.

  • Rede multi-host: você pode especificar uma rede de sobreposição para seus serviços. O gerenciador de swarm atribui automaticamente endereços aos contêineres na rede de sobreposição quando inicializa ou atualiza o aplicativo.

  • Descoberta de serviço: os nós do Swarm Manager atribuem a cada serviço no Swarm um nome DNS exclusivo e equilibram a carga dos contêineres em execução. Você pode consultar todos os contêineres em execução no enxame por meio de um servidor DNS incorporado no enxame.

  • Balanceamento de carga: você pode expor as portas para serviços a um balanceador de carga externo. Internamente, o enxame permite especificar como distribuir contêineres de serviço entre os nós.

  • Seguro por padrão: cada nó no enxame impõe autenticação e criptografia mútuas TLS para proteger as comunicações entre ele e todos os outros nós. Você tem a opção de usar certificados raiz autoassinados ou certificados de uma CA raiz personalizada.

  • Atualizações contínuas: no momento da implementação, você pode aplicar atualizações de serviço aos nós de forma incremental. O gerenciador de enxame permite controlar o atraso entre a implantação do serviço em diferentes conjuntos de nós. Se algo der errado, você pode reverter para uma versão anterior do serviço.

Docker Machine

Uma ferramenta que nos permite instalar e gerenciar o Docker Engine em hosts virtuais, com comandos docker-machine.

Você pode usar o Machine para criar hosts do Docker em sua caixa local do Mac ou Windows, na rede da empresa, no data center ou em provedores de nuvem como Azure, AWS ou Digital Ocean.

> O Docker encerrou o desenvolvimento ativo e o suporte para o Docker Machine. Um método principal para escalonamento automático do GitLab runner em instâncias de máquina virtual de nuvem pública é o Runner configurado com o executor do Docker Machine.

Docker Hub Docker HUB

Um repositório com mais de 250 mil imagens diferentes para os nossos containers. Para instalar, você precisa ter os seguintes requisitos:

  • Um sistema de 64 bits;
  • Se você utiliza o Windows 10, você precisar utilizar a versão Windows 10 PRO;
  • Habilitar a virtualização do Windows;

Play with Docker

Esse é um ambiente totalmente grátis para testar a todos os recursos do Docker pela web, o que facilita muito no aprendizado para iniciantes! Com práticas e exercícios.

Você verá que cada instância é um nó com seu endereço de IP e tem tempo para escalar seus ambientes antes que eles sejam removidos.

> Em exemplos posteriores, vamos utilizar o modo Docker Swarm com Flask e Hashicorp Consul como malha de serviço!

Instalação do Docker

Linux

sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker --now
docker
Enter fullscreen mode Exit fullscreen mode

Windows

  • Habilitar a Virtualização de CPU na BIOS (programa da placa-mãe) do seu computador
  • Baixar o pacote de atualização do kernel do Linux (WSL 2)

https://docs.microsoft.com/pt-br/windows/wsl/install-win10#step-4---download-the-linux-kernel-update-package

Sem título

Seguimos o passo a passo do instalador para aceitar a licença, autorizamos o instalador e seguimos com a instalação. Ao clicar em Finish, precisamos encerrar a sessão do Windows e iniciá-la novamente. Ao fazer o login, precisamos habilitar o Hyper-V, clicando em Ok, para que o computador será reiniciado.

Quando o computador terminar a reinicialização, irá aparecer um ícone do Docker na barra inferior, à direita, ao lado do relógio. O Docker pode demorar um pouco para inicializar, mas quando a mensagem Docker is running for exibida, significa que ele foi instalado com sucesso e já podemos utilizá-lo.

Exibir a versão do Docker

docker --version
Enter fullscreen mode Exit fullscreen mode

Exibir os todos os dados do Docker

docker version
Enter fullscreen mode Exit fullscreen mode

Lembrando que você precisa estar executando o Docker na sua máquina.

Hello, World! - Docker

docker run hello-world
Enter fullscreen mode Exit fullscreen mode

Lembrando que quando aparece a seguinte mensagem no terminal Unable to find image 'hello-world' locally, significa que o docker não achou a imagem localmente, mas sim precisou buscar a imagem e baixar ela!

Sem título

Imagens e Containers

Na aula anterior, para executar a imagem hello-world, executamos o comando docker run hello-world.

Quando executado esse comando, a primeira coisa que o Docker faz é verificar se temos a imagem hello-world no nosso computador, e caso não tenhamos, o Docker buscará e baixará essa imagem no Docker Hub/Docker Store.

A imagem é como se fosse uma receita de bolo, uma série de instruções que o Docker seguirá para criar um container, que irá conter as instruções da imagem, do hello-world. Criado o container, o Docker executa-o. Então, tudo isso é feito quando executamos o docker run hello-world.

Há milhares de imagens na Docker Store disponíveis para executarmos a nossa aplicação. Por exemplo, temos a imagem do Ubuntu:

Rodar a imagem do Sistema Operacional Ubuntu

docker run ubuntu
Enter fullscreen mode Exit fullscreen mode

É normal o nome do container ser seguida de nomes aleatórios, não tem problema nenhum! a imagem possui o nome escrita em azul para você identificar no Docker Desktop, assim como no terminal também informa.

Listar todos os containers ativos no momento

docker ps
Enter fullscreen mode Exit fullscreen mode

Listar todos os containers que eu criei

docker ps -a
Enter fullscreen mode Exit fullscreen mode

Listar todos os containers que eu criei (somente os id's)

docker ps -q
Enter fullscreen mode Exit fullscreen mode

Executar um Olá, Mundo dentro do container do Ubuntu

docker run ubuntu echo "Ola Mundo"
Enter fullscreen mode Exit fullscreen mode

Interagir mais com o container do Ubuntu

docker run -it ubuntu
Enter fullscreen mode Exit fullscreen mode

Dessa forma, ele retorna o terminal do Ubuntu:

root@9fea4151a9f8:/#

Se você rodar qualquer comando do Linux irá funcionar, porém não dentro do Sistema Operacional, mas sim dentro do container.

root@9fea4151a9f8:/# ls
Enter fullscreen mode Exit fullscreen mode

Retorno:

bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

Para sair é só ctrl+D ou exit.

Startando o container

docker ps -a
docker start [id do container]
docker ps
Enter fullscreen mode Exit fullscreen mode

Como também:

docker start -a -i [3 dígitos iniciais do id do container]
Enter fullscreen mode Exit fullscreen mode

Agora vamos brincar, digite a série de comandos de entrada abaixo:

ls
touch hello-world.txt
ls
echo "Hello, World!" > hello-world.txt
cat hello-world.txt
Enter fullscreen mode Exit fullscreen mode

Saída:

Hello, World!

Startando o container de forma interativa

docker start -it [nome do container]
Enter fullscreen mode Exit fullscreen mode

Sai do container interativo, mas desejo voltar novamente

docker start -a -i [id-do-container]
Enter fullscreen mode Exit fullscreen mode

Ou também você pode usar a seguinte forma:

docker start [nome container]
docker exec -it [nome do container] bash
Enter fullscreen mode Exit fullscreen mode

Parar o container

docker stop [id do container]
Enter fullscreen mode Exit fullscreen mode

Existem formas mais eficientes de rodar ou parar o docker, pois a execução as vezes demora muito...Para saber mais, use:

docker stop --help
Enter fullscreen mode Exit fullscreen mode

Então, com a lista de parâmetros, utilizaremos o parâmetro -t e com ele podemos, instantaneamente, executar o tempo de uma execução no docker, como:

docker stop -t 0 [3 dig. do id do container]
Enter fullscreen mode Exit fullscreen mode

Parar mais de um id

docker stop $(docker ps -q)
Enter fullscreen mode Exit fullscreen mode

ou

docker stop -t 0 $(docker ps -q)
Enter fullscreen mode Exit fullscreen mode

Layered File System

Aqueles dois principais estados de um container, quando criamos um ou iniciamos, ele fica no estado de running, e quanto a sua execução encerra ou
paramos, ele fica no estado de stopped:

Limpar container

docker prune
Enter fullscreen mode Exit fullscreen mode

Limpar containers inativos

docker container prune
Enter fullscreen mode Exit fullscreen mode

Deletar ou Remover containers

docker rm [id do container]
Enter fullscreen mode Exit fullscreen mode

Deletar todos os containers

docker container prune
Enter fullscreen mode Exit fullscreen mode

Remover ou Deletar as imagens do Docker

docker image prune
Enter fullscreen mode Exit fullscreen mode

Remover ou Deletar todas as imagens do Docker

docker system prune
Enter fullscreen mode Exit fullscreen mode

Remover ou deletar um Volume

docker volume prune
Enter fullscreen mode Exit fullscreen mode

Forçar remover um container

docker rm -f [id do container]
Enter fullscreen mode Exit fullscreen mode

Listando imagens

docker images
Enter fullscreen mode Exit fullscreen mode

Removendo imagens

docker rmi [container]
Enter fullscreen mode Exit fullscreen mode

Ver logs de um container

docker logs [ID do container]
Enter fullscreen mode Exit fullscreen mode

E do mesmo jeito que temos o comando docker container para mexermos com o container, temos o comando docker images, que nos exibe as imagens que temos na nossa máquina. Para remover uma imagem, utilizarmos o comando docker rmi, passando para ele o nome da imagem a ser removida, por exemplo:

Rodar uma determinada versão da imagem

docker run ubuntu:14.04
Enter fullscreen mode Exit fullscreen mode

Camada de imagens

Quando baixamos a imagem do Ubuntu, reparamos que ela possui camadas, mas como elas funcionam? Toda imagem que baixamos é composta de uma ou mais camadas, e esse sistema tem o nome de Layered File System.

As pricincipais características das camadas, são:

  • As camadas na imagem são de leitura apenas;
  • Essas camadas podem ser reaproveitadas em outras imagens, acelerando assim o tempo de download;
  • Toda imagem que baixamos é composta de uma ou mais camadas.

Essas camadas podem ser reaproveitadas em outras imagens. Por exemplo, já temos a imagem do Ubuntu, isso inclui as suas camadas, e agora queremos baixar a imagem do CentOS. Se o CentOS compartilha alguma camada que já tem na imagem do Ubuntu, o Docker é inteligente e só baixará as camadas diferentes, e não baixará novamente as camadas que já temos no nosso computador:

No caso da imagem acima, o Docker só baixará as duas primeiras camadas da imagem do CentOS, já que as duas últimas são as mesmas da imagem do Ubuntu, que já temos na nossa máquina. Assim poupamos tempo, já que precisamos de menos tempo para baixar uma imagem.

Uma outra vantagem é que as camadas de uma imagem são somente para leitura. Mas como então conseguimos criar arquivos na aula anterior? O que acontece é que não escrevemos na imagem, já que quando criamos um container, ele cria uma nova camada acima da imagem, e nessa camada podemos ler e escrever:

Então, quando criamos um container, ele é criado em cima de uma imagem já existente e nele nós conseguimos escrever. E com uma imagem base, podemos reaproveitá-la para diversos containers:

Isso nos traz economia de espaço, já que não precisamos ter uma imagem por container.

Praticando com o docker run

Já podemos fazer um container mais interessante, um pouco mais complexo. Então, vamos criar um container que segurará um site estático, para entendermos também como funciona a parte de redes do Docker. Para tal, vamos baixar a imagem dockersamples/static-site:

docker run dockersamples/static-site
Enter fullscreen mode Exit fullscreen mode
As imagens não oficiais, ou seja, imagens de terceiros, precisam ser especificadas pelo username e pela imagem.

Baixando a imagem apenas pelo docker, sem atrelar ao terminal

docker run -d dockersamples/static-site
Enter fullscreen mode Exit fullscreen mode

Se você executar os dois comandos, criará dois containers diferentes, pois o Docker é inteligente no quesito de solicitações de imagem, ele sabe criar um id e ambientes diferentes!

Acessando o site

docker run -d -P [nome da imagem]
Enter fullscreen mode Exit fullscreen mode

A flag -P significa que fará com que o Docker atribua uma porta aleatória do mundo externo, que no caso é a nossa máquina, para poder se comunicar com o que está dentro do container.

Abrindo uma porta externa para uma interna

Vamos ver como abrir portas e acessar o site com elas.

Descobrir a porta

docker ps
Enter fullscreen mode Exit fullscreen mode

Descobrir a porta de uma maneira mais legível

docker port [id do container]
Enter fullscreen mode Exit fullscreen mode

Logo, ele retornará a porta pelo protocolo TCP:

80/tcp -> 0.0.0.0:49155
80/tcp -> :::49155
443/tcp -> 0.0.0.0:49154
443/tcp -> :::49154

Se você copiar o endereço:

:49155
Enter fullscreen mode Exit fullscreen mode

E ir para o browser digitando na aba do navegador:

localhost:49155
Enter fullscreen mode Exit fullscreen mode

Você verá o preview do site estático!

Sem título

Nomeando um container

docker run -d -P --name [nome do container] [nome da imagem]
Enter fullscreen mode Exit fullscreen mode

Acessando os detalhes do container

dive [nome do container]
Enter fullscreen mode Exit fullscreen mode

Dessa forma, ele irá nomear o container que possuia um nome aleatório, isso facilita nosso aprendizado e nossos projetos.

Renomeando um container

docker rename [nome ou id do container] [novo nome]
Enter fullscreen mode Exit fullscreen mode

Mapeando portas do computador para a porta do servidor

docker run -d -p 12345:80 [nome da imagem]
Enter fullscreen mode Exit fullscreen mode

Assim, ele muda o endereço da porta do localhost para 12345.

docker

Definindo a variável autor

docker run -d -P -e AUTHOR="seu username" [imagem]
Enter fullscreen mode Exit fullscreen mode

Dessa forma, ele apresentará você no site estático. Mas primeiro acesse o endereço ip que ele mudou!

docker ps
Enter fullscreen mode Exit fullscreen mode

Dessa foma, coloque junto ao localhost.

docker

Docker para DevOps

DevOps é um tópico bem amplo que envolve tanto aspectos culturais como técnicos, mas possui como principal objetivo aumentar a qualidade e eficiência da entrega de software. DevOps é uma metodologia que visa integrar os times de desenvolvimento com infraestrutura e o Docker está tendo um papel importante nessa tarefa.

Repare que com Docker os desenvolvedores não precisam se preocupar em configurar um ambiente de desenvolvimento específico de cada vez. Em vez disso, eles podem se concentrar na construção de um código de boa qualidade. Isso, obviamente, leva à aceleração nos esforços de desenvolvimento. O Docker facilita muito construir o ambiente: é rapido, simples e confiável.

No outro lado, para a equipe de operações de TI/Sysadmins, o Docker possibilita configurar ambientes que são exatamente como um servidor de produção e permite que qualquer pessoa trabalhe no mesmo projeto com exatamente as mesmas configurações, independentemente do ambiente de host local. As configurações são descritas em arquivos simples facilmente aplicáveis pelo desenvolvedor.

Com a padronização de um entregável Docker é possível que o desenvolvedor tenha um ambiente similar ao de produção na sua máquina sem todo o custo de configuração e o Sysadmin consiga lidar apenas com um tipo de entregável conseguindo, desta forma, dar atenção aos desafios de monitoramento e orquestração para que nada dê errado. Neste caso, o melhor para os dois.

Salvando dados com volumes

É da natureza dos containers a volatilidade, isto é, eles são criados e removidos rapidamente e facilmente, mas devemos ter um lugar para salvar os dados, e esse lugar são os volumes.

Quando escrevemos em um container, assim que ele for removido, os dados também serão. Mas podemos criar um local especial dentro dele, e especificamos que esse local será o nosso volume de dados.

Quando criamos um volume de dados, o que estamos fazendo é apontá-lo para uma pequena pasta no Docker Host. Então, quando criamos um volume, criamos uma pasta dentro do container, e o que escrevermos dentro dessa pasta na verdade estaremos escrevendo do Docker Host. Ou seja, fica salvo no computador onde a Docker Engine está rodando.

Isso faz com que não percamos os nossos dados, pois o container até pode ser removido, mas a pasta no Docker Host ficará intacta.

Trabalhando com volumes

Sabendo disso, vamos ver como trabalhar com o Docker Host. No Terminal ou PowerShell (ou Docker Quickstart Terminal), criamos um container com o docker run, mas dessa vez utilizando a flag -v para criar um volume, seguido do nome do mesmo:

docker run -v "/var/www" ubuntu
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, criamos o volume /var/www, mas a que pasta no Docker Host ele faz referência? Para descobrir, podemos inspecionar o container, executando o comando docker inspect, passando o seu id para o mesmo:

Inspecionando o container

docker inspect [id do container]
Enter fullscreen mode Exit fullscreen mode

Ele retornará uma série de dados, mas o mais importante, no momento, é o Mounts:

      "Mounts": [
            {
                "Type": "volume",
                "Name": "54a5fad5f6c046a4afad7854caf90089a40d9c9d60d3eb604982b6084b799cc2",
                "Source": "/var/lib/docker/volumes/54a5fad5f6c046a4afad7854caf90089a40d9c9d60d3eb604982b6084b799cc2/_data",
                "Destination": "/var/www",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
         ]

Mesmo se eu remover o container, mas se tiver algo escrito no destination /var/www ele ainda vai existir, pois o que está escrito lá está ligada ao Docker Host, como se fosse um link.

Portanto, os dados vão continuar ainda dentro do computador.

Iniciando uma escrita no volume (montando ou linkando em /var/www pelo desktop)

docker run -it -v "C:\Users\Alura\Desktop:/var/www" ubuntu
Enter fullscreen mode Exit fullscreen mode

Os : servem para separar os dois meios do container que vai rodar a imagem do ubuntu.

Funcionou, então:

root@abd0286c0083:/#
ls
cd var/www/
touch novo-arquivo.txt
echo "este arquivo foi criado dentro de um volume" > novo-arquivo.txt
cat novo-arquivo.txt
touch segundo-arquivo.txt
ls
Enter fullscreen mode Exit fullscreen mode

O que acontece se eu remover esse container?

Continuando o tópico acima, vamos sair do container e remover ele:

exit
docker ps -a
docker container prune
Enter fullscreen mode Exit fullscreen mode

Aquela camada do container foi removida, porém os seus arquivos ainda permaneceram na sua Desktop!

[Stand alone] Rodando código em apenas um container Docker

Já vimos que o que escrevemos no volume (pasta /var/www do container) aparece na pasta configurada da nossa máquina local, que no vídeo anterior foi o Desktop. Mas podemos pensar o contrário, ou seja, tudo o que escrevemos no Desktop será acessível na pasta /var/www do container.

Isso nos dá a possibilidade de implementar localmente um código de uma linguagem que não está instalada na nossa máquina, e colocá-lo para compilar e rodar dentro do container. Se o container possui Node, Java, PHP, seja qual for a linguagem, não precisamos tê-los instalados na nossa máquina, nosso ambiente de desenvolvimento pode ser dentro do container.

É isso que faremos, pegaremos um código nosso, que está na nossa máquina, e colocaremos para rodar dentro do container, utilizando essa técnica com volumes.

Para isso, vamos usar um exemplo escrito Node.js. Até podemos executar esse código na nossa máquina, mas temos que instalar o Node na versão certa em que o desenvolvedor implementou o código.

Agora, como fazemos para criar um container, que irá pegar e rodar esse código Node que está na nossa máquina? Vamos utilizar os volumes. Então, vamos começar a montar o comando.

Primeiramente, como vamos rodar um código em Node.js, precisamos utilizar a sua imagem:

docker run node
Enter fullscreen mode Exit fullscreen mode

Ou

docker run -d -P --name running-node.js node
Enter fullscreen mode Exit fullscreen mode
docker ps -a
docker ps -a -q
Enter fullscreen mode Exit fullscreen mode

Além disso, precisamos criar um volume, que faça referência à pasta do código no nosso Desktop:

docker run -it -v "C:\Users\Dell\Desktop\volume-exemplo:/var/www" node
Enter fullscreen mode Exit fullscreen mode

Mas para fazer esse servidor rodar, precisamos inserir o famoso NPM (Node Package Manager), com o comando npm start:

docker run -it -v "C:\Users\Dell\Desktop\volume-exemplo:/var/www" node npm start
Enter fullscreen mode Exit fullscreen mode

Mas de acordo com a nossa aplicação, está programada para abrir a porta (endereço de ip) :3000:

docker run -p 8080:3000 -v "C:\Users\Dell\Desktop\volume-exemplo:/var/www" node npm start
Enter fullscreen mode Exit fullscreen mode

Não funcionou, devido o nosso container não rodar na pasta /var/www e sim numa pasta aleatória! No caso do Ubuntu, ele rodou na pasta do boot do Ubuntu, então para ele rodar o comando do npm devemos especificar onde ele deve abrir o meu container!

Para isso passaremos mais uma flag, a -w que significa "working directory", então é só referenciar nosso diretório a ele:

[Stand alone] Código completo do Docker + Node.js

docker run -d -p 8080:3000 -v "C:\Users\Dell\Desktop\volume-exemplo:/var/www" -w "/var/www" node npm start
Enter fullscreen mode Exit fullscreen mode

Feito todos esses passos, o container irá funcionar no localhost:8080.

docker

Eu posso modificar esse arquivo e atualizar a página localmente, que o servidor vai automaticamente atualizar. Portanto, eu tenho um ambiente de desenvolvimento dentro do meu desktop.

O container pode ser compartilhado entre a equipe e todos conteram a mesma versão dos programas e do ambiente.

Portanto, isso já resolve aquele velho erro de produção:

> Funcionou na minha máquina, na sua não? Como assim!? 🤔😕

> Então, se funcionou em uma máquina, funcionou em todas as máquinas! Esse é o grande benefício do Docker! 🐋 🐳 💙

Interpolação de comando

docker run -d -p 8080:3000 -v "$(pwd):/var/www" -w "/var/www" node npm start
Enter fullscreen mode Exit fullscreen mode

[Stand alone] Dockerfile

docker images
Enter fullscreen mode Exit fullscreen mode

Já trabalhamos com a imagem do ubuntu, hello-world, dockersamples/static-site e por fim do node, mas até agora não criamos a nossa própria imagem, para podermos distribuir para as outras pessoas.

A imagem é como se fosse uma receita de bolo. Então, para criarmos a nossa própria imagem, temos que criar a nossa receita de bolo, o Dockerfile (arquivo Docker), ensinando o Docker a criar uma imagem a partir da nossa aplicação, para que ela seja utilizada em outros locais.

dockerfile dockerfile

O Dockerfile nada mais é do que um arquivo de texto, e ele possui o nome identificador, porém se alterarmos o nome com o identificador , como node.dockerfile, ele será reconhecido, mesmo assim. No caso de possuir muitos Dockerfile é realmente interessante modificar o nome pela tecnologia que você está utilizando.

Vamos agora, distribuir nosso arquivo para que os outros desenvolvedores possam utilizar, para isso utilizaremos os comandos do Dockerfile:

[Stand alone] Montando imagem no Dockerfile

Você pode criar do zero ou pegar uma imagem base para isso! Vamos fazer nosso dockerfile se basear no node, então:

dockerfile

FROM node:latest
MAINTAINER isaacalves7 
ENV NODE_ENV=production
ENV PORT=3000
COPY . /var/www
WORKDIR /var/www
RUN npm install 
ENTRYPOINT npm start ["npm", "start"]
EXPOSE $PORT
Enter fullscreen mode Exit fullscreen mode
  • Identificamos com o FROM e depois imputamos a imagem e o sinal : servem para especificar a versão da imagem, no caso latest (última versão);
  • Outra instrução que é comum colocarmos é quem cuida, quem criou a imagem, através do MAINTAINER;
  • Variável de ambiente ENV, servem apenas para não gerar um log ao cliente;
  • Com o COPY copiamos nosso diretório padrão para rodar o node;
  • Com o WORKDIR assim que o container carregar o diretório, ele executará o mesmo com o npm start;
  • Com o RUN instalamos nossas dependências necessárias. E assim, podemos deletar a pasta node_modules, pois o que vale é apenas o dockerfile;
  • Com o ENTRYPOINT, cujo gerencia as dependências com base no JSON.

Feito a imagem, é hora de buildar essa imagem, para isso devemos ir ao terminal e inserir:

docker build -f Dockerfile -t isaacalves7/node-file .
Enter fullscreen mode Exit fullscreen mode

Após rodar esse comando, o docker fará dele uma imagem. Portanto:

docker images
Enter fullscreen mode Exit fullscreen mode

Dessa forma, poderá startar o comando dentro do repositório do arquivo:

docker run -d -p 8080:3000 isaacalves7/node-file
Enter fullscreen mode Exit fullscreen mode

Podemos alterar a variável de ambiente:

docker run -d -p 1234:8080 isaacalves7/node-file
Enter fullscreen mode Exit fullscreen mode

[Stand alone] Subindo imagens no Docker Hub

Faça sua conta no Docker Hub, depois ensira os comandos no terminal:

docker login
docker push [nome da imagem]
Enter fullscreen mode Exit fullscreen mode

No caso docker login é para se logar no Docker Hub, para ele reconhecer nosso ambiente e usuário.

Depois, inserimos docker push [nome da imagem] para ele subir nossa imagem no Docker Hub, para outros usuários terem acesso!

Somente baixar a imagem

docker pull [nome da imagem]
Enter fullscreen mode Exit fullscreen mode

Link do meu perfil: Docker HUB

🖧 Networking no Docker (Tipos de Redes)

Veremos como funciona a rede, e como fazemos para interligar diversos containers no Docker. Normalmente uma aplicação é composta por diversas partes, sejam elas o load balancer/proxy, a aplicação em si, um banco de dados, etc. Quando estamos trabalhando com containers, é bem comum separarmos cada uma dessas partes em um container específico, para cada container ficar com somente uma única responsabilidade.

Mas se temos uma parte da nossa aplicação em cada container, como podemos fazer para essas partes falarem entre elas? Pois para a nossa aplicação funcionar como um todo, os containers precisam trocar dados entre eles.

tipos de redes no Docker

Bridge network

bridge-network-docker

Host network

host-network-docker

Overlay network

overlay-network-docker

A boa notícia é que no Docker, por padrão, já existe uma default network, que no caso é a rede bridge. Isso significa que, quando criamos os nossos containers, por padrão eles funcionam na mesma rede:

[Stand alone] Verificando o default container com o Ubuntu

docker run -it ubuntu
Enter fullscreen mode Exit fullscreen mode

[Stand alone] Inspecionando a rede

docker inspect [id container]
Enter fullscreen mode Exit fullscreen mode
"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "905b02e0985ba89a81e437141cfbc432e7aa82c73fd51b2979ef989c9f8ec2a5",
                    "EndpointID": "",
                    "Gateway": "",
                    "IPAddress": "",
                    "IPPrefixLen": 0,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "",
                    "DriverOpts": null
                }

Logo, inserimos o comando:

hostname -i
ping 172.17.0.3
Enter fullscreen mode Exit fullscreen mode

Depois disso, o comando não funcionará, pois a imagem do Ubuntu só possui o básico para o Ubuntu funcionar via container.

Para isso, precisamos utilizar os comandos do Linux, como o de instalação de pacotes:

apt-get update && apt-get install
Enter fullscreen mode Exit fullscreen mode

Agora o nome do pacote:

apt-get update && apt-get install -y iputils-ping
Enter fullscreen mode Exit fullscreen mode

O iputils-ping serve para fazer o ping dentro do container.

ping --help
ping 172.17.0.3
Enter fullscreen mode Exit fullscreen mode

Comunicação de containers

docker network create --driver bridge [nome da rede]
docker network ls
Enter fullscreen mode Exit fullscreen mode

Criando um novo container atrelada a rede

docker run -it --name meu-container-de-ubuntu --network [nome da rede] ubuntu
docker inspect [nome do container]
Enter fullscreen mode Exit fullscreen mode

Pegando dados de um banco

Para praticar o que vimos sobre redes no Docker, vamos criar uma pequena aplicação que se conectará ao banco de dados, utilizando tudo o que vimos no vídeo anterior.

O que vamos fazer é utilizar a aplicação alura-books, que irá pegar os dados de um banco de dados de livros e exibi-los em uma página web. É uma aplicação feita em Node.js e o banco de dados é o MongoDB.

Pegando dados de um banco em um outro container

Então, primeiramente vamos baixar essas duas imagens, a imagem douglasq/alura-books na versão cap05 e a imagem mongo:

docker pull douglasq/alura-books:cap05
docker pull mongo
docker images
Enter fullscreen mode Exit fullscreen mode

Na imagem douglasq/alura-books, não há muito mistério. Ela possui o arquivo server.js, que carrega algumas dependências e módulos que são instalados no momento em que rodamos a imagem. Esse arquivo carrega também as configurações do banco, que diz onde o banco de dados estará em execução, no caso o seu host será meu-mongo, e o database, com nome de alura-books. Então, quando formos rodar o container de MongoDB, seu nome deverá ser meu-mongo. Além disso, o arquivo realiza a conexão com o banco, configura a porta que será utilizada (3000) e levanta o servidor.

server.js

code

No Dockerfile da imagem, também não há mistério, é basicamente o que vimos no vídeo anterior. Por fim, temos as rotas, que são duas: a rota /, que carrega os livros e os exibe na página, e a rota /seed, que salva os livros no banco de dados.

alura-books.dockerfile

code

Caso queira, você pode baixar aqui o código da versão cap05 da imagem alura-books.

Visto isso, já podemos subir a imagem:

docker run -d -p 8080:3000 douglasq/alura-books:cap05
Enter fullscreen mode Exit fullscreen mode

Ao acessar a página http://localhost:8080/, nenhum livro nos é exibido, pois além de não termos levantado o banco de dados, nós não salvamos nenhum dado nele.

docker

docker rm -f 43
Enter fullscreen mode Exit fullscreen mode

Então, vamos excluir esse container e subir o container do MongoDB, lembrando que o seu nome deve ser meu-mongo, e vamos colocá-lo na rede que criamos na sessão anterior:

docker run -d --name meu-mongo --network minha-rede mongo
Enter fullscreen mode Exit fullscreen mode

Com o banco de dados rodando, podemos subir a aplicação do mesmo jeito que fizemos anteriormente, mas não podemos nos esquecer que ele deve estar na mesma rede do banco de dados, logo vamos configurar isso também:

docker run --network minha-rede -d -p 8080:3000 douglasq/alura-books:cap05
Enter fullscreen mode Exit fullscreen mode

Verificando a rede:

docker network ls
docker inspect minha-rede
Enter fullscreen mode Exit fullscreen mode

Possuimos então dois containers na mesma rede (minha-rede):

"Containers": {
            "7dcce72922f00b3e3db3c9bc925c72cefb6495b0bb6e6d4a525629b62cc5a297": {
                "Name": "meu-mongo",
                "EndpointID": "89bb420463f092d057118137ce5b08d936194cae35db7f9886e3afcd8612042e",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "c68d8bbc9cdaea5fe75a3fce1e024eb6251f72e762c38b76670dd0ee7049d911": {
                "Name": "dreamy_wilbur",
                "EndpointID": "c3010c6d9c05af02d38b8816530677199d020b9c01d232f9fb86037375a3f5a4",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }

Agora, acessamos a página http://localhost:8080/seed/ para salvar os livros no banco de dados. Após isso, acessamos a página http://localhost:8080/ e vemos os dados livros são extraídos do banco e são exibidos na página.

docker

docker

Para provar isso, podemos parar a execução do meu-mongo e atualizar a página, veremos que nenhum livro mais será exibido.

docker stop meu-mongo
docker ps
Enter fullscreen mode Exit fullscreen mode

Então, esse foi um exemplo para praticar a comunicação entre containers, sempre lembrando que devemos colocá-los na mesma rede.

Docker Swarm

Ok, tudo bem até aqui. Agora vou ter vários serviços rodando usando o Docker. E para facilitar a criação desses containers já aprendemos usar o Docker Compose que sabe subir vários containers. O Docker Compose é a ferramenta ideal para coordenar a criação dos containers, no entanto para melhorar a escalabilidade e desempenho pode ser necessário criar muito mais containers para um serviço específico.

Em outras palavras, agora gostaríamos de criar muitos containers aproveitando várias máquinas (virtuais ou físicas)! Ou seja, pode ser que um microsserviço fique rodando em 20 containers usando três máquinas físicas diferentes. Como podemos facilmente subir e parar esses containers? Repare que o Docker Compose não é para isso e por isso existe uma outra ferramenta que se chama Docker Swarm.

Docker Swarm facilita a criação e administração de um cluster de containers.

Network: Bridge Mode

docker network create -d bridge petaBridge
docker network ls
docker run -d --net petaBridge --name db consul:latest
docker run -d --env "DB=db" --net petaBridge --name web -p 8000:5000 poroko/flask-demo-app
docker ps -a
docker exec -it web bash
bash$ ls
bash$ exit
docker exec -it web sh
Enter fullscreen mode Exit fullscreen mode

Network: Host Mode

docker run -d --net host --name db consul 
docker run -d --env "DB=localhost" --net host poroko/flask-demo-app
docker ps
netstat -nltp
Enter fullscreen mode Exit fullscreen mode
WARNING: Nesse modelo de rede, você não pode colocar outra aplicação da mesma linguagem na mesma porta, você precisa abrir outra porta!

[SWARM] Duas instâncias em rede bridge


Faremos um Docker Swarm, onde uma das duas máquinas será o gerenciador, ou seja, o principal controlando os demais nós desse cluster (no caso, a máquina).

Node2

Container de front-end (Flask = chrch/docker-pets:1.0 = web) e rede Bridge (petsBridge). No caso, esse vai ser o container principal do Docker SWARM.

Podemos escalonar o tráfego de rede com o Docker SWARM, criando assim o Load Balancer entre os dois containers (cada um em uma instância diferente).

docker network create -d bridge petsBridge
docker run -it --env "DB=db" --net petsBridge --name web -p 8000:5000 chrch/docker-pets:1.0
docker ps -a
docker restart [id do container]
ifconfig
docker swarm init --advertise-addr [IP do container]
> docker swarm join --token SWMTKN-1-1qrnyxbd5bja864nx27sdzpuwd52j60kdt1350ox42h45pkage-1jrrm0afvugxzdin34a5y6k1y 192.168.0.28:2377
docker node ls
hostname
docker network create -d overlay petsOverlay 
docker service create --network petsOverlay --name db consul
docker service create --network petsOverlay -p 8000:5000 -e "DB=db" --name web chrch/docker-pets:1.0
docker service ls
docker service scale web=3
Enter fullscreen mode Exit fullscreen mode

Node2

Container de back-end (Consul = db) e rede Bridge (petsBridge). Esse será chamado através do token de acesso do docker swarm para se juntar com o outro container, ligados pelo mesmo modelo de rede.

docker network create -d bridge petsBridge
docker run -d --net petsBridge --name db consul
docker swarm join --token SWMTKN-1-1qrnyxbd5bja864nx27sdzpuwd52j60kdt1350ox42h45pkage-1jrrm0afvugxzdin34a5y6k1y 192.168.0.28:2377
docker logs [ID container]
netstat -nltp
Enter fullscreen mode Exit fullscreen mode

Armazenamento no Docker (Docker storage)

Tanto o bind mounts quanto o volume, se o container for removido ou reiniciado, as informações serão persistidas em disco.

O tmpfs mounts é um disco temporário para guardar logs e caches da sua aplicação, o container for reiniciado ou removido esses arquivos serão deletados, ou seja, não foram para a camada de escrita do container.

Exemplo 1: Criando volume

docker volume create meuPrimeiroVolume
docker volume ls
docker volume inspect meuPrimeiroVolume
docker run -d -p 80:80 --name container-volume --mount source=meuPrimeiroVolume,nginx:latest
Enter fullscreen mode Exit fullscreen mode

Limites no Docker

Precisamos ter limites de memória e CPU no Docker, se acaso a sua aplicação consumir muito recurso de memória ou CPU desnecessariamente, siga o exemplo abaixo:

docker run -d --memory 10m busybox sleep 3600
docker stats
docker run --cpus=".5" -d --rm programium/stress -c 8 -t 50s
Enter fullscreen mode Exit fullscreen mode

projeto-dio

Docker Compose


Agora vamos subir múltiplos containers! Mas como? Simples! Basta rodar os dois comandos ao mesmo tempo:

docker run -d --name meu-mongo --network rede-producao mongo
docker run -d -p 3000:3000 --name alura-books --network rede-producao douglasq/alura-books 
Enter fullscreen mode Exit fullscreen mode

Contudo, essa forma de subir containers é um tanto falha, pois possue a maior probabilidade de:

  • Esquecer de uma flag;
  • Esquecer um parâmetro importante;
  • Muito manual;
  • Fácil de errar;
  • Preciso garantir a ordem.

[Compose] Funcionamento das aplicações em geral

Na vida real, sabemos que a aplicação é maior que somente dois containers, geralmente temos dois, três ou mais containers para segurar o tráfego da aplicação, distribuindo a carga. Além disso, temos que colocar todos esses containers para se comunicar com o banco de dados em um outro container, mas quanto maior a aplicação, devemos ter mais de um container para o banco também.

E claro, se temos três aplicações rodando, não podemos ter três endereços diferentes, então nesses casos utilizamos um Load Balancer em um outro container, para fazer a distribuição de carga quando tivermos muitos acessos. Ele recebe as requisições e distribui para uma das aplicações, e ele também é muito utilizado para servir os arquivos estáticos, como imagens, arquivos CSS e JavaScript.

Assim, a nossa aplicação controla somente a lógica, as regras de negócio, com os arquivos estáticos ficando a cargo do Load Balancer:

docker

Se formos seguir esse diagrama, teríamos que criar cinco containers na mão, e claro, cada container com configurações e flags diferentes, além de termos que nos preocupar com a ordem em que vamos subi-los. Ou seja, subir 5 flags na mão??? subir tudo isso de forma manual??? Que trabalhooooo....

dockerfile

Então, para isso foi criada uma ferramenta no Docker chamada Docker Compose, que ele executa e auxilia na build ou criação de vários (múltiplos) containers simultaneamente. Ele funciona seguindo um arquivo de texto, chamado docker-compose.yaml.

Esse arquivo serve para dar um conjunto de passos ao Docker e com ele podemos controlar quais containers sobem primeiro, ou seja, funciona como um processo de BIOS ou um automatizador de containers.

O Docker Compose cria uma rede padrão. Também é possível criar uma nova rede usando o comando docker network.

OBS: O YAML é semelhante ao JSON.

Seguindo o mesmo diagrama, vejamos nossa nova aplicação rodando atualizada para o uso do Docker Compose:

docker

O código do projeto pode ser baixado aqui.

Para começarmos a entender como funciona o Docker Compose, primeiramente vamos entender como funciona a aplicação que utilizaremos como base. É uma aplicação bem semelhante à utilizada na sessão anterior, com o mesmo servidor, rotas e banco de dados. De novidade, é que agora precisamos criar o NGINX, que é mais um container que devemos subir.

Então, ao utilizamos a imagem nginx, ou criamos a nossa própria. Como vamos configurar o NGINX para algumas coisas específicas, como lidar com os arquivos estáticos, vamos criar a nossa própria imagem, por isso que na aplicação há o nginx.dockerfile:

image

dockerfile

FROM nginx:latest
MAINTAINER isaacalves7
COPY /public /var/www/public
COPY /docker/config/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80 443
ENTRYPOINT ["nginx"]
# Parametros extras para o entrypoint
CMD ["-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

Nesse arquivo, nós utilizamos a última versão disponível da imagem do nginx como base, e copiamos o conteúdo da pasta public, que contém os arquivos estáticos, e um arquivo de configuração do NGINX para dentro do container. Além disso, abrimos as portas 80 e 443 e executa o NGINX através do comando nginx, passando os parâmetros extras -g e daemon off.

Por fim, vamos ver um pouco sobre o arquivo de configuração do NGINX, para entendermos um pouco como o load balancer está funcionando.

No arquivo nginx.conf, dentro server, está a parte que trata de servir os arquivos estáticos. Na porta 80, no localhost, em /var/www/public, ele será responsável por servir as pastas css, img e js. E todo resto, que não for esses três locais, ele irá jogar para o node_upstream.

No node_upstream, é onde ficam as configurações para o NGINX redirecionar as conexões que ele receber para um dos três containers da nossa aplicação. O redirecionamento acontecerá de forma circular, ou seja, a primeira conexão irá para o primeiro container, a segunda irá para o segundo container, a terceira irá para o terceiro container, na quarta, começa tudo de novo, e ela vai para o primeiro container e assim por diante.

Isso já está tudo pronto, basta baixarmos o código da imagem e da aplicação aqui.

Criando o docker-compose.yaml

Tanto faz as duas extensões, as duas são a mesma linguagem!

dockerfile dockerfile

Para utilizar o Docker Compose, devemos criar o seu arquivo de configuração, o docker-compose.yml, na raiz do projeto. Em todo arquivo de Docker Compose, que é uma espécie de receita de bolo para construirmos as diferentes partes da nossa aplicação, a primeira coisa que colocamos nele é a versão do Docker Compose que estamos utilizando:

dockerfile

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: douglasq/nginx
        container_name: nginx
        ports:
            - "80:80"
        networks: 
            - production-network
        depends_on: 
            - "node1"
            - "node2"
            - "node3"

    mongodb:
        image: mongo
        networks: 
            - production-network

    node1:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: douglasq/alura-books
        container_name: alura-books-1
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

    node2:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: douglasq/alura-books
        container_name: alura-books-2
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

    node3:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: douglasq/alura-books
        container_name: alura-books-3
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

networks: 
    production-network:
        driver: bridge
Enter fullscreen mode Exit fullscreen mode

Estamos utilizando a versão 3 pois é a versão mais recente no momento da criação do treinamento. O YAML lembra um pouco o JSON, mas ao invés de utilizar as chaves para indentar o código, ele utiliza espaços.

Agora, começamos a descrever os nossos serviços, os nossos services:

No YAML, toda vez que colocamos o caractére - significa que disponibilizamos um array.

Um serviço é uma parte da nossa aplicação. Lembrando do nosso diagrama:

docker

Temos NGINX, três Node, e o MongoDB como serviços. Logo, se queremos construir cinco containers, vamos construir cinco serviços, cada um deles com um nome específico.

Então, vamos começar construindo o NGINX, que terá o nome *nginx.

Em cada serviço, devemos dizer como devemos construí-lo, como devemos fazer o seu build:

O serviço será construído através de um Dockerfile, então devemos passá-lo onde ele está. E também devemos passar um contexto, para dizermos a partir de onde o Dockerfile deve ser buscado. Como ele será buscado a partir da pasta atual, vamos utilizar o ponto:

Construída a imagem, devemos dar um nome para ela, por exemplo douglasq/nginx:

E quando o Docker Compose criar um container a partir dessa imagem, vamos dizer que o seu nome será nginx:

Sabemos também que o NGINX trabalha com duas portas, a 80 e a 443. Como não estamos trabalhando com HTTPS, vamos utilizar somente a porta 80, e no próprio arquivo, podemos dizer para qual porta da nossa máquina queremos mapear a porta 80 do container. Vamos mapear para a porta de mesmo número da nossa máquina:

No YAML, toda vez que colocamos um traço, significa que a propriedade pode receber mais de um item. Agora, para os containers conseguirem se comunicar, eles devem estar na mesma rede, então vamos configurar isso também. Primeiramente, devemos criar a rede, que não é um serviço, então vamos escrever do começo do arquivo, sem as tabulações:

O nome da rede será production-network e utilizará o driver bridge:

Com a rede criada, vamos utilizá-la no serviço:

Isso é para construir o serviço do NGINX, agora vamos construir o serviço do MongoDB, com o nome mongodb. Como ele será construído a partir da imagem mongo, não vamos utilizar nenhum Dockerfile, logo não utilizamos a propriedade build. Além disso, não podemos nos esquecer de colocá-lo na rede que criamos:

Falta agora criarmos os três serviços em que ficará a nossa aplicação, node1, node2 e node3. Para eles, será semelhante ao NGINX, com Dockerfile alura-books.dockerfile, contexto, rede production-network e porta 3000:

Com isso, a construção dos nossos serviços está finalizada.

Ordem dos serviços

Por último, quando subimos os containers na mão, temos uma ordem, primeiro devemos subir o mongodb, depois a nossa aplicação, ou seja, node1, node2 e node3 e após tudo isso subimos o nginx. Mas como que fazemos isso no docker-compose.yml?

Nós podemos dizer que os serviços da nossa aplicação dependem que um serviço suba antes deles, o serviço do mongodb.

Da mesma forma, dizemos que o serviço do nginx depende dos serviços node1, node2 e node3.

[Docker Compose] Subindo os serviços

Com o docker-compose.yml pronto, podemos subir os serviços, mas antes devemos garantir que temos todas as imagens envolvidas neste arquivo na nossa máquina. Para isso, dentro da pasta do nosso projeto, executamos o seguinte comando:

Inicia o Docker Compose

docker-compose build
Enter fullscreen mode Exit fullscreen mode

Depois de buildar os serviços, eles podem ser vistos em:

docker images
Enter fullscreen mode Exit fullscreen mode

Após isso, podemos inserir o comando:

Starta todas as solicitações (build) do Docker Compose

docker-compose up
Enter fullscreen mode Exit fullscreen mode

Ou

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Listando e verificando o docker-compose

docker-compose ps
Enter fullscreen mode Exit fullscreen mode

Para e remove todos os containers do docker-compose

docker-compose down
Enter fullscreen mode Exit fullscreen mode

Verificando que os containers estão se comunicando

docker exec -it alura-books-1 ping alura-books-2
docker exec -it alura-books-1 ping node2
Enter fullscreen mode Exit fullscreen mode

Reinicializando containers

docker-compose restart
Enter fullscreen mode Exit fullscreen mode

Docker e Microsserviços

Trabalhar com uma arquitetura de microsserviços gera a necessidade de publicar o serviço de maneira rápida, leve, isolada e vimos que o Docker possui exatamente essas características! Com Docker e Docker Compose podemos criar um ambiente ideal para a publicação destes serviços.

O Docker é uma ótima opção para rodar os microsserviços pelo fato de isolar os containers. Essa utilização de containers para serviços individuais faz com que seja muito simples gerenciar e atualizar esses serviços, de maneira automatizada e rápida.

Top comments (0)