Docker – O que é, o que não é, e alguns detalhes a mais

Logotipo do DockerNo último post, eu falei um pouco sobre o Vagrant,  que é uma ferramenta que permite criar e distribuir máquinas virtuais voltadas especificadamente para desenvolvimento de software. Pois bem, quem já é mais antenado nas últimas novidades, ao ler o último post, deve ter pensado algo como “nossa, mas já temos o Docker, e containers são muito melhores que máquinas virtuais!!!!!”. E bem, na verdade, tempos atrás, eu mesmo teria dito o mesmo sobre quem ousasse comentar sobre Vagrant, máquinas virtuais e tal. Por isso, hoje, vou falar sobre o Docker, o que ele faz, o que ele não faz, seus recursos, uma breve comparação com o Vagrant e todo o contexto das máquinas virtuais e também um breve tutorial de uso.

O que é o Docker, afinal?

Bom, para quem já experimentou o Docker, a plataforma realmente parece ser mil maravilhas e aplicável a qualquer caso de uso. Mas, em termos práticos, o Docker nada mais é do que um wrapper turbinado (ele acrescenta alguns recursos a mais, que vou falar mais abaixo) em torno de um sub-sistema do kernel do Linux que até algum tempo atrás era considerada meio obscura, chamada LXC (abreviação para Linux Containers, sacou a semelhança? hein? hein?). Esse sub-sistema implementa uma porrada coleção de ferramentas, templates, bibliotecas e ligações de linguagens que fornece, esses sim, os famosos containers, que são bem pouco comparáveis em relação a máquinas virtuais mais parrudas, como as fornecidas por hypervisors como VMWare (normalmente usado para virtualizar servidores) e Virtualbox (que é mais ideal para desenvolvimento) e afins. Mas..

Mas, o que são esses containers?

Containers são uma forma de virtualização a nível de sistema operacional que permite rodar múltiplos “sistemas” isolados em um único sistema operacional real. Esses sistemas isolados conseguem ser, a partir da proteção dos containers, efetivamente isolados e limitados tanto em uso de disco, quanto memória RAM e CPU. Só que, embora num momento pareçam ser iguais às máquinas virtuais fornecidas por VMWare e Virtualbox, eles não são: Containers usam um truque de compartilhamento de Kernel para “poupar” recursos, e daí vem o fato de serem uma forma de virtualização a nível de sistema operacional.

Como o kernel é compartilhado entre os vários containers, o container acaba por não poder rodar sistemas que tenham kernel diferente do da máquina hospedeira em containers. Isso traz dois pontos importantes para o jogo:

  • Na virtualização a nível de sistema operacional, você não possui um hardware emulado/virtualizado, mas sim um acesso mais limitado ao hardware. O que isso significa? Significa que, no VMWare ou Virtualbox, que implementam a chamada virtualização “completa”, a virtualização ocorre efetivamente a baixo nível, e em teoria mesmo chamadas associadas do sistema operacional diretamente ao processador passam e são atendidas pelo hypervisor, que então aloca o recurso real ou nega o acesso, conforme os limites definidos, enquanto que, com o uso da virtualização a nível de sistema operacional, o programa usa as APIs do kernel normalmente, e, através desse uso, é limitado conforme os limites definidos para o container em questão. Ou seja, é o kernel quem “mente” para o programa quando diz que não possui acesso à memória RAM ou disco, por exemplo;
  • Além disso, o compartilhamento do kernel se reflete numa limitação básica que a virtualização completa não possui: O kernel precisa ser o mesmo da máquina hospedeira. Ou seja, a menos que dê a louca na Microsoft e ela resolva adotar o kernel do Linux no Windows (eita), você nunca vai ver algo como o LXC rodando containers do Windows, ou vice-versa (Windows rodando containers do Linux, quando a Microsoft resolver criar uma tecnologia de containers pro Windows também..);

Vendo essas duas limitações fundamentais dos containers, você deve estar se perguntando algo como:

Ué, mas se eu posso rodar “máquinas virtuais” do Linux de boas, por qual motivo não posso substituir Vagrant pelo Docker de boas também??

Bom, teoricamente, poder você pode. MAS, isso é uma baita “desrespeito” ao paradigma que o Docker prega, e por desrespeito ao paradigma eu to me referindo que isso é bem..gambiarra.

