Como funcionam as imagens do Docker

Concluído

Lembre-se de que dissemos que a imagem do contêiner se torna a unidade que usamos para distribuir aplicativos. Também mencionamos que o contêiner está em um formato padronizado que nossas equipes de desenvolvimento e operação usam.

Aqui, veremos as diferenças entre software, pacotes e imagens usados no Docker. O facto de conhecermos as diferenças entre estes conceitos vai ajudar-nos a perceber melhor como funcionam as imagens do Docker.

Também vamos discutir brevemente as funções do SO em execução no sistema anfitrião e do SO em execução no contentor.

Software empacotado num contentor

O software empacotado em um contêiner não se limita aos aplicativos que nossos desenvolvedores criam. Quando falamos de software, referimo-nos ao código da aplicação, pacotes do sistema, binários, bibliotecas, ficheiros de configuração e ao sistema operativo em execução no contentor.

Por exemplo, suponha que estamos desenvolvendo um portal de rastreamento de pedidos que os vários pontos de venda da nossa empresa usarão. Temos de examinar toda a pilha de programas de software que vão executar a nossa aplicação Web. O aplicativo que estamos construindo é um aplicativo MVC .NET Core, e planejamos implantar o aplicativo usando nginx como um servidor proxy reverso no Ubuntu Linux. Todos estes componentes do software formam parte da imagem de contentor.

O que é uma imagem de contentor?

Uma imagem de contentor é um pacote portátil que contém software. É esta imagem que, quando executada, se torna o nosso contentor. O contentor é a instância na memória de uma imagem.

Uma imagem de contentor é imutável. Depois de criar uma imagem, não é possível alterá-la. A única forma de alterar uma imagem é criando uma nova imagem. Esta funcionalidade é a nossa garantia de que a imagem que utilizamos na produção é a mesma imagem utilizada na programação e no CQ.

O que é o sistema operativo (SO) do sistema anfitrião?

O SO do sistema anfitrião é o SO no qual o motor do Docker é executado. Os contêineres do Docker executados no Linux compartilham o kernel do sistema operacional host e não exigem um sistema operacional de contêiner, desde que o binário possa acessar o kernel do sistema operacional diretamente.

Diagram showing a Docker image with no base OS and the dependency on the host OS Kernel.

No entanto, os contentores do Windows precisam de um SO de contentor. O contentor depende do kernel do SO para gerir serviços como o sistema de ficheiros, a gestão da rede, o agendamento de processos e a gestão da memória.

O que é o sistema operativo (SO) de contentor?

O sistema operacional do contêiner é o sistema operacional que faz parte da imagem empacotada. Temos a flexibilidade de incluir diferentes versões de sistemas operacionais Linux ou Windows em um contêiner. Esta flexibilidade permite-nos aceder a funcionalidades específicas do SO ou instalar software adicional que as nossas aplicações possam utilizar.

Diagram showing a Docker image with an Ubuntu base OS and the dependency on the host OS Kernel.

O sistema operacional do contêiner é isolado do sistema operacional host e é o ambiente no qual implantamos e executamos nosso aplicativo. Aliado à imutabilidade da imagem, este isolamento significa que o ambiente para a nossa aplicação executada na fase da programação é o mesmo que na fase da produção.

Em nosso exemplo, estamos usando o Ubuntu Linux como o sistema operacional de contêiner, e esse sistema operacional não muda de desenvolvimento ou produção. A imagem que utilizamos é sempre a mesma.

O que é o Sistema de Ficheiros de Unificação Empilhável (Unionfs)?

Usamos Unionfs para criar imagens do Docker. Unionfs é um sistema de arquivos que permite empilhar vários diretórios — chamados ramificações — de tal forma que pareça que o conteúdo está mesclado. Contudo, o conteúdo mantém-se fisicamente separado. O Unionfs permite-lhe adicionar e remover ramos à medida que vai criando o sistema de ficheiros.

