Ambiente de desenvolvimento com Docker e Visual Studio Code

Ambiente de desenvolvimento com Docker e Visual Studio Code

No final de dezembro resolvi criar uma aplicação web utilizando Node.js para validar se os meus conhecimentos de Aria Automation e Orchestrator seriam suficientes para fazer algo além de scripts. No final das contas entendi que é um pouco mais complexo do que imaginei, mas funcionou. Após iniciar o desenvolvimento, percebi que não estava feliz com o Node.js e MySQL instalados em uma VM, visto que não gosto da ideia de instalar diretamente na máquina física, mas gosto de trabalhar com os arquivos localmente (sim haha). Por este motivo, busquei alternativas para resolver o problema que eu mesmo criei. O que vou compartilhar aqui é a forma que melhor funcionou para mim, onde basicamente utilizo containers do Docker integrados ao Visual Studio Code.

O processo é relativamente simples:

  • Imagens
    • Vamos utilizar o Dockerfile com a imagem oficial do MySQL servindo como banco de dados e a imagem oficial do Ubuntu para o Node.js
  • Serviço
    • Com o uso do Docker Compose iremos configurar a forma como os containers irão trabalhar
  • Execução & testes
    • Só criar os containers, testar se tudo está funcionando e correr pro abraço
  • Integração com Visual Studio Code
    • Pronto, shoooow me the code!

É esperado que o Docker já esteja instalado no seu computador e que você tenha um conhecimento básico em como o Docker e containers em geral funcionam. A minha estação de trabalho é MacOS, mas poderia ser Windows ou Linux, os comandos são os mesmos.

Imagens

Node.js

Vamos começar preparando as imagens. Organização é tudo, então visando facilitar a execução vamos criar algumas pastas. Será uma pasta por imagem, vamos aos detalhes:

  • nodejs-conza
    • Dockerfile
    • .dockerignore
    • app.js
    • package.json
    • .env
    • node_modules
    • … e outros arquivos do projeto
  • mysql-conza
    • Dockerfile
    • init.sql

Agora que as pastas e arquivos estão criados, vamos colocar um pouco de código dentro destes arquivos. A primeira imagem será o Ubuntu. O Dockerfile irá criar uma imagem baseado no ubuntu:latest, instalará o Node.js, npm, nodemon e dependências do projeto (baseado no package.json) e por fim, realiza a cópia de todo o conteúdo da pasta (basicamente seu projeto Node.js inteiro para dentro do container).

Wait, copia tuuuuudo? não exatamente. O arquivo .dockerignore serve para ignorar alguns arquivos, ou seja, tudo o que estiver dentro deste arquivo será ignorado pelo Docker durante o processo de cópia (mesma lógica do .gitignore). Neste caso, queremos ignorar a cópia do Dockerfile em si, a pasta dos módulos do Node.js e .DS_Store. Ficaria mais ou menos assim:

node_modules
Dockerfile
.DS_Store

O Dockerfile fica assim:

# imagem docker
FROM ubuntu:latest

# instalar os pacotes necessarios
RUN apt-get update && \
    apt-get install -y curl sudo && \
    curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - && \
    sudo apt-get install -y nodejs && \
    npm install -g npm@10.4.0 && \
    npm install -g nodemon

# cria e define o diretorio de trabalho
WORKDIR /conza/world

# instala as dependencias do projeto
COPY package*.json ./
RUN npm install

# copia o projeto inteiro para o container
COPY / ./

# escuta a porta 3000
EXPOSE 3000

# roda o comando
CMD ["nodemon", "app.js"]

Tem um ponto importante sobre a conexão entre a aplicação e o banco de dados e isso pode variar bastante, mas aqui estou usando um arquivo .env na raiz do projeto Node.js, então em DB_HOST eu simplesmente informei o nome do container e as credenciais (vamos configurar estas informações na criação da imagem do MySQL). Não se preocupe, o Docker se vira pra resolver este nome. Exemplo do meu arquivo .env.

# Environment
NODE_ENV=development
PORT=3000

# Database
DB_HOST=world-database
DB_NAME=ovo
DB_USER=ovo_user
DB_PASS=MyS3cur3Huev!to

Pronto, agora basta executar o comando docker build -t nome-da-imagem:tag . . No meu caso ficou docker build -t nodejs-conza:latest . (perceba que tem um PONTO no final do comando). Se tudo ocorreu bem, então você deve ver a imagem com o comando docker images.

Se quiser testar, basta criar um container com o comando docker run -p 3000:3000 -d nodejs-conza:latest e abrir localhost:3000 (pode ser via navegador ou curl -v localhost:3000).

É fundamental garantir que a imagem esteja funcionando conforme esperado. Se este for o caso, então vamos seguir para a criação da imagem do MySQL.

MySQL

O processo muda um pouquinho, mas a lógica é exatamente igual. Aqui precisaremos do Dockerfile e do init.sql. O Dockerfile irá criar uma imagem baseado no mysql:latest, configura a senha de root do MySQL, fica ouvindo na porta 3306, permite conexões externas (a partir do container do Node.js) e por fim copia o init.sql para docker-entrypoint-initdb.d. Calma, vamos por partes.

O Dockerfile ficaria assim:

# imagem docker
FROM mysql:latest

# variaveis do ambiente
ENV MYSQL_ROOT_PASSWORD=MyS3cur3P@ss

# expoem a porta
EXPOSE 3306

# permite conexões externas
RUN echo "[mysqld]" > /etc/mysql/my.cnf && \
    echo "bind-address = 0.0.0.0" >> /etc/mysql/my.cnf

# configura banco, tabelas, usuario, etc
COPY init.sql /docker-entrypoint-initdb.d/

