Construindo uma aplicação com banco de dados utilizando docker compose
Construindo uma aplicação com banco de dados utilizando docker compose
Parte I: O banco de dados
Para esse exemplo vamos subir uma imagem de um banco de dados e uma aplicação com node, sendo assim vamos partir de um arquivo docker-compose.yaml de base
version: "3"
services:
networks:
minharede:
driver: bridge
A primeira coisa que vamos fazer é criar um container para a base de dados MySQL
version: "3"
services:
database:
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
networks:
- minharede
networks:
minharede:
driver: bridge
volumes:
mysql:
Antes de seguir vamos conhecer as opções novas:
command: --innodb-use-native-aio=0
- Equivalente ao CMD nesse caso seta esse parâmetro na execução do MySQL
- O InnoDB é um mecanismo de armazenamento utilizado no MySQL que utiliza o subsistema de I/O assíncrono (AIO nativo) no Linux para executar operações de leitura e gravação em páginas de arquivos de dados. Essa funcionalidade é ativada por padrão na opção de configuração innodb_use_native_aio e é aplicável apenas a sistemas Linux. Em sistemas Unix diferentes do Linux, o InnoDB utiliza apenas I/O síncrona. Anteriormente, o InnoDB utilizava apenas I/O assíncrona em sistemas Windows. No entanto, para usar o subsistema de I/O assíncrono no Linux, é necessário ter a biblioteca libaio instalada. Para saber mais a respeito visite esse link.
restart: always
- caso algo de errado nosso container vai ficar reiniciando sozinho até que ele consiga estar funcional, isso evita que o container morra em caso de erro.
tty: true
- caso seja preciso acessar o shell do sistema operacional desse container vamos habilitar o tty.
volumes:
- ./mysql:/var/lib/mysql
- Aqui estamos criando um volume, onde dizemos que tudo que for gravado no diretório
/var/lib/mysql
do container vai ser gravado no diretóriomysql
que está na nossa máquina. Isso serve para que ao desligar o container os dados gravados no mysql não sejam perdidos.
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
Usamos a opção environment
para passar variáveis de ambiente para a execução do container, nesse caso estamos definindo o nome da base de dados, a senha do usuário root, e um usuário para acessar a base de dados.
PRO TIP:
No lugar de utilizar environment
você pode utilizar env_file
e armazenar suas variáveis de ambiente em um arquivo .env que não deverá ser comitado no git.
Uma das vantagens dessa abordagem é que você não expões nomes de usuários e senhas, a desvantagem é que cada vez que alguém precisar subir esse container vai ter de saber criar o arquivo .env e suas variáveis.
env_file:
- .env
Agora podemos executar
docker-compose up -d
E teremos um servidor MySQL sendo executado.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
43ce1b233080 mysql:5.7 "docker-entrypoint.s…" 14 minutes ago Up 14 minutes 3306/tcp, 33060/tcp database
Podemos também visualizar os logs do container
docker logs database
2023-07-11 04:05:23+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.42-1.el7 started.
2023-07-11 04:05:23+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2023-07-11 04:05:23+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.42-1.el7 started.
2023-07-11 04:05:23+00:00 [Warn] [Entrypoint]: MYSQL_USER specified, but missing MYSQL_PASSWORD; MYSQL_USER will not be created
2023-07-11 04:05:23+00:00 [Note] [Entrypoint]: Initializing database files
...
Parte II: Aplicação node
Pra começar vamos criar um diretório chamado nodeapp e dentro desse diretório um arquivo chamado Dockerfile, nossa estrutura deve ficar mais ou menos assim:
.
├── docker-compose.yml
├── mysql
└── nodeapp
└── Dockerfile
O Dockerfile deve ter o seguinte conteúdo:
FROM node:18.16.1
WORKDIR /usr/src/app
EXPOSE 3000
Agora entre no diretório nodeapp
cd nodeapp
E crie uma aplicação chamada nodeapp
npm init
Veja que após o preenchimento dos dados foi criado um arquivo chamado package.json
Faça a instalação do express
npm install express --save
Crie um arquivo chamado index.js com o seguinte conteúdo:
const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("<h1>It Works !</h1>");
});
app.listen(port, () => {
console.log("Server running on port " + port);
});
Retornando ao docker-compose.yaml veja que ele agora tem o seguinte conteúdo:
version: "3"
services:
database:
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
env_file:
- .env
networks:
- minharede
networks:
minharede:
driver: bridge
volumes:
mysql:
Vamos adicionar nossa aplicação node
version: "3"
services:
app:
build:
context: ./nodeapp
networks:
- minharede
volumes:
- ./nodeapp:/usr/src/app
tty: true
ports:
- "3000:3000"
container_name: nodeapp
database:
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
networks:
- minharede
networks:
minharede:
driver: bridge
volumes:
mysql:
Uma vez feito isso, agora podemos iniciar o container:
docker-compose up -d --build
E pronto, temos nossos containers sendo executados.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bcc7c7d85ae0 exemplo_node_mysql_app "docker-entrypoint.s…" About a minute ago Up About a minute 3000/tcp nodeapp
43ce1b233080 mysql:5.7 "docker-entrypoint.s…" 33 minutes ago Up 33 minutes 3306/tcp, 33060/tcp database
Porém ainda não estamos conectados ao banco de dados MySQL, para isso vamos ter de editar nosso programa que está no arquivo index.js, sendo assim altere esse arquivo da seguinte forma:
Para isso a primeira coisa que vamos fazer é acessar o container da base de dados:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
551a29f83748 exemplo_node_mysql_app "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp nodeapp
24b1ec7ea978 mysql:5.7 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 3306/tcp, 33060/tcp database
Para isso vamos executar:
docker exec -it database bash
bash-4.2#
Uma vez dentro do container vamos acessar o shell do MySQL com o mysql-client
bash-4.2# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.42 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
Uma vez dentro do MySQL vamos ver quais databases ele tem
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| appdb |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql>
Veja que foi criada uma database com o nome de appdb
conforme especificado nas variáveis de ambiente do docker-compose.yml.
Agora vamos utilizar a database appdb para criar uma tabela para a nossa aplicação:
Primeiro selecione a database
mysql> use appdb;
Database changed
Agora crie a tabela
mysql> create table todo(id int not null auto_increment, title varchar(255), primary key(id));
Query OK, 0 rows affected (0.01 sec)
Podemos descrever a estrutura da nossa tabela com o comando desc
mysql> desc todo;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql>
ATENÇÃO: O código mostrado a seguir é apenas um exemplo, para demonstrar o funcionamento de uma aplicação e uma base de dados se comunicando, em uma aplicação real, que iria para produção jamais teríamos a camada de apresentação e dados misturadas e muito menos iríamos expor os dados da conexão com o banco de dados no código fonte blz ?
Vamos fazer com que a nossa aplicação node crie um registro nessa tabela. Primeiramente vamos acessar o container que tem a aplicação nodejs
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
551a29f83748 exemplo_node_mysql_app "docker-entrypoint.s…" 13 minutes ago Up 13 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp nodeapp
24b1ec7ea978 mysql:5.7 "docker-entrypoint.s…" 16 minutes ago Up 16 minutes 3306/tcp, 33060/tcp database
docker exec -it nodeapp bash
root@551a29f83748:/usr/src/app#
Vamos instalar o cliente mysql
root@551a29f83748:/usr/src/app# npm install mysql --save
Agora vamos alterar a nossa aplicação no arquivo index.js para que ela se conecte ao MySQL e cadastre um dado no banco de dados:
const express = require("express");
const app = express();
const port = 3000;
const database_configuration = {
host: "database",
user: "root",
password: "dbpasswd",
database: "appdb",
};
const mysql = require("mysql");
const connection = mysql.createConnection(database_configuration);
const sql = `INSERT INTO todo(title) VALUES ('Estudar')`;
connection.query(sql);
connection.end();
app.get("/", (req, res) => {
res.send("<h1>It Works !</h1>");
});
app.listen(port, () => {
console.log("Server running on port " + port);
});
Agora volte ao shell do container da aplicação node e execute a aplicação
root@551a29f83748:/usr/src/app# node index.js
Server running on port 3000
E verifique no container do MySQL se ocorreu o registro do dado
mysql> select * from todo;
+----+---------+
| id | title |
+----+---------+
| 1 | Estudar |
+----+---------+
1 row in set (0.00 sec)
mysql>
Tratando a dependência entre containers
Nesse nosso exemplo temos 2 containers onde um depende do outro, então queremos garantir que a aplicação se conecte ao banco de dados. Para fazer isso podemos contar com algumas opções.
A primeira é utilizar no nosso docker-compose a opção depends on, essa opção fará com que a ordem de execução dos containers seja alterada, então vamos editar o docker compose, deixando da seguinte forma:
version: "3"
services:
app:
build:
context: ./nodeapp
networks:
- minharede
volumes:
- ./nodeapp:/usr/src/app
tty: true
ports:
- "3000:3000"
container_name: nodeapp
depends_on:
- database
database:
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
networks:
- minharede
networks:
minharede:
driver: bridge
volumes:
mysql:
Agora garantimos que o container database vai subir primeiro. Porém o container da aplicação não vai ficar esperando o MySQL (banco de dados estar pronto), para essa finalidade temos algumas ferramentas, nesse exemplo vamos utilizar o dockerize.
Sendo assim conforme manda a documentação, vamos editar o nosso Dockerfile para instalar na imagem do node o dockerize
FROM node:18.16.1
ENV DOCKERIZE_VERSION v0.7.0
RUN apt-get update \
&& apt-get install -y wget \
&& wget -O - https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz | tar xzf - -C /usr/local/bin \
&& apt-get autoremove -yqq --purge wget && rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
EXPOSE 3000
Após editar o Dockerfile vamos buildar novamente a imagem
Então vamos parar os nosso containers
docker rm -f $(docker ps -qa)
Buildar novamente
docker-compose up -d --build
Por fim vamos alterar o entry point da nossa aplicação node, fazendo com que o dockerize seja executado, para isso altere o docker-compose da seguinte forma adicionando o entrypoint
version: '3'
services:
app:
build:
context: ./nodeapp
networks:
- minharede
volumes:
- ./nodeapp:/usr/src/app
tty: true
ports:
- "3000:3000"
container_name: nodeapp
entrypoint: dockerize -wait tcp://database:3306 -timeout 20s docker-entrypoint.sh
depends_on:
- database
database:
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=appdb
- MYSQL_ROOT_PASSWORD=dbpasswd
- MYSQL_USER=dbuser
networks:
- minharede
networks:
minharede:
driver: bridge
volumes:
mysql:
Nesse caso estamos dizendo que quando esse container for executado o dockerize vai aguardar por 20 segundos uma conexão com o mysql.
Faça um novo build
docker-compose up -d --build
E repare que o container nodeapp subiu depois do mysql (database) e se inspecionarmos os logs
docker logs nodeapp
2023/07/12 07:34:40 Waiting for: tcp://database:3306
2023/07/12 07:34:40 Connected to tcp://database:3306
Welcome to Node.js v18.16.1.
Type ".help" for more information.
Veja que o container ficou aguardando (retentando) a conexão com a base de dados.