Containers e Docker

Containers e Docker

O texto que se segue é, em verdade, uma espécie de resumo daquilo que apresentei recentemente no Braga.JS Não é um “Docker 101”, pois pressupõe que já ouviram falar da tecnologia (mesmo que nunca a tenha usado). Há bastante tópicos que não foram abordados e que deixo para outro post.

Fui apresentado ao mundo dos containers (e, em especial, ao Docker) há cerca de dois anos atrás, na MOG Technologies. Desde então, tenho utilizar todo o seu ecossistema em situações bastante diferentes, com propósitos e objetivos distintos (exemplos mais para o fim do post). Comecemos pelo básico.

O Básico

Aquilo que conhecemos como Docker é, na verdade, o Docker Engine, ou seja, o core que permite criar imagens e correr containers. Mas aquilo que o Docker oferece, tipicamente chamado de Docker Toolbox é bem mais completo e interessante. Vamos por partes.

Tipicamente tudo começa no Dockerfile, um ficheiro de configuração, com uma sintaxe bastante simples e intuitiva, que nos permite escrever “receitas”:

FROM ubuntu:16.04

RUN apt-get update
RUN apt-get install -y software-properties-common python
RUN apt-get install -y nodejs

RUN mkdir /var/www

ADD app.js /var/www/app.js

Tendo a nossa “receita” definida, utilizamos o comando build do Docker Engine para construir a imagem resultante. Aqui existem várias nuances interessantes. Primeiro, todas as imagens começam de uma imagem base (no caso anterior, estamos a usar um Ubuntu 16.04). Depois, cada comando é construído sequencialmente, em camadas read-only. Esta filosofia é bastante importante, pois permite tirar partido de mecanismos de “cache”, fazendo com que se poupe tempo de construção das imagens e espaço em disco.

Um aparte: isto significa que temos que ser inteligente ao construir o nosso Dockerfile. Ficheiros que mudem constantemente devem ser incluídos tão tarde quanto possível no processo de construção da imagem, de forma a que invalide o menor número possível de camadas em cache. Isto significa, tomando o exemplo anterior, que uma alteração no ficheiro app.js iria implicar apenas a construção da última camada!

Todo este mecanismo permite que as camadas que compõe uma imagem possa ser reaproveitadas pelo Docker Engine. Dado que essas camadas são apenas de leitura, isto signfica que quando instanciamos um container, estamos na verdade, a criar uma pequena camada temporária de escrita. Quando o container é criado, esta camada de RW é criada e quando ele é destruído, a sua camada também o é (a não ser que indiquemos explicitamente que as alterações feitas sejam guardadas). Como cada container tem a sua própria camada de escrita (e todas as mudanças feitas são guardadas nessa camada), isto significa que é possível instanciar vários containers sobre a mesma imagem!

Através do comando run, conseguimos correr correr containers Docker. Porém, imaginemos que queremos servir uma API pública em nodeJS para aceder a uma base de dados mongoDB. Poderíamos, obviamente, correr o comando docker run para um dos containers, expôr a porta da API pública para o exterior e fazer a ligação entre os dois containers para que a aplicação em nodeJS consiga aceder à base de dados. É aqui que entra o Docker Compose, pois permite-nos, através de um ficheiro bastante simples, realizar operações bastante complexas de networking:

db:
  image: mongo
  command: "--smallfiles --logpath=/dev/null"
web:
  build: .
  command: node app.js
  ports:
    - "80:3000"
  links:
    - db

Vamos por partes. Estamos a criar dois containers, o “db” (baseado numa imagem do mongo) e o “web” (construído “na hora” pelo Docker Engine). O container “web” quando é iniciado corre o comando node app.js e expõe a sua porta interna 3000 (default do nodeJS) para a porta 80 do host onde está a correr. Por fim, a “magia” do “links”, permite que se crie uma ligação entre os dois containers. Isto significa que não temos forma de aceder diretamente ao container que contém a base de dados, a não ser através da nossa aplicação.

