Nós comentamos no último post sobre o assunto sobre como os runtimes de containers funcionam, além disso falamos sobre o que é o OCI, o que é um CRI e também criamos um container usando ContainerD diretamente através da integração com a aplicação.

Porém, o ContainerD não é o único runtime existente, vamos entender um pouco sobre o que é o ContainerD, quais as suas vantagens e desvantagens sobre o seu par CRI-O e como o Docker se encaixa nisso tudo.

Docker

Como eu comentei no meu artigo sobre a história do Docker, antes da versão 1.11, o Docker era considerado um único monolito. Tudo que podia ser feito por um runtime de containers estava sendo feito pelo Docker em um único local, o download das imagens, rede, gerenciamento de ciclo de vida e tudo mais em um único processo rodando como root.

Porém, como qualquer sistema que cresce ao longo do tempo, o Docker não podia se manter monolítico para sempre, isso ia começar a pesar na arquitetura e estava começando a dificultar a integração com sistemas operacionais como o Linux. Até que alguém teve uma ideia de separar o Docker em diversos pedaços.

Em uma nota de lançamento da versão 1.11, a empresa disse que iria quebrar o daemon do Docker e começar a utilizar o runC juntamente com o containerd. Isso significava muita coisa para o ecossistema, pois agora o Docker estaria de fato utilizando o mesmo runtime que foi doado por eles em 2015 para a CNCF, criando a OCI. Dessa forma não só teríamos um modelo de containers padronizado, mas também que poderia ser evoluído ao longo dos anos de forma independente.

Ter uma ferramenta dividida em seções mais especializadas faz com que outras pessoas se especializem em manter essas seções, de forma que a aplicação como um todo se torna melhor por ser mais bem desenvolvida. Na mesma nota eles mostraram o novo modelo de funcionamento do Docker:

Modelo de funcionamento do Docker (Fonte: Docker)

Podemos ver que o engine (dockerd) principal do Docker foi separado do runtime, e agora ele se concentrava somente em receber os inputs do usuário e comunicar os mesmos ao containerd, que por sua vez iria iniciar os runtimes do runC ou qualquer outro runtime que fosse compatível com o OCI.

Em suma, dando nome a todas as partes:

  • Docker Engine (ou Docker Daemon, dockerd): É o responsável por receber os comandos do usuário pelo CLI e passar essas requisições para o containerd.
  • containerd: Interface compatível com o OCI capaz de executar, baixar e extrair qualquer imagem que seja também compatível com a OCI e executar processos do runC.
  • runC: Lida com todo o gerenciamento e criação dos containers de acordo com a especificação do OCI.

Isso criou uma série de aberturas para que outras pessoas e outras empresas também fizessem seus próprios runtimes de containers, e agora vamos entender a diferença entre os dois principais.

containerd

Conforme falamos nos artigos citados, o containerd é um daemon que executa e controla instancias do runC. Gerenciando seu ciclo de vida, bem como toda a parte de transferência e extração de imagens, armazenamento, rede e etc. Isso é o que a gente chama de container engine.

O containerd é a ferramenta principal que abstrai grande parte da funcionalidade que o Docker acumulava como um todo, se fossemos simplificar, podemos dizer que o containerd é o conjunto de funções mínimas que um container runtime precisa para executar qualquer container.

O containerd ajuda a abstrair as chamadas de kernel (syscalls) para que containers possam executar da mesma forma em qualquer sistema operacional, independente do que está acontecendo embaixo deles. Isso é chamado de supervisão e é um padrão muito comum quando estamos executando VMs.

Sempre que rodamos um SO dentro de outro, o SO convidado não sabe que está rodando dentro de uma VM, então ele continua chamando as syscalls necessárias para se comunicar com o hardware, e é ai que entra o trabalho do hypervisor, que é abstrair essas chamadas de sistema para que o SO convidado não precise implementar cada kernel individual se ele estiver rodando dentro, por exemplo, de uma máquina Linux.

O containerd é essa implementação, de forma que outras ferramentas podem também construir suas implementações de runtime sobre ele sem se preocupar em qual sistema operacional estão rodando, já que o containerd abstrai toda essa funcionalidade.

Vantagens do containerd

  • Você tem completo controle de imagens, podendo baixar e enviar imagens para registros
  • Permite a utilização via API dentro da sua própria linguagem de programação
  • Quando dentro do Kubernetes, pode ser usado como CRI
  • Totalmente configurável
  • Permite gerenciar ciclos de vida de containers através de uma API
  • Gerenciamento de armazenamento e snapshots
  • Extensível
  • Abstrai completamente o SO que está rodando

CRI-O

Além do containerd, outro runtime famoso é o CRI-O. O CRI vem de Container Runtime Interface, que é um plugin que expõe uma interface que permite que um Kubelet (agent que roda dentro de cada nó dentro de um cluster do Kubernetes) use diferentes tipos de runtime compatíveis com a especificação da OCI sem precisar de recompilação ou reinicialização. O runC é o runtime mais famoso, porém temos outros como o crun, railcar e o kata.

Não vamos entrar em detalhes sobre o porquê do Kubernetes precisar de um CRI, pois já falamos sobre tudo isso no post anterior

Tendo em vista tudo isso, o CRI-O foi criado especificamente para permitir que o Kubernetes execute containers sem muito código ou ferramentas externas. Isto porque o CRI-O é construído em diversas bibliotecas diferentes, veja alguns de seus componentes:

  • Container Runtime compatível com a OCI, por padrão é o runC mas pode ser qualquer um
  • containers/storage: Módulo responsável por lidar com os layers de armazenamento e filesystems
  • containers/image: Módulo responsável por baixar imagens de registries
  • networking: Usado para criar a camada de rede dos pods, existem vários plugins chamados de CNI que podem ser utilizados
  • conmon: Um utilitário chamado de "container monitoring" que serve para monitorar o que está acontecendo dentro dos containers

A imagem abaixo ilustra todo o processo de vida do CRI-O dentro de um cluster Kubernetes:

Processo de uso do CRI-O dentro do Kubernetes (Fonte: CRI-O)

Conclusão

Apesar de estarmos acostumados com o Docker, existem ainda muitas outras ferramentas e ideias que estão pairando sobre nossas cabeças quando o assunto é containers. Usei este artigo como base para poder escrever este conteúdo e aconselho fortemente que vocês vejam os demais artigos relacionados.

Até mais!