DEV Community

Cover image for Introdução ao Docker
Anderson de Alencar
Anderson de Alencar

Posted on • Edited on

Introdução ao Docker

Resumo dos conceitos do livro Learn Docker in a
Month of Lunches
do autor Elton Stoneman.

visão geral do docker

Docker começa em um simples conceito: empacotar um aplicativo com todas as suas dependências, para que você possa executar esse aplicativo da mesma maneira em qualquer lugar.

Docker facilita a transição entre serviços de nuvem e faz com que o projeto não fique preso a nenhum serviço de PaaS ou IaaS.

Monólitos não são tão úteis quando se usam Docker. Mover uma aplicação para contêineres pode ser o primeiro passo para a modernização da arquitetura do sistema. Dessa forma, usando Docker, pode-se usar a arquitetura de microsserviços e cada função em seu próprio contêiner, possibilitando pequenas funções, que podem ser facilmente testadas e isoladas uma das outras.

O que é um Contêiner?

Similar a ideia de um contêiner físico, uma caixa, onde dentro dessa caixa contém a aplicação e um computador com próprio endereço IP, disco rígido e nome. Tudo isso criado e gerenciado pelo Docker para executar a aplicação. A aplicação não pode ver nada fora do contêiner, além disso, esse contêiner está sendo executado em um computador, que pode conter outros contêineres, porém todos compartilham a mesma CPU, memória RAM e OS.

visão geral do docker, exibindo a relação do docker com o sistema operacional

Contêineres são executados apenas quando a aplicação está sendo executada. Assim que o processo encerra, o contêiner vai para o estado Exited, logo não consomem CPU e memória.

Para se conectar a um terminal dentro de um container de uma imagem chamada diamol/base,

docker container run --interactive --tty diamol/base
Enter fullscreen mode Exit fullscreen mode

A flag --interactive diz ao Docker que deseja estabelecer uma conexão e a flag --tty significa que deseja se conectar ao terminal.

Lista todos os contêineres.

docker container ls --all
Enter fullscreen mode Exit fullscreen mode

Lista contêineres em execução.

docker container ls
Enter fullscreen mode Exit fullscreen mode

Mostra informações de um contêiner em execução, como CPU, memória, rede, e disco.

docker container stats
Enter fullscreen mode Exit fullscreen mode

Remover todos os contêineres

docker container rm --force $(docker container ls --all --quiet)
Enter fullscreen mode Exit fullscreen mode

Hospedando um Website usando Docker

Um simples exemplo de como executar um website em um contêiner a partir da imagem diamol/ch02-hello-diamol-web

docker container run --detach --publish 8088:80 diamol/ch02-hello-diamol-web
Enter fullscreen mode Exit fullscreen mode

Onde --detach (-d) executa o contêiner em background e --publish (-p) expõe uma porta do contêiner ao computador.

Assim, o tráfego pode ser interceptado pelo Docker e enviado ao contêiner. Neste exemplo, a rede enviada a porta do computador pela porta 8088 será enviada pelo Docker a um contêiner para a porta 80.

executando um website em um container, a imagem mostra o redirecionamento da porta do computador para o contêiner

Entendendo como Docker funciona

visão geral dos componentes do docker, docker api, docker engine e docker cache
Docker possui alguns componentes internos

  • Docker Engine é o componente de gerenciamento do Docker. É o responsável por buscar imagens locais ou baixar imagens quando necessário e reutilizá-las se já baixadas. Trabalha com o sistema operacional para criar contêineres, redes virtuais e outros recursos do Docker.

  • Docker API é o modo que todas as funcionalidades do Docker Engine estão disponíveis, uma REST API padrão. É acessado do computador local por padrão, mas também pode ser configurada para acessada por outros computadores da rede.

  • Docker CLI é o cliente do Docker API. Quando comandos são executados, na verdade, são enviados ao Docker API e o Docker Engine realizada a tarefa.

Construindo Docker Images

Dockerfile é um simples script usado para empacotar uma aplicação e na sua saída gera um arquivo de imagem Docker.

As instruções mais usadas são:

  • FROM: toda imagem é gerada a partir de outra, em geral, uma depedência;
  • ENV: configura as variáveis de ambiente. A sintaxe é [key]="[value]";
  • WORKDIR: cria um diretório na imagem do sistema que será o diretório de trabalho;
  • COPY: copia arquivos ou diretórios para o diretório local da imagem/container;
  • CMD: especifica os comandos para executar a aplicação quando o Docker inicia o container a partir da imagem.
  • RUN: executar comandos

Um exemplo de Dockerfile é mostrado abaixo.

FROM diamol/node

ENV TARGET="blog.sixeyed.com"
ENV METHOD="HEAD"
ENV INTERVAL="3000"

WORKDIR /web-ping
COPY app.js .

