Resumo dos conceitos do livro Learn Docker in a
Month of Lunches do autor Elton Stoneman.
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.
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
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
Lista contêineres em execução.
docker container ls
Mostra informações de um contêiner em execução, como CPU, memória, rede, e disco.
docker container stats
Remover todos os contêineres
docker container rm --force $(docker container ls --all --quiet)
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
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.
Entendendo como Docker funciona
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"]
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 .
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
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
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.
Lista todas as imagens e o tamanho
docker image ls
Ao executar este comando, o espaço ocupado desconsidera as camadas compartilhadas
Mostra exatamente o espaço ocupado pelo Docker
docker system df
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.
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"]
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.
O versionamento pode ser feito da seguinte forma:
[major].[minor].[patch]
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
Para criar uma nova referência a imagem, neste caso na versão v1,
docker image tag <image-name> $dockerId/<image-name>:v1
Por fim, para enviar ao Docker,
docker image push $dockerId/<image-name>:v1
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.
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ó.
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)