Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Understanding Docker Architecture: Engine, containerd, and runc

Recommended resources

Olá! Na nossa última lição, mergulhamos nos fundamentos que tornam os contêineres possíveis: os namespaces do kernel do Linux para isolamento e os cgroups para limitação de recursos. Vimos que um contêiner, em sua essência, é um processo Linux padrão com "visão" e "uso" de recursos controlados.

Nesta lição, vamos subir um nível de abstração para entender as ferramentas que orquestram esses recursos do kernel. O objetivo é que você consiga descrever a arquitetura do Docker—incluindo o Docker Engine, containerd e runc—e como esses componentes colaboram para gerenciar o ciclo de vida de um contêiner. Isso preencherá a lacuna entre os recursos de baixo nível do kernel e os comandos docker que usamos no dia a dia.

A Arquitetura Docker: De Monolítica a Modular

Inicialmente, o Docker era uma aplicação monolítica, onde um único daemon era responsável por tudo. Com o tempo, para promover a padronização e a interoperabilidade (especialmente com o surgimento de orquestradores como o Kubernetes), o Docker adotou uma arquitetura modular.

Hoje, a interação com o Docker segue um modelo cliente-servidor, que evolui para uma cadeia de componentes especializados.

Docker Architecture: Client to Runc Flow
Este fluxograma ilustra a arquitetura moderna do Docker. Um comando do usuário no Docker Client viaja através do Docker Daemon, que delega as tarefas de tempo de execução para o `containerd`, que por sua vez utiliza o `runc` para criar e executar o processo do contêiner.

Vamos dissecar essa cadeia, começando pela interface com o usuário até chegar ao processo que efetivamente roda no kernel.


A Cadeia de Comando do Docker

Para entender como um simples comando docker run se transforma em um contêiner em execução, vamos examinar cada peça do quebra-cabeça.

O artigo a seguir, de Srinivasaraju Tangella, oferece uma excelente visão geral dessa arquitetura, da interface de linha de comando (CLI) até o kernel.

Inside Docker: The Complete Architecture Explained from CLI to Kernel

Este artigo explica de forma clara e sequencial cada componente da arquitetura Docker. Ele servirá como nosso guia principal para esta lição.

Leia as seções 1 a 7 e 10. Preste atenção especial na função de cada componente (Docker CLI, dockerd, containerd, containerd-shim, runc) e como eles se comunicam entre si. A seção 10 ('The Full Execution Flow') resume o processo de forma excelente.

Com base no que você leu, vamos detalhar as responsabilidades de cada componente.

1. Docker Client e Docker Daemon (dockerd)

  • Docker CLI (docker): Esta é a sua interface. Quando você digita um comando como docker run, você está interagindo com o cliente Docker. Ele não faz o trabalho pesado; em vez disso, ele traduz seu comando em uma chamada de API REST e a envia para o Docker Daemon.
  • Docker Daemon (dockerd): Este é o "cérebro" do Docker. É um processo de longa duração (um daemon) que escuta as chamadas da API do cliente. dockerd gerencia os objetos de alto nível do Docker, como imagens, redes e volumes. No entanto, quando se trata de executar um contêiner, ele não faz o trabalho sozinho. Ele delega essa responsabilidade a um componente mais especializado.

Essa separação é fundamental. Você pode, por exemplo, ter o Docker CLI em sua máquina local se comunicando com um dockerd rodando em um servidor remoto.

2. containerd: O Gerente de Ciclo de Vida do Contêiner

Como o artigo mencionou, dockerd passa a solicitação de execução de um contêiner para o containerd.

  • containerd é um runtime de contêiner de alto nível. Sua única preocupação é gerenciar o ciclo de vida completo do contêiner no sistema:
    • Baixar imagens de um registry (como o Docker Hub).
    • Gerenciar o armazenamento de imagens e os sistemas de arquivos em camadas (snapshots).
    • Iniciar, parar e supervisionar contêineres.
    • Configurar a rede para o contêiner.

containerd foi doado pelo Docker para a Cloud Native Computing Foundation (CNCF). Isso o tornou um padrão da indústria, usado não apenas pelo Docker, mas também pelo Kubernetes e outras ferramentas, o que é relevante para seus objetivos de aprendizado mais amplos.

O vídeo a seguir fornece uma excelente explicação visual sobre a hierarquia entre dockerd, containerd e os componentes subsequentes.

Docker Architecture | Docker components : daemon, containerd, containerd-shim, runc

Para complementar a leitura, este vídeo do canal InvolveInInnovation detalha as responsabilidades de cada componente da arquitetura de forma clara e visual.

Assista dos 02:16 aos 05:39. O vídeo abordará containerd, containerd-shim e runc. Foque em entender como containerd gerencia o ciclo de vida e como o shim permite a existência de 'daemonless containers'.

3. runc: O Executor do Contêiner