CMD ["node", "/web-ping/app.js"]
Enter fullscreen mode Exit fullscreen mode

Para transformar um Dockerfile em uma Docker image execute o comando no diretório onde se encontra o Dockerfile

docker image build --tag web-ping .
Enter fullscreen mode Exit fullscreen mode

Onde --tag é o argumento para o nome da imagem e o argumento final é o diretório onde o Dockerfile e os arquivos relacionados se encontram.

Para executar o contêiner a partir da imagem pode-se usar o comando

docker container run web-ping
Enter fullscreen mode Exit fullscreen mode

Ou executar com outras variáveis de ambiente. Isso dá flexibilidade de executar o mesmo contêiner, porém com parâmetros diferentes.

docker container run -e TARGET=docker.com -e INTERVAL=5000 web-ping
Enter fullscreen mode Exit fullscreen mode

Entendendo Docker images

Uma imagem no Docker é uma coleção de camadas de imagens. Essas camadas são arquivos que ficam no Docker Engine. Assim, essas camadas podem ser compartilhadas entre diferentes contêineres.

Camadas são compartilhadas entre diferentes imagens

Lista todas as imagens e o tamanho

docker image ls
Enter fullscreen mode Exit fullscreen mode

Ao executar este comando, o espaço ocupado desconsidera as camadas compartilhadas

Mostra exatamente o espaço ocupado pelo Docker

docker system df
Enter fullscreen mode Exit fullscreen mode

Se o código não mudar entre as builds, o Docker reconhece e utiliza a build anteriormente salva, assim não duplica as imagens. Portanto, é recomendado ordenar as linhas de instruções no Dockerfile pela frequência em que elas mudam, logo as linhas mais improváveis de mudar vem primeiro, justamente para que o Docker utilize o cache e execute apenas as últimas instruções, economizando tempo, disco e rede.

Além disso, se as imagens são compartilhadas, elas não podem ser modificadas, forçando-as a serem apenas para leitura, assim impede que uma modificação altere outras imagens em cascata.

Empacotando código-fonte em Docker Imagens

Para executar um determinado código, é necessário um conjunto de ferramentas, que pode ser trabalhoso quando se trabalha em equipe. Dessa forma, o Docker pode criar um script com todas as ferramentas de desenvolvimento, além de compilar o código-fonte e o resultado é a aplicação. Isso favorece que no contêiner da aplicação não tenha nada além do necessário, diminuindo o tamanho da imagem, aumentando o tempo de inicialização e menos hardware para executá-lo, além de melhorar a segurança, pois há menos software disponível para potenciais ataques.

Para isso acontecer pode ser usado um Dockerfile multi etapas, onde cada etapa é independente, porém arquivos e pastas podem ser copiadas entre etapas e o resultado é apenas a etapa final.

Processo de um Dockerfile multi etapas

Um exemplo de Dockerfile multi etapas segue abaixo usado para um código em Java, onde cada etapa é identificada pela instrução FROM. Cada etapa também pode ser chamada por um nome usando o comando AS.

FROM diamol/maven AS builder

WORKDIR /usr/src/iotd
COPY pom.xml .
RUN mvn -B dependency:go-offline

COPY . .
RUN mvn package

# app
FROM diamol/openjdk

WORKDIR /app
COPY --from=builder /usr/src/iotd/target/iotd-service-0.1.0.jar .

EXPOSE 80
ENTRYPOINT ["java", "-jar", "/app/iotd-service-0.1.0.jar"]
Enter fullscreen mode Exit fullscreen mode

Na etapa de builder, usa-se a imagem diamol/maven que contém o OpenJDK e a ferramenta Maven. Em seguida, cria-se o diretório de trabalho e copia o pom.xml, onde o Maven define as configurações para o projeto em Java. Na primeira instrução RUN executa-se o comando do Maven para baixar as dependências do projeto. A instrução COPY transfere os arquivos e pasta para o diretório da imagem. Na segunda instrução RUN, o Maven compila o projeto em Java e gera um arquivo .jar.

Na segunda etapa, que começa com a imagem diamol/openjdk contém o Java 11, mas não o Maven. Em seguida, cria-se o diretório de trabalho e copia o arquivo jar da etapa de builder. Por fim, a porta da aplicação é exposta e ENTRYPOINT é uma alternativa ao CMD que diz o que fazer quando o contêiner iniciar a partir da imagem, neste caso, executar o arquivo compilado.

É interessante notar que as ferramentas de compilação não estão na versão final, colaborando para uma imagem menor. Outro bom exemplo é o curl, usado para baixar arquivos da Internet, pode ser necessário usado em alguma etapa do processo, mas não é necessário na imagem final.

Compartilhando Imagens no Docker Hub

Docker Hub é a maior plataforma para registrar imagens e o padrão do Docker Engine, onde as imagens são buscadas caso não sejam encontradas localmente.