# inicia o MySQL
CMD ["mysqld"]

A cereja do bolo fica por conta do init.sql, este arquivo pode criar o banco de dados, tabelas, usuários, enfim, tudo o que você faria diretamente no MySQL pode ser feito com nele. Claro, isso vai mudar para cada projeto, mas ficaria algo mais ou menos assim:

-- cria um banco de dados chamado ovo e seleciona ele
CREATE DATABASE IF NOT EXISTS ovo;
USE ovo;

-- cria uma tabela chamada users no banco de dados ovo
CREATE TABLE IF NOT EXISTS users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    user_name VARCHAR(100),
    user_user VARCHAR(50),
    user_pass VARCHAR(255),
    user_type INT
);

-- cria o usuário ovo_user (perceba o '%')
CREATE USER 'ovo_user'@'%' IDENTIFIED BY 'MyS3cur3Huev!to';

-- permite o usuário ovo_user no banco de dados ovo
GRANT ALL PRIVILEGES ON ovo.* TO 'ovo_user'@'%' WITH GRANT OPTION;

-- aplica tudo
FLUSH PRIVILEGES;

É só isso, agora basta executar o comando para criar a imagem e ver a mágica acontecer. No meu caso, ficaria assim docker build -t mysql-conza . 🙂

Aquele teste básico para validar se a imagem está funcionando: docker run -p 3306:3306 -d mysql-conza:latest. Se estiver tudo certo, então pode remover os dois containers de teste e vamos partir para a criação do serviço.

Serviço

Para o Docker, serviços são um conjunto de containers executados juntos, como uma aplicação e banco de dados, por exemplo. É exatamente isso que vamos criar agora. Quem irá nos ajudar com esta tarefa é o Docker Compose. Precisamos basicamente criar um arquivo chamado docker-compose.yml usando este código:

version: '3'

# configurações do serviço de banco e dados
services:
  world-database:
    # imagem que acabamos de criar do MySQL
    image: mysql-conza:latest
    container_name: world-database
    ports:
      - '3306:3306'
    # volume para persistência dos dados (se não existir o Docker cria)
    volumes:
      - world-mysql-data:/var/lib/mysql

# configurações do serviço da aplicação
  world-app:
    # imagem que acabamos de criar do Node.js
    image: nodejs-conza:latest
    container_name: world-app
    ports:
      - '3000:3000'
    # volume para persistência dos dados (se não existir o Docker cria)
    volumes:
      - world-nodejs-data:/conza/world
    # a aplicação depende do banco de dados
    depends_on:
      - world-database

# rede do Docker (se não existir o Docker cria)
networks:
  conza-net:
    driver: bridge

volumes:
  world-mysql-data:
    driver: local
    name: world-mysql-data
  world-nodejs-data:
    driver: local
    name: world-nodejs-data

Estamos dando algumas instruções para o Docker saber como configurar o serviço que queremos, neste caso, pedimos para o Docker criar um serviço com dois containers com configurações bem especificas para cada um (imagem que acabamos de criar, porta, com volume externo para persitência dos dados e com uma rede do Docker). Não se preocupe em criar a rede e volumes previamente, se não existir, o Docker cria isso pra você, basta definir o nome que você deseja no Docker Compose.

Execução & testes

Se você chegou até aqui eu tenho uma excelente noticia: o pior já passou. Agora é só executar o comando para subir o serviço e ver a mágica acontecer. Para isso,  manda bala no docker compose up.

Configurei o winston para os logs da minha aplicação, com isso é possível observar que a conexão com o banco de dados funcionou perfeitamente.

E por fim, basta chamar no localhost:3000 que já vejo o World rodando lindão.

Visual Studio Code

Eu gosto da ideia de desenvolver localmente, me parece otimizar bastante os processos, além de evitar depedências externas. Ah, quando escrevo “localmente” entenda diretamente na minha estação de trabalho e não em uma máquina virtual ou servidor remoto. Por este motivo, mesmo que o código está em um container e o banco em outro, ainda estão na minha máquina local e isso é bem diferente de estar em uma máquina virtual. O que precisamos aqui é apenas uma extensão do Visual Studio Code chamada Dev Containers. Essa maravilha consegue identificar os containers que estão em execução no Docker e abrir o código como se estivesse local, com isso, é possível usar todos os recursos que o Visual Studio Code oferece, incluindo, por exemplo, a sincronização do código com o Git, entendeu onde quero chegar, né?!

Vamos lá. Após instalar a extensão Dev Containers, navegue até a opção Remote Explorer. Você verá os seus containers, então basta selecionar o container da aplicação.

Na próxima tela, o Visual Studio irá permitir explorar os arquivos do container para abrir a pasta do seu projeto. No meu caso, configurei para ficar dentro de /conza/world. Então é só apontar para este diretório e voilà!

Prontoooooo!

Resultados

  • Chega de instalar milhares de coisas na sua máquina local
  • Aplicação Node.js e banco de dados MySQL rodando em containers diferentes
  • Containers com volumes externos, ou seja, se você deletar os containers e recria-los com o arquivo do Docker Compose, as informações do seu banco de dados e o seu código estarão lá
  • Não depende de um sistema operacional especifico (roda em MacOS, Linux ou Windows)
  • Escalabilidade – novas funcionalidades podem ser novos containers
  • Com o Visual Studio Code e a extensão a experiência é sensacional
  • Último e mais importante: it’s so cool! Agora você pode falar que usa containers HAHA

E você, é do time que instala tudo local ou tem outra estratégia. Compartilhe que estou curioso para saber. Abraço e até a próxima!

 

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *