Como funcionam as imagens do Docker

Concluído

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

Aqui, veremos as diferenças entre software, pacotes e imagens, conforme usado no Docker. Conhecer as diferenças entre esses conceitos nos ajudará a entender melhor como funcionam as imagens do Docker.

Também discutiremos brevemente as funções do sistema operacional em execução no host e o sistema operacional em execução no contêiner.

Software empacotado em um contêiner

O software empacotado em um contêiner não é limitado aos aplicativos criados por nossos desenvolvedores. Quando falamos de software, nós nos referimos ao código do aplicativo, aos pacotes do sistema, aos binários, às bibliotecas, aos arquivos de configuração e ao sistema operacional em execução no contêiner.

Por exemplo, suponha que estejamos desenvolvendo um portal de acompanhamento de pedidos que será usado pelas várias lojas de nossa empresa. Precisamos examinar a pilha completa de programas de software que executará nosso aplicativo Web. O aplicativo que estamos compilando é um aplicativo .NET Core MVC e pretendemos implantá-lo usando o nginx como um servidor proxy reverso no Ubuntu Linux. Todos esses componentes de software constituem a imagem de contêiner.

O que é uma imagem de contêiner?

Uma imagem de contêiner é um pacote portátil que contém software. Essa é a imagem que, quando executada, se torna nosso contêiner. O contêiner é a instância em memória de uma imagem.

Uma imagem de contêiner é imutável. Depois de criar uma imagem, você não poderá alterá-la. A única maneira de alterar uma imagem é criar outra imagem. Esse recurso é a nossa garantia de que a imagem que usamos em produção é a mesma imagem usada em desenvolvimento e garantia de qualidade.

O que é o sistema operacional host?

O sistema operacional host é o sistema operacional no qual o mecanismo do Docker é executado. Os contêineres do Docker em execução no Linux compartilham o kernel do sistema operacional do host e não exigem um sistema operacional do contêiner, contanto 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 contêineres do Windows precisam de um sistema operacional do contêiner. O contêiner depende do kernel do sistema operacional para gerenciar serviços como o sistema de arquivos, o gerenciamento de rede, o agendamento de processos e o gerenciamento de memória.

O que é o sistema operacional do contêiner?

O sistema operacional do contêiner é o sistema operacional que faz parte da imagem empacotada. Temos a flexibilidade para incluir diferentes versões de sistemas operacionais Linux ou Windows em um contêiner. Essa flexibilidade nos permite acessar recursos específicos do sistema operacional ou instalar software adicional que pode ser usado pelos aplicativos.

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. Combinado com a imutabilidade da imagem, esse isolamento significa que o ambiente para o aplicativo executado em desenvolvimento é o mesmo que em produção.

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

O que é o Sistema de Arquivos 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 branches – de modo que eles apareçam como se o conteúdo estivesse mesclado. No entanto, o conteúdo é fisicamente mantido separado. Unionfs permite adicionar e remover branches à medida que você cria o sistema de arquivos.

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

Por exemplo, suponha que estejamos criando uma imagem para nosso aplicativo Web de anteriormente. Colocaremos a distribuição do Ubuntu em camadas como uma imagem base sobre o sistema de arquivos de inicialização. Em seguida, instalaremos o nginx e nosso aplicativo Web. Estamos efetivamente colocando o nginx e o aplicativo Web em camadas sobre a imagem original do Ubuntu.

Uma camada gravável final é criada quando o contêiner é executado por meio da imagem. No entanto, essa camada não persiste quando o contêiner é destruído.

O que é uma imagem base?

Uma imagem base é uma imagem que usa a imagem scratch do Docker. A imagem scratch é uma imagem de contêiner vazia que não cria uma camada do sistema de arquivos. Essa imagem pressupõe que o aplicativo que você pretende executar possa usar diretamente o kernel do sistema operacional host.

O que é uma imagem pai?

Uma imagem pai é uma imagem de contêiner com base na qual você cria suas imagens.

Por exemplo, em vez de criar uma imagem do scratch e, em seguida, instalar o Ubuntu, usaremos uma imagem já baseada no Ubuntu. Podemos até mesmo usar uma imagem que já tenha o nginx instalado. Uma imagem pai geralmente inclui um sistema operacional do contêiner.

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

Os dois tipos de imagens nos permitem criar uma imagem reutilizável. No entanto, as imagens base nos permitem ter mais controle sobre o conteúdo final da imagem. Lembre-se de que, anteriormente, vimos que uma imagem é imutável; só é possível fazer uma adição a uma imagem, e não subtrair algo dela.

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 essas imagens de contêiner base do Windows.

O que é um Dockerfile?

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

  • A imagem base ou pai que usamos para criar a imagem
  • Comandos usados para atualizar o sistema operacional base e instalar software adicional
  • Artefatos de compilação a serem incluídos, como um aplicativo desenvolvido
  • Serviços a serem expostos, como um armazenamento e uma configuração de rede
  • Comando a ser executado quando o contêiner é iniciado