O que eu estou querendo dizer é que, no Vagrant, com uma máquina virtual, você pode chegar e simplesmente instalar Nginx, MySQL, PHP, NodeJS e o que mais precisar instalar para sua aplicação rodar decentemente. No Docker, a filosofia prega que cada container deve ter uma responsabilidade única, ou seja, Nginx é um container, MySQL é outro container, PHP é outro container e NodeJS é ainda outro container. A principio, isso é bom, pois facilita o teste, atualização, deploy e tudo mais de containers e serviços (inclusive, este blog roda dentro de dois containers), mas… Eventualmente isso torna tudo um bocadinho mais complicado, pois:

  • Você precisa lidar com toda a conexão entre todos os containers (afinal, eles são isolados, se lembra?);
  • Alguns containers eventualmente precisam lidar com os mesmos arquivos (e aí acontece de se perder em permissões de leitura, escrita, acesso e etc);
  • Você precisa dar algum jeito de lidar com logs;
  • Você precisa tomar cuidado com aplicações que precisam armazenar dados permanentemente, pois containers são stateless (ou seja, não salvam nenhum dado internamente) e por isso compartilhamento de arquivos são necessários;
  • E por fim você precisa tomar cuidado manualmente para fazer tarefas que normalmente o sistema operacional faria por você (como lidar com processos zumbis, iniciar syslog, etc. etc. etc.);

Só os dois primeiros são características que, por padrão, usando máquinas virtuais só seriam enfrentados se você efetivamente estivesse lidando com um sistema DISTRIBUÍDO. E é daí que vem o ponto: O Docker pode ser usado para quase toda aplicação? Pode. Mas, ele deve? Não necessariamente, visto que, para muitas coisas, usar Docker direito tende a ser mais trabalhoso do que configurar uma boa máquina virtual que atende as suas necessidades.

Mas, se configurar o Docker direito tende a ser mais trabalhoso do que configurar uma boa máquina virtual, então não devo usá-lo?

Não necessariamente. Os maiores problemas do Docker, às vezes, são suas maiores virtudes. Alguns exemplos disso são os próprios usos que eu já fiz do Docker e ao qual ele atende muito bem:

  • Permitir o uso de versões específicas de linguagens/interpretadores/compiladores/bancos de dados/etc. sem que você precise instalar/compilar tudo manualmente;
  • Testes de novos softwares, pois é mais rápido do que o boot de uma máquina virtual e não polui a sua máquina;
  • Permitir a execução de softwares sem instalar nada na máquina principal;

Esse blog que vos fala, por exemplo, está rodando sob dois containers do Docker, um para o php-fpm, outro para o MySQL. Onde está o Nginx? Está fora, instalado na máquina principal, e aí o Nginx se conecta com o php-fpm para poder renderizar essa página da forma que você vê. Parece complicado? Bom, pode até ser um pouquinho, mas..

  • Eu não preciso instalar PHP no VPS;
  • Eu posso usar diferentes versões do PHP para diferentes projetos (já aconteceu de eu rodar um projeto antigo de um cliente numa versão mais antiga do PHP, e isso em nada interferiu aqui no blog, por exemplo);
  • Eu consigo rodar outros softwares em containers e, todos o que precisam de conexão com o mundo real (porta 80) podem facilmente passar pelo proxy do Nginx, que tem todas as configurações, centralizadas;
  • E assim vai..

Hoje, nesse servidor, eu preferencialmente executo as coisas novas dentro de containers. Isso permite que minha lista de pacotes seja mínima e eu não precise ficar me preocupando com arquivos de configuração aqui e ali. Claaaaro que isso dificulta as coisas quando eu quero instalar algo novo (pois, afinal, sempre quero rodar as coisas dentro de um container) mas no fim as vantagens compensam, como quando eu resolvi instalar um projeto em NodeJS e esse acabar instalando 3812098390182908 dependências, que eu pude apagar simplesmente deletando o container do projeto e recriando apenas com as coisas que eu realmente precisava.