containerd não cria o contêiner diretamente. Para essa tarefa, ele chama um runtime de baixo nível: runc.

  • runc é a implementação de referência da especificação OCI (Open Container Initiative). Sua única função é receber uma configuração padrão (um arquivo config.json e um sistema de arquivos raiz) e usar os recursos do kernel do Linux (namespaces, cgroups, que vimos na lição anterior) para criar um contêiner.

Depois que runc cria o processo do contêiner, ele encerra. Ele não fica em execução para monitorar o contêiner. Essa tarefa é deixada para o próximo componente.

4. containerd-shim: O Elo Perdido

Então, se runc sai de cena, quem fica como pai do processo do contêiner? E como containerd (ou dockerd) se mantém funcional se eles precisarem ser reiniciados? A resposta é o containerd-shim.

Para cada contêiner em execução, um pequeno processo shim é criado. Suas responsabilidades são:

  • Atuar como o pai direto do processo do contêiner, evitando que ele se torne um processo "órfão".
  • Manter os pipes de entrada/saída padrão (STDIN, STDOUT, STDERR) abertos para o contêiner, mesmo que dockerd ou containerd sejam reiniciados.
  • Reportar o status de saída do contêiner de volta para o containerd quando ele terminar.

Essa camada de shim é o que permite a funcionalidade "Live Restore" do Docker, garantindo que seus contêineres continuem rodando mesmo se você atualizar ou reiniciar o daemon do Docker.

Teste seu entendimento!

Você precisa depurar um problema onde um contêiner está sendo criado, mas falha imediatamente. Você suspeita que o problema está na forma como os namespaces e cgroups estão sendo aplicados. Qual componente da arquitetura Docker é o principal responsável por interagir diretamente com o kernel para aplicar essas configurações e, portanto, seria o foco principal de sua investigação em nível de sistema?

Show answer

O componente a ser investigado seria o runc.

Enquanto dockerd orquestra a operação e containerd gerencia o ciclo de vida e prepara o "pacote" do contêiner (imagem e configuração), é o runc que efetivamente executa as chamadas de sistema (syscalls) para o kernel do Linux para criar os namespaces, aplicar os limites dos cgroups e iniciar o processo isolado. Uma falha nesse nível aponta diretamente para a interação do runc com o kernel.


Uma Exploração Prática (Opcional)

Sua preferência por entender "como as coisas funcionam" me leva a sugerir um recurso mais aprofundado. O artigo a seguir não apenas explica a arquitetura, mas também mostra como usar as ferramentas de linha de comando de cada camada (ctr para containerd e o próprio runc) para executar contêineres, contornando o dockerd.

Dado seu background com Linux e desenvolvimento, seguir esses experimentos pode ser extremamente revelador.

Docker: Internal Architecture

Para uma experiência prática que solidifica o entendimento da arquitetura modular, este artigo do desenvolvedor 'kt' é excepcional. Ele demonstra como inspecionar a árvore de processos e interagir diretamente com containerd e runc.

Recomendo fortemente que você leia as seções 1, 2 e 3. A seção 1 ('Grasping the Big Picture') mostra como usar pstree para visualizar a hierarquia dockerd -> containerd -> containerd-shim -> container_process. As seções 2 e 3 guiam você na execução de um contêiner usando apenas containerd e, em seguida, apenas runc. Tentar esses passos em seu ambiente Docker fornecerá uma compreensão concreta e de baixo nível.


Conclusão

Nesta lição, desvendamos a arquitetura moderna do Docker. Vimos como o que parece ser um único comando docker run é, na verdade, uma cadeia de responsabilidades delegadas entre componentes especializados.

Principais pontos a reter:

  • Arquitetura em Camadas: A arquitetura do Docker é uma pilha: Docker CLI -> dockerd -> containerd -> containerd-shim -> runc -> Kernel Linux.
  • dockerd (Orquestrador): A interface de alto nível que gerencia imagens, volumes e redes e traduz os comandos do usuário em chamadas para o containerd.
  • containerd (Gerente de Ciclo de Vida): Um runtime padrão da indústria focado em baixar imagens e gerenciar o ciclo de vida do contêiner (iniciar, parar).
  • runc (Executor): Um runtime de baixo nível, compatível com OCI, que pega uma configuração e um sistema de arquivos e cria o contêiner usando os recursos do kernel (namespaces e cgroups).
  • containerd-shim (Supervisor): Um processo intermediário que permite que dockerd e containerd sejam reiniciados sem parar os contêineres, garantindo robustez.

Agora que entendemos a arquitetura que inicia um contêiner, a próxima lição focará nos comandos que você usará para interagir com eles. Vamos explorar em detalhes o comando docker run, incluindo como executar contêineres em primeiro plano (interactive) e em segundo plano (detached), e como gerenciar seu estado com docker start, stop, restart e remove.