Diagram showing the stacking of layers in a Docker image created with unionfs.

Por exemplo, suponhamos que estamos a compilar uma imagem para a nossa aplicação Web referida anteriormente. Vamos colocar a distribuição do Ubuntu em camadas como uma imagem de base sobre o sistema de ficheiros de arranque. Em seguida, instalaremos o nginx e nosso aplicativo web. Estamos efetivamente sobrepondo o nginx e o aplicativo web em cima da imagem original do Ubuntu.

Uma última camada gravável é criada quando o contentor é executado a partir da imagem. No entanto, essa camada não persiste quando o contêiner é destruído.

O que é uma imagem de base?

Uma imagem de base é uma imagem que utiliza a imagem scratch do Docker. A imagem scratch é uma imagem de contentor vazia que não cria uma camada de sistema de ficheiros. Esta imagem pressupõe que a aplicação que vai executar pode utilizar diretamente o kernel do SO do sistema anfitrião.

O que é uma imagem principal?

Uma imagem principal é uma imagem de contentor a partir da qual vai criar as suas imagens.

Por exemplo, em vez de criar uma imagem a partir de scratch e, em seguida, instalar o Ubuntu, vamos usar uma imagem já baseada no Ubuntu. Podemos até usar uma imagem que já tenha o nginx instalado. Uma imagem principal costuma incluir um SO de contentor.

Qual é a principal diferença entre as imagens de base e as imagens principais?

Os dois tipos de imagem permitem-nos criar uma imagem reutilizável. No entanto, as imagens de base permitem-nos um maior controlo sobre o conteúdo final da imagem. Lembre-se de que uma imagem é imutável; você só pode adicionar a uma imagem e não subtrair.

No Windows, você só pode criar imagens de contêiner baseadas em imagens de contêiner base do Windows. A Microsoft fornece e presta serviços a estas imagens de contentor base do Windows.

O que é um Dockerfile?

Um Dockerfile é um ficheiro de texto que contém as instruções que utilizamos para compilar e executar uma imagem do Docker. Os seguintes aspetos da imagem são definidos:

  • A imagem de base ou principal que utilizamos para criar a nova imagem
  • Comandos para atualizar o SO de base e instalar software adicional
  • Artefactos de compilação a incluir, por exemplo, uma aplicação desenvolvida
  • Serviços a expor, como armazenamento e configuração de rede
  • O comando a executar quando o contentor for iniciado

Vamos mapear estes aspetos a um Dockerfile de exemplo. Suponhamos que estamos a criar uma imagem do Docker para o nosso site do ASP.NET Core. O Dockerfile pode se parecer com o exemplo a seguir:

# Step 1: Specify the parent image for the new image
FROM ubuntu:18.04

# Step 2: Update OS packages and install additional software
RUN apt -y update &&  apt install -y wget nginx software-properties-common apt-transport-https \
	&& wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
	&& dpkg -i packages-microsoft-prod.deb \
	&& add-apt-repository universe \
	&& apt -y update \
	&& apt install -y dotnet-sdk-3.0

# Step 3: Configure Nginx environment
CMD service nginx start

# Step 4: Configure Nginx environment
COPY ./default /etc/nginx/sites-available/default

# STEP 5: Configure work directory
WORKDIR /app

# STEP 6: Copy website code to container
COPY ./website/. .

# STEP 7: Configure network requirements
EXPOSE 80:8080

# STEP 8: Define the entry point of the process that runs in the container
ENTRYPOINT ["dotnet", "website.dll"]

Não vamos abordar a especificação do arquivo Dockerfile aqui, nem os detalhes de cada comando no exemplo anterior. No entanto, observe que existem vários comandos neste arquivo que nos permitem manipular a estrutura da imagem. Por exemplo, o comando copia o COPY conteúdo de uma pasta específica na máquina local para a imagem de contêiner que estamos criando.