Um ponto que eu acho legal no uso de containers é que, como tudo é isolado, em teoria tudo fica mais seguro. O php-fpm, por exemplo, só consegue enxergar que tem uma conexão com o MySQL, e o MySQL não precisa enxergar nada de fora, ou seja, não precisa se conectar a nenhum container, a nenhuma outra máquina ou mesmo à máquina física em questão. Aliás, os outros projetos que eu executo no servidor teoricamente nem sabem que o php-fpm e o MySQL estão rodando na máquina, também, afinal, até a lista de processos é isolada.

Graças a essas características, hoje, containers são usados em muitos serviços da web, como Heroku (que até onde eu saiba foi meio que pioneiro no uso de containers), Travis CI, Codeship, Deis e muitos serviços no qual você parece ter acesso à uma máquina completa, mas, na verdade, está apenas rodando dentro de um container com outras milhares de aplicações rodando na mesma máquina. =)

No fim das contas, o importante é usar containers (e o Docker, portanto) quando este lhe for realmente conveniente, e não ter como regra sempre usá-lo, mesmo quando seu uso pode vir a ser realmente mais difícil do que deveria. Aliás, essa deveria ser a regra pra todo software, mas, deixo para comentar mais sobre isso noutro post. 😀

E, claro, não posso deixar de destacar três coisas que já falei (ou ao menos mencionei) no post e que são bem relacionadas a respeito do uso de containers:

  • Não use containers da mesma forma que usaria uma máquina virtual;
  • PELO AMOR DE DEUS PAI não use/procure/deseje acesso SSH dentro de containers (pois isso super implica em usar containers como máquinas virtuais, o que não é legal);
  • Nada te proíbe de às vezes misturar softwares que estão em containers com softwares que não estão em containers (e às vezes isso pode ser interessante, inclusive);

Agora que você leu mais de 1500 palavras sobre o que o Docker é, o que não é e alguns pontos legais a respeito, vamos ao:

Tutorial básico de uso do Docker

Nesse tutorial, para seguir mais ou menos a base do tutorial básico de uso do Vagrant, vamos criar um container Ubuntu no Docker, instalar o Apache 2 nele, e depois alterar a página inicial padrão do Apache 2 para “Olá Mundo” básico usando o vim, acessando o interior do container (embora essa não seja a melhor prática de todas, vou fazer isso apenas para seguir a mesma ideia do tutorial básico de uso do Vagrant, mesmo).

1) Baixe e instale o Docker (importante ter uma máquina de 64 bits, pois é o único ambiente no qual o Docker efetivamente roda..);
2) Depois, numa pasta vazia, crie um arquivo com o nome “Dockerfile” e com o conteúdo abaixo:

No arquivo acima, estão especificados os comandos que o Docker deverá seguir na criação da imagem do container. Aqui, vale a especificação do que é imagem e do que é container para o docker:

  • Imagem é uma especificação do que o container vai possuir quando for executado. Ela não é uma entidade ativa (uma imagem não pode ser executada), mas sim passiva, pois apenas armazena as instruções e dados que depois serão usadas para criar o container;
  • Container é basicamente uma imagem sendo executada, acompanhado de informações extras adicionais necessárias para o S.O, como segmentos de dados (data e stack), status do processo e dados extras que o S.O pode necessitar para a execução do programa;

Ou seja, para você possuir um container, você precisa ter uma imagem já construída. Portanto, para ter um container rodando a partir do zero, é necessário, além do Dockerfile, da execução de dois comandos: Um para construir a imagem, e outro para criar um container a partir dessa imagem, diferente do Vagrant que a partir de um único comando já faz tudo automagicamente, mesmo na configuração inicial. Esses comandos serão abordados mais abaixo.

Agora, vamos à descrição do que cada linha faz:

  1. A primeira linha define em que imagem a nossa imagem final deve ser baseada. No nosso caso, trata-se do Ubuntu Trusty, que é definido lá no repositório oficial do Docker;
  2. A segunda linha instrui o Docker a executar, dentro do Ubuntu Trusty, um simples “apt-get update“. Note que, por padrão, os containers já rodam com usuário root, e por isso o “sudo” não é necessário na frente do comando;
  3. A terceira linha o Docker a instalar os pacotes apache2 e vim. O vim não é necessário para a instalação do apache 2, mas vai ser útil para a posterior edição do arquivo HTML padrão do Apache;
  4. A quarta linha instrui o Docker a expor a porta 80 do futuro container por padrão;
  5. A quinta linha instrui o Docker que comando deve ser executado quando o container for iniciado;

