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”:
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:
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
Yesterday's edition of Braga JS was one of the best so far, with talks from @miguelpoeira and @naps62. Stay tuned for more news soon. pic.twitter.com/bB8otfUFUH
— Subvisual (@subvisual) 12 de abril de 2017