Imaginemos agora que lançamos a nossa aplicação, começamos a ter bastante visitas e, portanto, queremos escalar a nossa aplicação on-demand com vários containers web. Voilà Docker Swarm, um gestor de clusters integrado com o Engine, que permite, definir qual o número mínimo e máximo de cada tipo de containers, qual deve ser a estratégia em caso de erro/falha, fornecendo serviços de service discovery, load balancing, entre outros.

Até agora temos vindo a falar de imagens, de construir imagens com base num modelo de stacking layers, mas ainda não falamos do Docker Registry. O Docker Registry não é mais que um repositório que permite a partilha de imagens de uma forma distribuída (pensem-no como um “git para containers”). O maior “repositório” público (e default do Engine) é o Docker Hub, porém qualquer pessoa pode instalar um Docker Registry e usá-lo internamente.

O Ecossistema

A popularidade do Docker nos últimos anos fez com que a comunidade desenvolvesse não só as ferramentas da Toolbox, como também criasse um ecossistema à sua volta. Sejam ferramentas de monitorização como o Scout, Portainer, Prometheus, Weave Scope, Datadog, cAdvisor da Google; ferramentas de orquestração como o Helios da Spotify ou o Apache Mesos; soluções mais abrangentes e completas como o Kubernetes ou até mesmo sistemas operativos desenhados a pensar nos containers, como o CoreOS. A quantidade de projetos (open-source, grátis até ou determinado nível/limite ou pagos) aumenta de dia para dia.

Para além disso, o número de imagens oficiais no Docker Hub também cresce diariamente, agregando imagens de tudo e mais alguma coisa.

Todo este desenvolvimento e apoio da comunidade significa uma menor necessidade de “reinventar a roda”.

Casos de Uso

Microserviços

A crescente adoção de uma arquitetura apoiada em microserviços é um match perfeito com o Docker, permitindo de uma forma fácil que cada um tenha o seu ambiente específico, escale consoante seja necessário.

Desenvolver Aplicações Linux em Windows

Com a possibilidade de correr containers Linux num ambiente Windows, abre-se a possibilidade de desenvolver projectos num ambiente Linux a partir de outros sistemas operativos (Windows, por exemplo).

Manter Aplicações Self-deployed

Querem ter um Gitlab com alguns runners de CI? Ou um Jenkins? Ou um Redmine? Os Docker containers são o ideal. É fácil manter o ambiente isolado, a manutenção é simples e a atualização para novas versões também é trivial.

Permitir Playgrounds de testes rápidos

Este é, possivelmente, o caso de uso mais utilizado. Tipicamente arrancar um container leva meros segundos. O mesmo para o desligar. Arrancar vários containers é um processo rápido e trivial. Constuir um ambiente sandbox com uma aplicação Ruby on Rails, uma base de dados Postgres e um Redis é simples e rápido e facilmente partilhável com outras pessoas!

Querem experimentar a nova versão de nodeJS? Fácil. Querem experimentar um cenário de testes para a vossa aplicação nas condições X?

Porque gosto do Docker

Portabilidade

Apesar de ser um assunto polémico dentro da comunidade, a verdade é que os containers Docker são bastante portáveis entre a maioria dos sistemas operativos. A facilidade com que pegamos numa imagem de um local A e a colocamos num local B, exatamente com o mesmo comportamento, é brutal.

Colaboração

De um ponto de vista integrador, o Docker mostra-se como o “one-size-fits-all solution”. Uma verdadeira ponte entre developers QA e DevOps, permitindo uma variedade de workflows totalmente flexível e adaptável a vários cenários. A facilidade com que é possível partilhar imagens permite trabalhar colaborativamente de uma forma que não era possível com as Virtual Machines.

Previsibilidade & Consistência

Esta é a minha característica favorita. A segurança que tenho ao fazer uma imagem e saber que vai correr no computador dos meus colegas é uma sensação fantástica. Acabou-se o “works for me” (“funciona no meu PC”) e o “*ah, não estava a dar porque tinha a versão da tecnologia X errada!”.

Sobre a apresentação

comments powered by Disqus