Para identificação de uma imagem, são necessárias 4 partes no nome da imagem que servem como referência.

identificação de uma imagem, primeiro o domínio, autor, nome da imagem, tag

O versionamento pode ser feito da seguinte forma:

[major].[minor].[patch]
Enter fullscreen mode Exit fullscreen mode

Um versionamento que acrescenta um patch pode ser apenas resolução de bugs e ter as mesmas funções que a última versão. Quando minor é adicionado significa serem adicionadas novas funções, porém nenhuma é removida. Por fim, major pode ter funções completamente diferentes.

Para fazer login no Docker Hub,

export dockerId="<docker-id>"
docker login --username $dockerId
Enter fullscreen mode Exit fullscreen mode

Para criar uma nova referência a imagem, neste caso na versão v1,

docker image tag <image-name> $dockerId/<image-name>:v1
Enter fullscreen mode Exit fullscreen mode

Por fim, para enviar ao Docker,

docker image push $dockerId/<image-name>:v1
Enter fullscreen mode Exit fullscreen mode

Logo, é possível ir ao website visualizar a imagem em que foi feita upload.

Além disso, também é possível executar um próprio Docker registry como uma forma de backup ou de economizar banda.

⚠️ Importante ressalvar o uso de imagens verificadas ou oficiais para evitar problemas de segurança.

Usando Docker Volumes

Um contêiner é um sistema de arquivo único, preenchidos com os arquivos da imagem. As imagens são armazenadas como camadas, assim o disco do contêiner é apenas um sistema de arquivos virtual.

Contêineres possuem um espaço que podem ser usado para escrever ou modificar arquivos, porém esse espaço é exclusivo de cada contêiner, assim, caso o contêiner seja removido ou alterado (como em uma atualização, por exemplo) os dados serão perdidos.

Quando o contêiner tenta editar um arquivo da imagem, é gerada uma cópia que será exclusiva daquele contêiner e não interfere nos outros.

O exemplo abaixo mostra um número aleatório escrito em um arquivo por dois contêineres gerados a partir da mesma imagem.

A camada de escrita ela é única de cada contêiner e imagens compartilhadas são read-only

Portanto, Docker tem funcionalidades que permitem armazenar dados de forma independente do ciclo de vida dos containers, são eles: volumes e mounts.

Volumes são usados para persistirem informações. Pode-se criar um volume e associar a um container, que aparecerá como um diretório. Assim, ao salvar dados nesse diretório dentro do container, na verdade, será salvo no volume. Logo, ao serem criados novos containers e associados ao volume, todos os dados salvos estarão disponíveis.

Compartilhar o mesmo volume entre diferentes aplicações não é o ideal. Aplicações, geralmente, possuem acesso exclusivo a banco de dados. Volumes são melhores usados para preservarem atualizações nas aplicações, assim, é possível criar várias versões e associar o volume a nova versão ou a versão desejada.

Um modo de compartilhar armazenamento entre contêineres é usando bind mounts, que cria um diretório na máquina host disponível para um contêiner. Isso pode ser usado como backup ou um armazenamento distribuído acessível pela rede do computador.

Os bind mounts são bidirecionais, tanto o host como o contêiner podem ler ou escrever no diretório, então vale ressaltar o aspecto de segurança que contêineres, geralmente, são executados com o menor privilégio possível, sendo necessário autorização do usuário para escrever ou ler nesse diretório.

Cada contêiner possui um único disco virtual que o Docker une de várias fontes, isso é chamado union filesystem. Assim, o contêiner vê tudo como apensa um disco rígido e trabalha com arquivos e diretórios da mesma forma, estejam eles no disco ou não. O autor pode decidir quais serão as fontes de dados deste disco rígido, podem existir diversos volumes ou bind mounts, mas o contêiner verá tudo como um só.

descrição de cada camada de um contêiner

Segue, de maneira geral, como as formas de armazenamento podem ser usadas.

  • Camada de Escrita: dados temporários, como cache ou cálculos. São únicos de cada contêiner e tem o mesmo ciclo de vida que o contêiner.

  • Bind Mounts locais: compartilhar dados entre o host e o contêiner, por exemplo, código-fonte, realizando modificações locais sem precisar criar uma nova imagem.

  • Bind Mounts distribuídos: podem serem usados para o compartilhamento de configurações, fontes de leitura de informações ou cache distribuído ou para a escrita de dados que podem serem usados por qualquer contêiner ou máquina na rede.

  • Volume: compartilhar dados entre contêineres e armazenamento gerenciado pelo Docker. Quando uma aplicação atualiza para um novo contêiner, os dados serão salvos no volume da versão anterior.

  • Camadas das Imagens: sistema de arquivos inicial do contêiner. As últimas camadas sempre sobrepõe as mais novas. Camadas são sempre de leitura e compartilhada entre contêineres.

Top comments (0)