Vamos mapear esses aspectos para um Dockerfile de exemplo. Suponha que estejamos criando uma imagem do Docker para nosso site do ASP.NET Core. O Dockerfile pode ser semelhante ao 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, ou os detalhes de cada comando no exemplo anterior. No entanto, observe que há vários comandos nesse arquivo que nos permitem manipular a estrutura da imagem. Por exemplo, o comando COPY copia o conteúdo de uma pasta específica no computador local para a imagem de contêiner que estamos criando.

Lembre-se de que mencionamos anteriormente que as imagens do Docker usam unionfs. Cada uma destas etapas cria uma imagem de contêiner armazenada em cache conforme compilamos a imagem de contêiner final. Essas imagens temporárias são colocadas em camadas sobre a imagem anterior e apresentadas como uma única imagem depois que todas as etapas são concluídas.

Por fim, observe a última etapa, a etapa 8. O ENTRYPOINT no arquivo indica qual processo será executado depois de executarmos um contêiner por meio 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 gerenciar imagens do Docker

As imagens do Docker são arquivos grandes que inicialmente são armazenados no computador e, para gerenciá-los, precisamos de ferramentas.

A CLI do Docker e o Docker Desktop nos permite gerenciar imagens compilando-as, listando-as, removendo-as e executando-as. Gerenciamos as imagens do Docker usando o cliente docker. O cliente não executa os comandos diretamente, e envia todas as consultas para o daemon dockerd.

Não abordaremos todos os comandos do cliente e os sinalizadores de comandos aqui, mas veremos alguns dos comandos mais usados. A seção Saiba mais no final deste módulo inclui links para a documentação do Docker, que abrange todos os comandos e os sinalizadores de comandos detalhadamente.

Como compilar uma imagem

Usamos o comando docker build para compilar imagens do Docker. Vamos supor que usemos a definição de 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 de 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. No entanto, observe as etapas listadas na saída. Quando cada etapa é executada, uma nova camada é adicionada à imagem que estamos compilando.

Além disso, observe que executamos vários comandos para instalar o software e gerenciar a configuração. Por exemplo, na etapa 2, executamos os comandos apt -y update e apt install -y para atualizar o sistema operacional. Esses comandos são executados em um contêiner em execução criado para essa etapa. Depois que o comando for executado, o contêiner intermediário será removido. A imagem armazenada em cache subjacente é mantida no host de build e não é excluída automaticamente. Essa otimização garante que os builds posteriores reutilizem essas imagens para acelerar os tempos de build.

O que é uma marca de imagem?

Uma marca de imagem é uma cadeia de texto usada para definir a versão de uma imagem.

No exemplo de build anterior, observe a última mensagem de build que indica "temp-ubuntu: mais recente marcado com êxito". Ao compilar uma imagem, nomeamos e, opcionalmente, marcamos a imagem usando o sinalizador de comando -t. Em nosso exemplo, nomeamos a imagem usando -t temp-ubuntu, enquanto o nome da imagem resultante era marcado como temp-ubuntu: mais recente. Uma imagem será rotulada com a marca latest se você não especificar uma marca.

Uma única imagem pode ter várias marcas atribuídas a ela. Por convenção, a versão mais recente de uma imagem recebe a marca mais recente e uma marca que descreve o número de versão da imagem. Ao liberar uma nova versão de uma imagem, você pode reatribuir a marca mais recente para referenciar a nova imagem.

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

Confira outro exemplo. Suponha que você queira usar as imagens do Docker das amostras do .NET Core. Aqui temos quatro versões de plataformas que 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 de .NET Core. As marcas especificam para 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 Registro de imagem local no computador. Exiba as imagens desse registro com o comando docker images.

docker images

A saída se parece com o seguinte exemplo:

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

Observe como a imagem é listada com o Nome, a Marca e uma ID da Imagem. Lembre-se de que podemos aplicar vários rótulos a uma imagem. A saída anterior mostra um exemplo; embora os nomes de imagem sejam diferentes, podemos ver que as IDs são as mesmas.

A ID da imagem é uma maneira útil de identificar e gerenciar imagens nas quais o nome ou a marca de uma imagem possa ser ambíguo.

Como remover uma imagem

Remova uma imagem do registro do Docker local com o comando docker rmi. Isso será útil se você precisar economizar espaço no disco do host do contêiner, pois as camadas de imagem de contêiner são somadas ao espaço total disponível.

Especifique o nome ou a ID da imagem a ser removida. Este exemplo remove a imagem do aplicativo Web de exemplo usando o nome da imagem:

docker rmi temp-ubuntu:version-1.0

Você não poderá remover uma imagem se um contêiner ainda estiver usando a imagem. O comando docker rmi 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 por meio de uma imagem. Em seguida, veremos como gerenciar contêineres.

Verificar seu conhecimento

1.

O Docker Desktop é um aplicativo para criar e compartilhar aplicativos e microsserviços em contêineres disponíveis em quais dos sistemas operacionais a seguir?

2.

Qual é o comando correto do Docker para recompilar uma imagem de contêiner?

3.

Qual das frases a seguir descreve melhor uma imagem de contêiner?