Site menu Docker: máquinas virtuais leves

Docker: máquinas virtuais leves

Tecnicamente, o Docker é uma tecnologia de "máquinas virtuais leves". A leveza é tanta que abre espaço para novos métodos de desenvolvimento e distribuição de aplicativos.

Um hospedeiro do tipo VMWare ESXi permite rodar diversas máquinas virtuais (VMs) em uma máquina física, o que é extremamente útil e economiza muito hardware. Porém, há algumas desvantagens. Existe uma perda de performance, e é preciso reservar RAM para cada VM. Se a VM está ociosa, aquele bloco de RAM (que é um recurso escasso e caro) também fica ocioso.

O equivalente Docker da máquina virtual é o container. Ele é virtualizado em termos de recursos: sistemas de arquivos, conexões de rede, número de CPUs, permissões, etc. O kernel do sistema operacional do container é o mesmo do hospedeiro.

A virtualização e a isolação do container fazem uso de recursos presentes há muito tempo no kernel do Linux, principalmente cgroups e unionfs. Isto significa que, a rigor, tanto o hospedeiro quanto os containers têm de rodar Linux. (O Docker para Windows e para Mac rodam o hospedeiro numa máquina virtual para fazer a adaptação.)

O fato do container ser sempre Linux é uma limitação. Mas, num mundo em que praticamente todo serviço de nuvem roda em servidores Linux, ninguém se importa.

Figura 1: Primeira execução de um container Ubuntu, num Mac. A imagem `ubuntu` é automaticamente baixada do Docker Hub se não estiver disponível localmente.

Frente às VMs convencionais, os containers são muito mais "leves" e usam o hardware da forma mais eficiente possível. Não há reserva de RAM, os diversos containers compartilham toda a RAM da máquina como se fossem processos.

Muitos aplicativos fazem uso dos recursos de auto-contenção por conta própria, para fins de segurança. O que o Docker oferece a mais é uma interface padrão, bem documentada e fácil de usar.

O que o Docker oferece a mais

Embora seja um conceito poderoso, não é realmente difícil criar uma "máquina virtual leve" no estilo container, nem é preciso usar o Docker para isso. O "tchan" do Docker está na interface padrão e nas ferramentas embutidas, com vantagens bem palpáveis para desenvolvedores e para o TI:

Ferramenta Vantagens
Um container Docker pode ser criado e executado em Linux, Windows e Mac (e FreeBSD, e possivelmente outros conforme o tempo passa). O desenvolvedor pode executar aplicativos Linux em sua própria máquina, seja qual for seu sistema predileto.

Servidores Windows podem rodar serviços Linux facilmente.
O Docker implementa uma linguagem de script (Dockerfile) que padroniza a geração da imagem. (Imagem é o conteúdo inicial do sistema de arquivos de um container.) A geração do container pode fazer parte do código-fonte do aplicativo, pode ser delegada ao desenvolvedor, e pode ser automatizada. A carga de trabalho do TI é aliviada.

Delegar a criação da imagem ao desenvolvedor mitiga o velho problema de um aplicativo executar no ambiente de desenvolvimento/teste, e misteriosamente falhar em produção.
O Docker Hub é um repositório público de imagens, o que torna extremamente fácil obter da Internet uma imagem, e também publicar imagens para outros usarem. Todos os aplicativos importantes e as principais distros Linux publicam imagens atualizadas por lá.

Em quase 100% dos casos, imagens e containers são baseados em imagens preexistentes do Docker Hub.
Serviços que oferecem imagens "prontas para uso" podem ser colocados a rodar, e atualizados, com esforço quase zero por parte do TI.

Uma imagem criada pelo desenvolvedor pode basear-se na imagem pública muito próxima do resultado final desejado, minimizando o esforço inicial em escrever o Dockerfile.
Uma imagem pode ser baseada em outra imagem, e o espaço ocupado em disco pela imagem-filha é apenas a diferença entre as duas. Da mesma forma, um container só ocupa espaço em disco na medida em que seu conteúdo diverge da imagem inicial. Pode-se manter todas as versões antigas de um container, para o caso de um serviço em produção precisar voltar a uma versão anterior, sem preocupações com espaço em disco.

Se fosse para resumir numa frase, o Docker segue a tendência da "infraestrutura-como-código", que empodera o desenvolvedor e desamarra o aplicativo do ambiente de produção, seja Linux, Windows, nuvem A, nuvem B, etc.

Filosofia do Docker

Na minha opinião, a característica mais marcante de um container Docker é que há apenas um processo principal por container.