Lembre-se de que, anteriormente, mencionamos que as imagens do Docker fazem uso do unionfs. Cada um destes passos cria uma imagem de contentor colocada em cache à medida que vamos compilando a imagem de contentor final. Essas imagens temporárias são colocadas em camadas sobre a imagem anterior e apresentadas como uma única imagem assim que todas as etapas forem concluídas.

Por fim, repare no último passo, o passo 8. O ENTRYPOINT no ficheiro indica o processo a ser executado depois de executarmos um contentor a partir de uma imagem. Se não houver ENTRYPOINT ou outro processo a ser executado, o Docker interpretará isso como se não houvesse nada para o contêiner fazer e o contêiner será encerrado.

Como gerir imagens do Docker

As imagens do Docker são arquivos grandes que são inicialmente armazenados no seu PC, e precisamos de ferramentas para gerenciar esses arquivos.

A CLI do Docker e o Docker Desktop nos permitem gerenciar imagens criando, listando, removendo e executando-as. Gerimos as imagens do Docker ao utilizar o cliente docker. O cliente não executa os comandos diretamente e envia todas as consultas para o dockerd daemon.

Não vamos abordar aqui todos os comandos do cliente e os sinalizadores de comandos, mas vamos analisar alguns dos comandos mais utilizados. A seção Saiba mais no final deste módulo inclui links para a documentação do Docker, que aborda todos os comandos e sinalizadores de comando em detalhes.

Como compilar uma imagem

Utilizamos o comando docker build para compilar imagens do Docker. Vamos supor que utilizamos a definição do Dockerfile anterior para compilar uma imagem. Aqui está um exemplo que mostra o comando build:

docker build -t temp-ubuntu .

Aqui está a saída que o comando build gera:

Sending build context to Docker daemon  4.69MB
Step 1/8 : FROM ubuntu:18.04
 ---> a2a15febcdf3
Step 2/8 : RUN apt -y update && apt install -y wget nginx software-properties-common apt-transport-https && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb && add-apt-repository universe && apt -y update && apt install -y dotnet-sdk-3.0
 ---> Using cache
 ---> feb452bac55a
Step 3/8 : CMD service nginx start
 ---> Using cache
 ---> ce3fd40bd13c
Step 4/8 : COPY ./default /etc/nginx/sites-available/default
 ---> 97ff0c042b03
Step 5/8 : WORKDIR /app
 ---> Running in 883f8dc5dcce
Removing intermediate container 883f8dc5dcce
 ---> 6e36758d40b1
Step 6/8 : COPY ./website/. .
 ---> bfe84cc406a4
Step 7/8 : EXPOSE 80:8080
 ---> Running in b611a87425f2
Removing intermediate container b611a87425f2
 ---> 209b54a9567f
Step 8/8 : ENTRYPOINT ["dotnet", "website.dll"]
 ---> Running in ea2efbc6c375
Removing intermediate container ea2efbc6c375
 ---> f982892ea056
Successfully built f982892ea056
Successfully tagged temp-ubuntu:latest

Não se preocupe se você não entender a saída anterior. Porém, repare nos passos listados no resultado. Quando cada passo é executado, uma nova camada é adicionada à imagem que estamos a compilar.

Além disso, repare que executamos uma série de comandos para instalar o software e gerir a configuração. Por exemplo, no passo 2, executamos os comandos apt -y update e apt install -y para atualizar o sistema operativo. Esses comandos são executados em um contêiner em execução criado para essa etapa. Quando o comando é executado, o contêiner intermediário é removido. A imagem colocada em cache subjacente é mantida no sistema anfitrião de compilação e não é automaticamente eliminada. Esta otimização garante que as compilações posteriores reutilizem essas imagens para acelerar os tempos de compilação.

O que é uma etiqueta de imagem?

Uma tag de imagem é uma cadeia de caracteres de texto usada para fazer a versão de uma imagem.

