Neste post vou fazer uma introdução sobre Docker, porem, não vou focar na parte teórica, estou planejando lançar um curso, onde irei mencionar os detalhes mais profundamente.
Aqui vamos entender através de uma abordagem mais prática, e principalmente porque usar Docker e quais vantagens.
Cenário
Você trabalha em uma software house, onde cria e mantem N projetos, em várias linguagens e várias versões de cada linguagem.
Temos um problema ai, nem todas dependências de um projeto trabalham bem quando você tem várias versões, sem contar o que foi deprecado, e você tem que rodar em sua maquina aquele projeto de 2010, onde a versão mais atual removeu a feature x, y e z.
O Docker pode nos ajudar nisso. Neste caso você apenas vai manter o Docker instalado em seu computador de desenvolvimento, e pedir para que ele rode em uma área isolada o recurso x, na versão y, e depois de acabar aquele debug, ou adicionar uma nova feature no seu sistema, você
simplesmente encerra essa área isolada que o Docker criou pra você.
Essa “área isolada” chamamos de container, podemos ter N containers rodando em nossa maquina, cada um
com sua dependência, na sua versão, ou seja, podemos ter um container rodando Node com a versão 13.1.0, para rodar o projeto X, e simutaneamente, é necessário rodar um projeto que fornece uma api que foi desenvolvido em Net Core na versão 2.0.
Percebe que neste caso, já teríamos que ter instalado Node e Net Core em versões especificas?
Isso é um problema?
A principio não, porem existe outro fator que pode fazer pensar duas vezes em manter o projeto em sua maquina: A configuração de ambiente.
Ja passou por um cenário onde você tinha uma configuração X no servidor de produção (estou me referindo sobre um caminho de rede compartilhada, um serviço que roda periodicalmente, um servidor de arquivos na rede, etc), e na sua
maquina o ambiente não tem nada disso configurado?
Ou você se vê parando seu trabalho para configurar todo o ambiente em uma maquina nova, porque entrou um novo dev.
Isso vai impactar bastante em sua produtividade, sem contar que talvez falte algo, que não seja possível configurar em sua rede local.
Com o Docker podemos configurar o ambiente de desenvolvimento idêntico ou muito próximo do ambiente de produção.
Mesmo que seja para apenas subir aquele serviço que “não faz nada” em seu ambiente de desenvolvimento.
E todo esse cenário que citei, se trata do ambiente de desenvolvimento apenas, quando levamos o Docker para produção
temos uma serie de recursos disponíveis para escalar nossa aplicação.
Vamos enumerar as vantagens que citei a cima
Configuração rápida e Produtividade
Como mencionei anteriormente, é muito rápido você configurar um ambiente, com a versão necessária para seu projeto funcionar, seja C#, Java, Node, PHP, Ruby, etc.
Isolamento
Cada container em execução no Docker, é isolado, os recursos são somente para ele. É possível que eles se comuniquem.
Eu vi um cenário que um banco de grande porte, tinha hospedado seu site institucional no domínio xpto.com.br. E o profissional
de SEO por sua vez solicitou precisava subir um blog wordpress e para otimizar seria melhor que o blog estivesse disponivel
através da seguinte url: banco-xpto.com.br/blog em vez de um sub domínio. Tem 2 pontos aqui, primeiro o site institucional não
estava hospedado em um servidor apache, o segundo ponto, é que mesmo estivesse eu não acho uma boa ideia por se tratar de um projeto open source, que outros desvantagens
podem explorar vulnerabilidade (quero deixar claro que sou a favor de open source, só estou evidenciando os cuidados que devemos ter), e devemos manter a versão estável, claro
que isso ocorre mesmo fora do contexto do Docker. O Docker atua como facilitador para isolar uma aplicação neste caso.
Compatibilidade
A frase “mas na minha maquina funciona”, eu não digo que isso deixa de ser verdade, mas você fica isento ou muito próximo disso (vai depender de você).
Ocorre que trabalhando com docker no definimos como o container deve se comportar independente de onde esteja rodando, seja na sua maquina ou no servidor de produção.
Fazemos isso através das imagens criadas no docker, podendo usar as disponíveis pelo Docker ou personalizando as nossas com base nas já criadas pela Docker.
Recursos
Um container é muito mais leve que uma maquina virtual, afinal ele só carrega o que você solicita, ou seja, ele não vai
subir um sistema operacional inteiro por container, isso através uma vantagem em relação aos recursos da maquina, seja no ambiente de desenvolvimento ou produção.
Agora imagine como isso pode te ajudar com ambiente que tem múltiplos micro services.
É possível reduzir a sobrecarga de desempenho e implantar milhares de micro services no mesmo servidor.
Deploy continuo
O Docker garante ambientes consistentes, do desenvolvimento à produção. Os contêineres do Docker são configurados para manter internamente todas as configurações e dependências;
Você pode usar o mesmo contêiner desde o desenvolvimento até a produção, certificando-se de que não há discrepâncias ou intervenção manual.
Imagine você subir manualmente todos as dependências dos projetos, isso é possível, mas temos um porem, se imagine subindo um ambiente
onde um depende de outro, e nesse meio tempo algum recursos será “quebrado”, porque a versão ainda não está disponível no outro ambiente, no deploy podemos manter isso estável.
O Docker pode ser executado no Linux, Windows e MacOS.
No caso do Windows é necessário ter Windows 10 Pro como requisito minimo e ter Hyper-V habilitado.
Já escrevi muito antes de mostrar como executar o Docker. não vou abordar como instalar o Docker, vou partir do principio que você tem
ele instalado (se não ficará muito extenso).
Instalação para Linux: https://docs.docker.com/install/linux/docker-ce/ubuntu/
Instalação para Windows: https://docs.docker.com/docker-for-windows/
Instalação para MacOS: https://docs.docker.com/docker-for-mac/
Com o Docker instalado e rodando em seu computador, temos condição de criar nossos containers através do terminal
do seu S.O.
Praticando
Vamos começar com algo essencial: Listar nossos container em execução:
docker container ps
Executando este comando você verá uma lista de containers que estão em execução, no caso não temos nenhum
Como podemos ver na imagem, é mostrado apenas algumas colunas:
CONTAINER ID: Identificador unico do seu container
IMAGE: Image que seu container depende para funcionar, seja node, net core, java, php, etc.
COMMAND: Comando executado no container para execução
COMMAND: Data e hora da criação do container
STATUS: Status do container
PORTS: Portas disponíveis para acesso ao container.
NAMES: Nome atribuído ao container, você vai perceber que caso você não dê um nome ao seu container, um nome aleatório sera gerado.
Vamos criar nosso primeiro container, usando node (poderia ser outra imagem), como podemos ver as images disponiveis no Docker hub.
docker run node
(Sim, ele cria o container rodando ele)
Ao executar este comando basicamente vai acontecer algumas coisas:
* O Docker vai entender que você quer um novo container para executar com node (este é o nome da imagem que estamos solicitando) com nenhuma versão
especificada. A versão é especificada depois do nome da imagem, caso não seja especificada (como nosso caso), ele irá buscar a última versão.
Para determinar uma versão deveria ser da seguinte forma: node:13.1.0, também podemos determinar explicitamente que será a ultima versão: node:latest
* Em seguida, o Docker verifica se você ja utilizou a imagem do node nesta versão anteriormente, caso não, ele irá baixar a imagem para sua maquina, ou seja, uma segunda
execução de um container com esta imagem, será mais rápida, ja que não sera necessário baixar novamente.
* Após a execução o container será interrompido, já que não especificamos que queríamos inteirar com ele. Se você digitar o comando para listar
os containers novamente, verá que continua na mesma, sem nenhum container em execução. Para ver o container que foi executado, podemos incluir a flag “-a”, isso
identificará que queremos listar os containers parados também:
docker container ps -a
Podemos ver que as definições do container que foi executado. Mas isso nos adianta muito, certo?
Podemos ver todas as imagens que baixamos através do comando:
docker images
O container parado não nos resolve muita coisa, vamos interagir com o container adicionando uma flag ao comando “-it”. Para informar que a execução
deve ser no modo “interactive” e “tty”
docker run -it node
(Repare que um segundo container será criado, neste caso).
Perceba que agora temos a possibilidade de executar comando dentro do container com node instalado.
Se você abrir outro terminal e listar os containers vai perceber que agora você tem um container em execução.
Isso só nos serve para testar algo dentro do container, já dessa forma como criamos não é possível se comunicar com o container, ja que
não temos portas de rede disponível ou alguma pasta compartilhada entre seu computador (chamado de host) e o container.
Vamos ver como compartilhar um pasta entre seu host e o container. Fazemos isso atraves da flag “-v caminho do host:caminho do container”, como segue:
docker run -it -v /var:/var/www node
No comando acima, criamos um container usando a imagem do node na última versão, no modo iterativo, compartilhando
a pasta “var” do nosso host com a pasta “var/www” do container. Na prática usamos este recurso para
compartilhar dados, deixar o projeto disponível no container enquanto desenvolvemos.
Esse “compartilhamento de pasta” é chamado de “volume” no docker.
Um execução de container pode ter N volumes, ou seja, podemos chamar o comando para rodar um container informando a flag “-v host:container” N vezes.
Também podemos nos comunicar com o container através das portas de rede, para isso devemos invocar o comando com a flag “-p porta do seu host:porta do seu container”.
Claro que tem que fazer sentido, quando acessar o container com nosso x.x.x.:porta, algo responda dentro do container com a solicitação do request, seja um serve ouvindo a porta, algum protocolo
disponivel, etc. Como uma aplicação angular como explico neste post como criar usando container: Projeto angular com docker
docker run -it -p 80:65100 node
Este comando irá criar um container que ao receber um request na porta 80 irá redirecionar para 65100 dentro do container.
Se você abrir o navegador agora sem uma aplicação não fará muito sentido agora, porque não funcionara, então continue lendo, que você
será capaz de executar os passos deste outro post: Projeto angular com docker
Até o momento vimos como iniciar o container em modo iterativo com a flag “-it”. Nem sempre queremos manter o terminal para
interagir com ele, podemos deixar o container executando e obter acesso a ele só quando necessário. Para isso
vamos iniciar o container “desatachado”, então vamos trocar “-it” por “-d”, assim o container ficará em execução, e não ficaremos
com o terminal preso:
docker run -d node
Bom, isso é só o começo, e parece um pouco verboso, o que podemos fazer para minimizar este processo?
Podemos trabalhar com arquivos dentro do projeto, para que não seja necessário ficar interagindo a todo momento
com container.
Dockerfile
O Dockerfile é um arquivo que fica no projeto para definir uma “receita” de como queremos que nosso container funcione, ou seja, criamos nossa
definição através de um arquivo. O Dockerfile fica sem extensão, porem ele não precisa ter esse nome necessariamente, mas por convenção é normal manter assim.
Normalmente se coloca este arquivo na raiz do seu projeto com as definições de imagem que ele usará, qual pasta ficará disponivel dentro do container, se irá expor uma porta
dentro do container, entre outras configurações disponiveis. Vou pegar como exemplo o Dockerfile do outro projeto:
FROM node
WORKDIR /app
COPY package.json ./
RUN npm install -g @angular/cli@8.3.19
RUN npm install
COPY . .
EXPOSE 4200
CMD npm run start
Na primeira linha definimos que o container deve usar a imagem do node, na última versão.
Sem seguida, definimos que a pasta onde o container irá trabalhar é a pasta app, neste caso, ele cria (se necessário) e define como
padrão.
Depois o arquivo package.json é copiado para pasta definida no WORKDIR.
É instalado o angular CLI dentro do container
Rodamos o comando para instalar todas as dependencias do projeto no container.
Copiamos todo o projeto para pasta /app dentro do container.
Expomos a porta 4200, dentro do container (e não fora).
e por último rodamos o comando start do projeto.
Percebe que esses passos vai depender do que seu projeto precisa?
Você pode criar um pasta, com permissão de somente leitura, etc, ou seja, seu requisito de infra estrutura.
Criando somente o arquivo Dockerfile, podemos criar nossa imagem e depois rodar um container.
Para isso, navegue no terminal até sua pasta de projeto, e execute o comando para criar uma imagem docker:
docker build -t docker-imagem-angular-dev .
Com este comando criamos uma imagem chamada docker-imagem-angular-dev, com base nas configurações do Dockerfile, que no
caso deste comando se refere por “.”, ou seja, na pasta que você esta atualmente no terminal.
Após criar a imagem, podemos ver que a imagem foi criada através do comando:
docker images
E agora podemos criar um container com base na nossa imagem:
docker run -d -p 8081:4200 docker-imagem-angular-dev
Como já vimos, no comando acima estamos criando um container com base em docker-imagem-angular-dev, que por
sua vez é uma personalização de uma imagem node, já que nosso Dockerfile informa que o container deve trabalhar com node
Ainda podemos deixar um pouco mais fácil para subir nosso ambiente, através do docker compose. Com este
arquivo é possível configurar algumas diretrizes para nosso ambiente. Ele trabalha com a syntax YAML. Lembrando
que é importa manter a tabulação para que tudo funcione com este tipo de arquivo
Vamos criar um arquivo chamado docker-compse.yml na raiz do projeto com o seguinte conteúdo:
Primeiro definimos a versão do compose, em seguida declaramos os serviços, neste caso apenas um serviço
que nomeamos como: dev-app-angular. Definimos a imagem que deve ser utilizada neste serviço (através da propriedade “build”)
deve ser local, ou seja, utilizada na definição do Dockerfile. O atributo “ports” define uma porta de entrada, neste caso
sua maquina deve acessar o container através da porta 8089 (poderia ser 4200), e redirecionará para porta 4200
do container, que por sua vez responde a aplicação pendurada no serve do angular.
Também temos que definir os volumes que devem ser mapeados, neste caso estamos mapeando a pasta
de dependências e a pasta de WORKDIR do container.
Agora podemos subir nosso ambiente, onde após o comando ele se encarregará de criar a imagem, e subir o
container:
docker-compose up -d
Espero ter ajudado!
Até a próxima!