Se o seu aplicativo usa um servidor Web, um banco de dados e executa duas rotinas periódicas, haverá quatro containers (dois de execução contínua e dois intermitentes). O log de um container é simplesmente a saída de terminal (stdout/stderr) do processo principal.

Aplicativos no "estilo antigo" podem exigir algum contorcionismo na adaptação ao Docker. Por exemplo, se dois ou mais processos trocam dados via arquivos em uma determinada pasta, cria-se um volume Docker que será montado pelos diversos containers que precisem dessa pasta.

Por padrão, o processo roda com permissões de root (administrador) dentro do container. Isto não é um problema imediato de segurança porque o aplicativo está "preso" dentro do container. Claro, continua sendo uma boa prática renunciar a direitos desnecessários, tanto que softwares de primeira classe como Apache, NGINX, MySQL, etc. fazem isso sempre, estejam fora ou dentro de um container.

Já mencionamos imagens e containers. A diferença entre uns e outros é talvez o ponto mais confuso do Docker. Em essência, uma imagem é o modelo estático de um container, e um container é uma imagem em execução.

Figura 2: Imagens presentes no meu Docker. (A interface administrativa Portainer não faz parte do Docker-base, mas pode ser instalado facilmente com dois comandos do Docker, já que roda como um container.)
Figura 3: Containers presentes no meu Docker. (Ache o ovo-de-Páscoa.) O Docker atribui nomes aleatórios a containers sem nome definido pelo usuário, além do hash ID.

Esta relação entre imagens e containers tem algumas peculiaridades:

Figura 4: Consulta de containers ativos e inativos via linha de comando.
Figura 5: Reutilização de um container Ubuntu que estava parado. É preciso usar os parâmetros -i -t para obter um prompt.

Apesar de ser possível parar e reiniciar um container muitas vezes sem perder os arquivos que estão lá dentro, o conteúdo do container deve ser tratado como volátil e descartável. Arquivos que precisem "sobreviver" à execução do container devem ser alojados em volumes.

Noves fora o caso de arquivos em volumes, a aplicação dockerizada ideal não grava arquivo nenhum, e o tamanho efetivo do seu container é zero, pois não faz nenhuma modificação no sistema de arquivos herdado do modelo.

Servidores como gado

O tratamento dos containers como entes descartáveis é parte da tendência de tratar servidores como gado, sem valor enquanto indivíduos, gerenciados de forma automatizada. Historicamente, a regra é tratar servidores como "pets": instalados e mantidos por administradores dedicados, com expectativa de longa durabilidade.

Obviamente, um container Docker precisa de um servidor físico para rodar, porém é muito mais fácil configurar um servidor Docker do que configurar quinze servidores virtuais. E mesmo a figura do servidor físico vai dando lugar para a execução direta de containers na nuvem.

Pessoalmente, vejo esta tendência não como uma abolição das tarefas típicas de TI, mas sim sua absorção pelo processo de desenvolvimento. Em lugar da tradicional separação entre desenvolvimento e produção, o próprio código-fonte descreve a infra-estrutura de que precisa para rodar.

Clusters e orquestras

O Docker dá ferramentas para criar e manipular containers, mas não toma decisões administrativas.

Sim, é possível marcar um container para execução contínua, e o Docker reinicia-o automaticamente em caso de quebra ou de reboot do servidor, mas isso é tudo. Se esse container for movido para outro servidor Docker, tal marcação se perde e tem de ser refeita.

Da mesma forma, a execução periódica de containers intermitentes precisa ser feita por alguma ferramenta externa, como por exemplo o crontab.

O ideal seria poder comprar um servidor com o Docker já instalado de fábrica, ou então contratar um serviço de nuvem, e passar uma "receita" especificando containers permanentes, intermitentes, etc.

Outro desafio com raízes semelhantes é a escalabilidade. Pode ser necessário usar um cluster de servidores Docker para dar conta do trabalho, e/ou rodar várias instâncias de um mesmo container, de preferência em máquinas ou pelo menos CPUs diferentes, para atender os muitos usuários de um serviço.

A este conjunto de problemas se dá o nome de orquestração. Há basicamente duas opções a considerar: Docker Swarm (da mesma equipe do Docker) e Kubernetes (do Google).

O Kubernetes é compatível com muitas tecnologias de containers, máquinas virtuais e nuvens, e é capaz de orquestrar infra-estruturas heterogêneas. Talvez por conta disso, tem se firmado como o padrão da indústria, a ponto do Docker para Mac vir com Kubernetes embutido.