No exemplo de compilação anterior, repare na última mensagem de compilação que indica "Successfully tagged temp-ubuntu: latest". Quando compilamos uma imagem, atribuímos um nome e opcionalmente uma etiqueta à imagem utilizando o sinalizador de comando -t. No nosso exemplo, atribuímos um nome à imagem utilizando -t temp-ubuntu, ao passo que o nome da imagem resultante foi etiquetado como temp-ubuntu: latest. Uma imagem fica etiquetada com a etiqueta latest se não especificar uma etiqueta.

É possível atribuir múltiplas etiquetas a uma só imagem. Por convenção, à versão mais recente de uma imagem é atribuída a etiqueta latest (mais recente), assim como uma etiqueta que indica o número da versão da imagem. Quando lançar uma nova versão de uma imagem, pode reatribuir a etiqueta latest (mais recente) para referenciar a nova imagem.

Para Windows, a Microsoft não fornece imagens de contêiner base com a tag mais recente. Para imagens de contêiner base do Windows, você precisa especificar uma tag que deseja usar. Por exemplo, a imagem de contêiner base do Windows para Server Core é mcr.microsoft.com/windows/servercore. Entre suas tags estão ltsc2016, ltsc2019, e ltsc2022.

Aqui está outro exemplo. Vamos supor que pretende utilizar as imagens do Docker dos exemplos do .NET Core. Aqui temos quatro versões de plataformas das quais podemos escolher:

  • mcr.microsoft.com/dotnet/core/samples:dotnetapp

  • mcr.microsoft.com/dotnet/core/samples:aspnetapp

  • mcr.microsoft.com/dotnet/core/samples:wcfservice

  • mcr.microsoft.com/dotnet/core/samples:wcfclient

Na lista de imagens anterior, podemos ver que a Microsoft fornece vários exemplos do .NET Core. As tags especificam a quais exemplos a imagem se refere: ASP.NET, Serviço WCF e assim por diante.

Como listar imagens

O software do Docker configura automaticamente um registo de imagens local no seu computador. Pode ver as imagens contidas nesse registo com o comando docker images.

docker images

A saída se parece com o exemplo a seguir:

REPOSITORY          TAG                     IMAGE ID            CREATED                     SIZE
tmp-ubuntu          latest             f89469694960        14 minutes ago         1.69GB
tmp-ubuntu          version-1.0        f89469694960        14 minutes ago         1.69GB
ubuntu              18.04                   a2a15febcdf3        5 weeks ago            64.2MB

Repare como a imagem é listada com o respetivo Nome, Etiqueta e um ID de imagem. Lembre-se de que podemos aplicar várias etiquetas a uma imagem. A saída anterior mostra um exemplo; mesmo que os nomes das imagens sejam diferentes, podemos ver que os IDs são os mesmos.

O ID da imagem é uma forma útil de identificar e gerir imagens cujo nome ou etiqueta sejam ambíguos.

Como remover uma imagem

Pode remover uma imagem do registo local do Docker com o comando docker rmi. Isso é útil se você precisar economizar espaço no disco host do contêiner, porque as camadas de imagem do contêiner somam ao espaço total disponível.

Especifique o nome ou ID da imagem a remover. Neste exemplo, a imagem é removida da aplicação Web de exemplo utilizando o nome de imagem:

docker rmi temp-ubuntu:version-1.0

Não é possível remover uma imagem se um contêiner ainda estiver usando a imagem. O docker rmi comando retorna uma mensagem de erro, que lista o contêiner que depende da imagem.

Exploramos os conceitos básicos das imagens do Docker, como gerenciar essas imagens e como executar um contêiner a partir de uma imagem. Em seguida, vamos ver como gerir contentores.

Verifique o seu conhecimento

1.

O Docker Desktop é uma aplicação para criar e partilhar aplicações em contentores e microsserviços disponível em quais dos seguintes sistemas operativos?

2.

Qual é o comando correto do Docker para recompilar uma imagem de contentor?

3.

Qual das seguintes frases descreve melhor uma imagem de contentor?