3) Agora, na mesma pasta onde você criou o arquivo, execute o comando “docker build -t tutorial-docker .“. A saída final do programa deve ser algo similar a isso:

Note que, no comando acima, a imagem é construída e tageada com a tag “tutorial-docker“. Se não aplicássemos uma tag na imagem, teríamos que usar o hash que o docker gera automaticamente para ela na construção da imagem, o que não é muito amigável para um tutorial.

Com isso, a nossa imagem tutorial-docker vai ter sido criada com perfeito sucesso, baseada no Ubuntu Trusty e já com os pacotes Apache 2 e vim pré-instalados. Agora, vamos criar o container? =)

4) Para criar o container, execute o comando “docker run –name container tutorial-docker -p 3000:80 -d tutorial-docker“, como visto abaixo:

No comando executado para criar o container, “-t <tag>” define a tag que será usada para comandar o container, “-p 3000:80” faz o docker redirecionar a porta 3000 do seu computador para a porta 80 do container, e “-d” manda o docker mandar a execução do container para o background, permitindo assmi que o fluxo normal do terminal ocorra.

Além disso, como mostrado na imagem, “docker ps” faz o papel de “monitor” dos containers em execução, mostrando o que está rodando, que portas está usando e a quanto tempo está executando, além de outras informações.

Note também que, diferente do Vagrant, aqui precisamos fazer um redirecionamento de portas para que seja possível acessar o Apache 2.

5) Agora, vamos alterar o arquivo HTML de boas vindas do Apache para ter nosso Olá Mundo, como fizemos no tutorial básico do Vagrant. A diferença aqui é que, diferente do Vagrant, o acesso ao container não vai ser feito através de SSH, mas sim a partir de um truque do docker chamado “docker exec“.

Outra coisa importante: Como no caso do Vagrant, essa não é a melhor forma de alterar arquivos dentro da máquina, pois todas as alterações serão perdidas caso eventualmente o container seja recriado (o que é necessário quando a imagem recebe uma atualização, por exemplo). Só vamos fazer isso aqui para demonstrar o uso do “docker exec“, mesmo. =)

O comando a ser executado neste caso é “docker exec -ti container-tutorial-docker bash“. Veja abaixo:

Como que o docker exec faz para acessar o interior do container sem ser por SSH? Bom, eu acho que num primeiro momento dá para considerar que a resposta para esta pergunta é..

Mas, sobre o comando “docker exec -ti container-tutorial-docker bash“, basicamente:

  • -ti” faz o Docker manter a conexão com o terminal aberta (o docker exec por padrão fecha todas as conexões com o seu terminal e apenas executa o programa especificado no container);
  • container-tutorial-docker” é o nome do nosso container, como esperado;
  • bash” é o nome do programa (localizado dentro do container) que desejamos executar;

E com isso temos o arquivo HTML alterado corretamente e tudo funcionando usando Docker, de boas. \o/

Uma observação: Nesse exemplo do tutorial, embora o docker exec tenha sido usado apenas de modo ilustrativo, o container, por si só, não quebrou o paradigma do Docker de cada container ter uma responsabilidade única. Isso porque o Apache apenas serve arquivos, e o container executa o Apache, então está tudo certo, assim. =)

Bom, espero que tenham gostado do tutorial. Foram mais de 2600 palavras investido num post que eu precisava fazer a algum tempo, mas que só com as pesquisas recentes permitiram que eu escrevesse de forma mais sábia sobre o assunto. Não é pouca palavra não. 😛

Ah, e quem quiser saber mais sobre o Docker, dá uma estudada na documentação, que é bem organizada e é bem completa, explicando desde como criar um Dockerfile a como usar os vários comandos do Docker de forma fácil e rápida.

Por fim, compartilhe, e deixe seus comentários, sua opinião é muito importante para mim e mesmo dúvidas ou sugestões vão me ajudar a melhorar ainda mais o conteúdo desse e dos futuros posts. Ficam os links relacionados ao Docker:

Gostou do conteúdo desse post? Apoie o blog pagando a partir de R$1/mês através da nossa página no Apoia.se! Isso me ajudará a manter o blog no ar, além de trazer mais coisas legais para vocês! Obrigado desde já! 😀