Acharam que iam se livrar de mim né? estão muito enganados :). Você já sabe tudo sobre de onde vieram os containers, já sabe como instalar a plataforma mais completa de containers, o Docker e hoje você entenderá como é possível pegar aquela sua aplicação e colocar em um container ;).
Bom, vamos começar por um ponto bem importante: Qual a melhor forma de fazer isso? Existem algumas formas. Umas melhores, outras piores, outras bem inusitadas, mas o que veremos é a mais tradicional, que é utilizando o Dockerfile. Porém, antes precisamos saber o que é o Dockerfile. Vamos lá!
O Dockerfile
De modo simples o Dockerfile é um arquivo de texto descritivo de como deve ser seu ambiente para executar a sua aplicação. Ou seja, é nele que você descreve quais pacotes precisam ser instalados, quais versões, etc.
É claro que você deve prestar atenção em alguns detalhes de como criar o Dockerfile, por exemplo. Você deve sempre se basear em uma imagem Docker já existente, outro ponto é que quanto mais instruções seu Dockerfile tiver, mais camadas a imagem terá e possivelmente será maior também. Abaixo te trago um exemplo bem simples de um Dockerfile para uma aplicação em Java:
# Herda de uma imagem base.
FROM dockerfile/ubuntu
# Instala o java e suas dependências
RUN \
apt-get update && \
apt-get install -y openjdk-8-jdk && \
rm -rf /var/lib/apt/lists/*
# Define diretório onde estará sua aplicação
WORKDIR /data
# Define a variavelJAVA_HOME
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
# Define comando de inicialização do container
CMD ["bash"]
Esse pequeno trecho de código é o suficiente para que possamos criar nossa própria imagem Docker, para que com ela seja possível executar os nossos containers. Note que os comandos que aí estão são exatamente os mesmos que devem ser executados em um host Linux tradicional para a instalação do Java, bem simples não é mesmo?
A Imagem
Certo, já sabemos o que é o Dockerfile, mas afinal, para que ele serve? A resposta é simples: Serve para gerar uma imagem, que nada mais é do que um “artefato” que será utilizado como base para a execução de seus containers. Uma imagem possui diversas camadas, e como mencionado acima, elas são originadas pelas instruções utilizadas no Dockerfile.
Por padrão, todas as camadas, exceto a mais superficial, são readonly, ou seja, não podem ser sobrescritas, isso facilita e é o que torna as imagens tão versáteis, pois essas camadas podem ser reutilizadas em mais de uma imagem, bem loco né? Veja na imagem abaixo como é a estrutura de uma imagem:
Note que todo container executa utilizando uma imagem, no entanto, como apenas a camada mais superficial (a que o container visualiza) pode ser escrita, é possível utilizar essa mesma imagem para vários containers, pois a única camada de escrita sempre será diferente para cada container.
Unindo as coisas – Node.JS
Já sabemos o que é o Dockerfile e para que ele serve, já sabemos que o resultado do Dockerfile é uma imagem, e sabemos para o que ela serve também, agora basta juntarmos essas duas informações para criar nossas próprias imagens e executar nossos containers onde precisarmos.
Vamos começar com algo simples, agora é o momento de criarmos nossa imagem para nossa aplicação em node \o/. O primeiro passo é criarmos nosso Dockerfile, veja o exemplo abaixo:
FROM node:8
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "npm", "start" ]
Em seguida, vamos montar a nossa aplicação, para isso vamos criar um arquivo chamado package.json com o seguinte conteúdo:
{
"name": "umbler_web_app",
"version": "1.0.0",
"description": "Node.js no Docker",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.16.1"
}
}
O package.json serve para declarar as dependências de nossa aplicação node. Assim é possível definir algumas ações, por exemplo, como a aplicação deve ser inicializada.
O próximo passo é criar um arquivo, que vamos chamá-lo de server.js e adicionar o seguinte conteúdo:
'use strict';
const express = require('express');
const PORT = 3000;
const HOST = '0.0.0.0';
const app = express();
app.get('/', (req, res) => {
res.send('Ola mundo\n');
})
app.listen(PORT, HOST);
console.log(`Running on https://${HOST}:${PORT}`);
Ok, lembre de criar todos os arquivos no mesmo diretório, em seguida vamos executar um comando que gerará a imagem. Execute:
$docker build -t “meu-node”
Note que este comando executará as instruções do Dockerfile passo-a-passo e, no caso de estar tudo certo, ele criará uma nova imagem chamada meu-node. Para visualizar a imagem você deve executar:
$docker images
Veja que aparecerá essa imagem e mais uma imagem denominada node, com a tag 8, que como você pode ter notado é a imagem base utilizada pelo nosso Dockerfile.
Certo Cristiano, como eu sei que isso irá funcionar?
Simples, vamos criar nossos containers. 😉
Para isso, precisamos executar o seguinte comando:
$docker run -d --name app1 -p 3000:300 meu-node
Dessa forma, se você acessar: https://ip-do-host:3000 você visualizará a aplicação node que criamos.
Para criar mais containers, basta você executar o mesmo comando, mudando apenas a porta exposta, visto que uma porta TCP pode ser utilizada apenas uma vez, faça isso mudando o primeiro atributo do parâmetro -p.
Unindo as coisas – MongoDB
Já fizemos nossa aplicação em node.js executar em container, criamos uma imagem para isso, agora vamos seguir a mesma lógica para containerizar o MongoDB.
Precisamos cuidar um detalhe. Como sabemos, é muito importante que os dados de um banco de dados nunca sejam perdidos, não é mesmo? Para isso, quando você utilizar Docker para executar seu banco de dados, lembre-se de mapear uma pasta no host para onde serão salvos os dados que devem ser persistidos. Vamos entender melhor mais adiante.
Diferentemente de nossa aplicação node, não precisamos criar uma imagem específica para mongodb, necessariamente (a não ser é claro que você possua algo muito particular), podemos utilizar a imagem oficial que existe no hub do Docker. Antes, vamos preparar o ambiente, para isso:
$mkdir ~/data
Dessa forma criamos uma pasta na home do usuário chamada “data” onde serão armazenados os dados do mongo, que serão persistidos, para que não sejam perdidos caso o container morra. Em seguida, vamos criar o container de mongo:
$docker run -d --name mongodb -p 27017:27017 -v ~/data:/data/db mongo
Note que neste comando temos um parâmetro novo, que é o -v, que serve para realizar o mapeamento de um diretório do host para o container. Com isso, os dados salvos dentro desse diretório no container serão persistidos no host. Ou seja, caso o container morra, basta subir outro container de mongo mapeando o mesmo diretório.
Outro ponto importante: no exemplo acima não tratamos a questão de autenticação para acesso ao banco de dados. Ou seja, NÃO use essa forma em produção, ok?
Ahh, mas eu quero saber como a imagem de mongo foi feita.
Não tem problema, você pode acessar este link e ler o Dockerfile utilizado para gerar essa imagem 😉
Unindo as coisas – Node.JS + MongoDB
Sabendo como subir os dois ambientes, é possível fazer com que os mesmos se comuniquem. Ou seja, que a aplicação em node se conecte como banco mongo, para fazer isso é simples. Obviamente você deve adicionar em seu package.json algum driver de acesso ao banco mongo (normalmente mongoose). Após isso, basta você editar a connection string para acessar o nome do container de mongo ou até mesmo o ip do host na porta do mongo, 27017.
Assim sua aplicação node conseguirá acessar o banco mongo, isso tudo sem precisar instalar o framework, serviço do banco de dados, etc. É necessário apenas ter o Docker instalado.
Existe ainda uma forma mais fácil de ter esse ambiente, que é utilizando o Docker Compose, mas isso é assunto para um próximo post 🙂
Por hora, é isso que gostaria de apresentar. Qualquer coisa comenta aí que vamos conversando, ok?
Abraço!