Fazer uso de containers é uma excelente abordagem para automatizar o funcionamento e a distribuição de projetos. Entretanto, se você está criando uma aplicação ASP.NET Core com Entity Framework Core, você provavelmente utilizou migrations para construir o banco de dados.
Agora, como poderíamos automatizar a inicialização dessa aplicação? Através de do Startup do SQL Server.
O SQL Server possui uma série de aplicativos funcionais em seu core. Um deles se chama sqlcmd
. Esse executável permite com que você possa realizar comandos instantâneos no SQL Server sem necessariamente conectar a sua aplicação ou um gerenciador de banco de dados e está disponível para uso na imagem Docker do SQL Server.
⚠️ Atenção: Se você está utilizando um computador com processador ARM, provavelmente você não utilizou a imagem do SQL Server mas sim a imagem do Azure SQL Edge. Lembre-se que a imagem do Azure SQL não possui o executável sqlcmd
e por isso, não funcionará.
Para mostrar a automatização do processo, utilizei uma API simples utilizando .NET 6 e EF Core chamada ToDo.API.
1º Passo
Iremos criar um arquivo docker-compose.yml com a imagem do SQL Server e garantir que exista um volume nela. Este volume foi feito com o bind /data
-> /initdb
e servirá para colocarmos um script SQL capaz de construir o banco de dados.
version: '3'
services:
db:
image: mcr.microsoft.com/mssql/server:2019-latest
volumes:
- ./data:/initdb
container_name: todo-database
ports:
- 1433:1433
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=TodoApiSqlPass123!
- MSSQL_PID=Developer
2º Passo
Agora, iremos criar as migrations para a nossa aplicação. Será muito importante garantir que exista apenas uma única migrations capaz de construir o banco de dados inteiro. Podemos fazer isso com o seguinte comando dentro do diretório da API:
dotnet ef migrations add initialcreate
3º Passo
O próximo passo será criar um script com o código dessa migration. Como ela é a única, não precisamos nos preocupar em indicar qual migration criar o script. Iremos salvar esse script dentro do diretório /data
, para onde apontamos o volume:
dotnet ef migrations script -o ../data/initial.sql
4º Passo
Agora iremos editar o arquivo /data/initial.sql
para indicar o banco que estamos trabalhando. Nesta API, configurei a API para se conectar a um banco de dados chamado Todo
. Portanto, no início do arquivo initial.sql
, iremos colocar as seguintes instruções:
CREATE DATABASE Todo;
GO
USE Todo;
GO
No final desse arquivo, você pode criar instruções de INSERT
para popular o seu banco de dados como um seeder
.
5º Passo
Podemos então criar um Dockerfile
para criar uma imagem da API. Dentro do diretório da API, criaremos o seguinte:
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "ToDo.Api.dll"]
Esse é o modelo de Dockerfile mais simples para aplicações ASP.NET
e você pode construir o mesmo da maneira desejada.
6º Passo
Agora podemos finalmente adicionar o serviço da API dentro do docker-compose.yml
como um serviço dependente do banco de dados:
api:
container_name: todo-api
build: ./ToDo.Api
ports:
- 5501:5501
depends_on:
- db
7º Passo
Nosso último passo e mais importante. Popular o banco de dados. Para isso, iremos criar um shell script. Mas vamos entender como funciona o processo.
Dentro do container do banco de dados, temos o executável sqlcmd
como mencionado anteriormente, localizado em /opt/mssql-tools/bin/sqlcmd
.
Este executável pode ser chamado com os seguintes parâmetros:
sqlcmd -S server -U usuario -P senha -d banco -i script.sql
Onde:
-S: corresponde ao servidor de conexão com o SQL Server;
-U: corresponde ao usuário;
-P: corresponde à senha;
-d: corresponde ao banco onde será feita a conexão;
-i: corresponde ao script SQL que será executado.
Para a nossa aplicação, podemos chamar então o sqlcmd
com um docker exec
da seguinte maneira:
docker exec todo-database /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P TodoApiSqlPass123! -d master -i initdb/initial.sql
Nesse comando, executamos dentro do container todo-database
, nomeado no arquivo docker-compose.yml
passando o server, usuário e senha também configurados no mesmo. Para o banco de dados, selecionamos master
, pois é o banco de maior acesso em um server novo, e ainda não possuímos o nosso banco.
Quanto ao script, passamos o diretório /initdb
que criamos no volume do container junto do arquivo initial.sql
criado pela migration.
8º Passo
Entretanto, para deixar algo mais automatizado, iremos criar um script shell para executar tal comando. Portanto, iremos criar um arquivo chamado init.sh
na raiz do repositório com o seguinte conteúdo:
#!/bin/bash
while :
do
PORTS=`lsof -i:1433`
echo $PORTS
if ["$PORTS" = '']; then
echo 'Database is down, trying to reconnect to run initial migrations!'
sleep 5s
else
docker exec todo-database /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P TodoApiSqlPass123! -d master -i initdb/initial.sql; echo "All done!";
break
fi
done
echo "Successfuly executed!"
⚠️ Atenção: Este script está preparado para computadores Linux ou Mac mas você pode adaptar para Windows com o Powershell.
Como funciona esse script?
Primeiramente, como o container de um banco de dados SQL Server pode ser demorado a subir, temos que garantir algo para validar se o mesmo está operacional. Esse container está expondo a porta 1433 e com ela, iremos verificar se o banco está operacional.
O script roda o comando lsof -i:1433
para verificar os processo executando nessa porta. Se não encontrar nenhum processo usando a porta, o mesmo irá imprimir a mensagem Database is down, trying to reconnect to run initial migrations!
e irá esperar por 05 segundos para tentar novamente.
Caso o mesmo encontre o container do banco de dados, irá executar o sqlcmd
e ao fim do processo, imprimir as mensagens All done!
e Successfuly executed!
.
Para facilitar a visualização, você pode clonar o repositório completo.
Danilo Silva
Desenvolvedor de software experiente em boas práticas, clean code e no desenvolvimento de software embarcado e de integração com hardwares de controle e telecomunicação.
Top comments (1)
Bom trabalho. Ficou didático o passo a passo.