Compreender a importância de uma nova tecnologia como o Docker é fundamental ao iniciar seu aprendizado e o primeiro passo é identificar os problemas que ela resolve. Se uma tecnologia não oferece soluções significativas, sua adoção pode não ser necessária. Isso é especialmente verdadeiro no dinâmico campo do desenvolvimento de software, onde as necessidades e as ferramentas estão em constante evolução.
Quando se trata do Docker, a compreensão de suas vantagens é bastante direta. Vamos explorar os principais benefícios de usar o Docker, especialmente para desenvolvedores que estão começando a sua jornada na área.
Uma das principais razões para aprender Docker é a sua relevância no mundo corporativo. O Docker tornou-se um padrão na indústria para o desenvolvimento, entrega e operação de aplicações, tornando-se uma habilidade quase indispensável para os desenvolvedores modernos. Integrar o Docker em projetos pessoais não só amplia a experiência prática, mas também prepara o desenvolvedor para as exigências e desafios encontrados em ambientes de produção profissionais.
Por que aprender Docker?
Antes de mergulharmos nas especificidades do Docker e de como ele revoluciona o desenvolvimento de software, é importante entender os desafios frequentemente enfrentados por desenvolvedores. Estes problemas, embora possam parecer menores à primeira vista, podem se tornar obstáculos significativos à eficiência e ao sucesso de um projeto. Para contextualizar melhor o impacto do Docker, vamos explorar alguns desses problemas comuns e as soluções tradicionais que são comumente aplicadas.
Problema 1: conflito de portas e compatibilidade de versões
Considere um cenário comum em desenvolvimento: você está trabalhando em duas APIs em .NET, uma API de produtos e outra de clientes. Ambas estão configuradas, no arquivo de inicialização (Startup), para usar a mesma porta, digamos a porta padrão 5000. Ao tentar executar as duas simultaneamente, você se depara com um problema de conflito de portas. Para resolver isso sem Docker, seria necessário alterar manualmente a configuração de uma das APIs para usar uma porta diferente, permitindo a execução simultânea.
Problema 2: compatibilidade de versões
Além do conflito de portas, um desafio frequente em desenvolvimento de software é a compatibilidade de versões de ferramentas e bibliotecas. Por exemplo, diferentes projetos podem depender de versões específicas do .NET ou de outras dependências, levando a conflitos quando executados no mesmo ambiente.
Problema 3: compartilhamento e disponibilização do projeto
Imagine que você está desenvolvendo o backend de uma aplicação em .NET, enquanto um colega assume o desenvolvimento do frontend. Para que ele possa integrar e utilizar os endpoints da sua API, é necessário tornar o projeto acessível. Idealmente, isso envolveria o deploy da aplicação, mas no estágio inicial, o foco é no desenvolvimento local.
Para compartilhar o progresso, você pode subir o código para o GitHub e fornecer acesso ao repositório. No entanto, isso implica que seu colega terá que configurar o ambiente de desenvolvimento: instalar a IDE apropriada, como o Visual Studio, e garantir que a versão correta do runtime seja baixada – um processo que pode ser especialmente complicado se o projeto utilizar uma versão do .NET que não seja padrão na IDE, como por exemplo o .NET 5.
O colaborador também precisará instalar e configurar o banco de dados necessário, seja MySQL, SQL Server ou outro. Isso pode ser um processo trabalhoso, exigindo orientação adicional caso ele não esteja familiarizado com a IDE ou com o processo de configuração das dependências do projeto.
Agora, eis onde o Docker entra como uma solução elegante para esse cenário. Com ele, seu colega simplesmente precisa tê-lo instalado em sua máquina. Então, ele pode baixar e executar a imagem do seu projeto Dockerizado. Essa imagem contém tudo o que é necessário para o projeto funcionar – o serviço backend, a versão correta do runtime do .NET e o banco de dados. Tudo isso sem a necessidade de configurações adicionais de IDE ou instalação de banco de dados, simplificando significativamente o processo de disponibilização e compartilhamento do ambiente de desenvolvimento.
Possíveis soluções sem Docker
As soluções convencionais para os problemas de conflito de portas e compatibilidade de versões incluem:
- Uso de múltiplas máquinas: designar uma máquina física dedicada para cada aplicação.
- Máquinas virtuais: configurar várias máquinas virtuais, cada uma com seu próprio ambiente operacional isolado.
Ambas as abordagens, embora funcionais, têm suas desvantagens. Elas requerem mais recursos, tempo para configuração e manutenção, e não são tão eficientes em termos de uso de hardware e energia.
Conforme a imagem, comparamos uma máquina virtual com um contêiner Docker. Máquinas virtuais replicam ambientes operacionais completos para cada aplicação, enquanto contêineres Docker, sendo mais leves, compartilham o mesmo sistema operacional do host, mas isolam as aplicações em ambientes separados. Isso resulta em maior eficiência de recursos, rapidez na inicialização e facilidade de gerenciamento.
No esquema apresentado, vemos diferentes contêineres, cada um hospedando um serviço específico – APIs e bancos de dados. O Docker permite que cada contêiner tenha sua própria porta interna, que é mapeada para uma porta única no host. Isso evita o conflito direto de portas no host, já que o Docker gerencia o encaminhamento das requisições para as portas corretas internamente nos contêineres. O resultado é uma coexistência harmoniosa onde múltiplos serviços operam independentemente, sem a necessidade de reconfiguração manual ou o uso de múltiplas máquinas. Isso exemplifica a eficiência e a simplicidade do Docker na resolução de conflitos de portas, facilitando a gestão de aplicações em ambientes de desenvolvimento complexos.
Exemplo Prático: rodando uma aplicação .NET com docker no Windows
Após entendermos os benefícios do Docker e como ele pode simplificar nosso trabalho, é hora de colocar a mão na massa com um exemplo prático. Vamos criar um ambiente Docker que executa uma aplicação .NET simples no Windows.
Instale o Docker Desktop no Windows
Acesse o site oficial e baixe a versão mais recente do Docker Desktop para Windows. Execute o instalador e siga as instruções para instalar o Docker no seu sistema. Após isso, abra o PowerShell ou o Prompt de Comando e digite o seguinte comando para verificar se o Docker foi instalado corretamente:
Você deve ver a versão instalada como resultado.
Crie um Projeto .NET Simples
Nesse exemplo estamos utilizando o Visual Studio 2022. Abra-o na versão que preferir e crie um novo projeto. Buscando por “Api”, selecione o projeto “API Web do ASP.NET Core” e siga para a próxima página.
Após dar um nome para o projeto, na próxima página vamos selecionar a versão do .NET que utilizaremos. Nesse exemplo, estarei usando o .NET 6.0.
Há a opção de habilitar o Docker nesse momento, mas não iremos marcar para fazer esse processo nos próximos passos.
Após criar o projeto, para o nosso exemplo, iremos excluir essas duas classes iniciais “WeatherForecastController” e “WeatherForecast“. Vamos criar uma nova controller simples para exemplo.
Com o botão direito em “Controllers”, vamos criar uma nova classe chamada “HomeController“.
Nessa classe, use o seguinte código:
using Microsoft.AspNetCore.Mvc;
namespace DockerApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> ReturnHello()
{
var response = "Hello, Docker!";
return Ok(response);
}
}
}
Criamos um endpoint simples que retorna um “Hello, Docker!”.
Para testar, vamos rodar a aplicação no botão verde de início, ou então com o atalho “F5”. Abrirá a tela do Swagger com o nosso endpoint GET /Home criado. Clicando em “Try it out” no canto direito e depois em “Execute“, temos sucesso na resposta (código 200) e o nosso “Hello”, conforme esperado.
Detalhes do projeto
Para que tudo corra bem com o Docker, vamos fazer algumas modificações simples no projeto.
Primeiro, no arquivo Program (ou Startup caso esteja usando uma versão anterior ao .NET 6), temos o seguinte código:
Nesse trecho, caso a aplicação esteja rodando no ambiente de desenvolvimento, então o Swagger é ativado, ou seja, irá exibir a página que vimos anteriormente para testar nossos endpoints. Em outros ambientes, por motivos de segurança, não fica disponível. Como o foco aqui é apenas teste e não estamos nos preocupando com a segurança da API, iremos modificar para o seguinte código:
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI();
Dessa forma ativamos o Swagger em todos os ambientes, permitindo visualizarmos essa página de documentação quando estivermos utilizando o container. Para fazer isso de forma mais segura, uma das soluções seria definir o Docker como ambiente de desenvolvimento incluindo uma variável de ambiente no Dockerfile (veremos mais detalhes sobre ele adiante). Isso possibilita configurar o comportamento da aplicação com base no ambiente em que está sendo executada.
Conteinerizando o projeto
Agora com o projeto feito, podemos criar o Dockerfile. Há duas formas de fazer, a primeira é manualmente:
Na raiz do projeto, vamos criar um novo arquivo documento de texto e chamá-lo de Dockerfile. Vamos excluir a extensão txt, o nome deverá ficar apenas “Dockerfile”. Aparecerá uma caixa de diálogo para confirmar a ação. Atenção ao local de criação, deverá ser na pasta onde encontramos o arquivo Program.cs conforme mostrado:
Após isso, no Visual Studio, o arquivo aparecerá para que possamos editá-lo. Incluímos o seguinte:
# Usa a imagem oficial do SDK do .NET 6.0 como base
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
# Especifica a porta que a aplicação irá expor
EXPOSE 80
# Define a pasta de trabalho dentro do contêiner
WORKDIR /app
# Copia os arquivos do projeto para o contêiner
COPY . .
# Restaura as dependências e faz o build da aplicação
RUN dotnet restore
RUN dotnet publish -c Release -o out
# Cria uma imagem menor usando a imagem oficial do ASP.NET
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
# Copia os arquivos publicados da fase de build
COPY --from=build /app/out ./
# Especifica o comando a ser executado quando o contêiner for iniciado
CMD ["dotnet", "NomeDaSuaApi.dll"]
A segunda forma de fazer é ainda mais simples, podemos gerar esse Dockerfile automaticamente no Visual Studio: clicando com o botão direito no projeto, em seguida Adicionar e depois Suporte Docker. O SO de destino deverá ser Linux.
Igualmente, teremos um arquivo Dockerfile com os passos semelhantes ao que criamos manualmente.
O Dockerfile é o arquivo de configuração usado para construir imagens de contêineres. É como uma “receita” ou um conjunto de instruções para criar a imagem Docker, onde cada comando adiciona uma camada à imagem final. Ele contém comandos específicos para copiar os arquivos, instalar pacotes, definir variáveis de ambientes, expor portas, definir comandos de inicialização, etc. Fizemos manualmente o básico do básico para criação da imagem, mas poderíamos ter adicionado outros comandos conforme nossa necessidade.
Construção da imagem e execução do container
Agora com o Dockerfile em mãos, vamos gerar a imagem do nosso projeto.
Abrindo o CMD você pode navegar até a pasta do projeto ou, dentro da pasta onde está o projeto, pode escrever CMD na barra de navegação, dessa forma:
Com o CMD aberto, usaremos o comando:
docker build -t dockerapi .
Para entender o comando:
- docker build : comando de build para construir a imagem com base nos comandos do Dockerfile
- -t dockerapi : o -t ou --tag é usada para atribuir um nome ou tag à imagem que estamos construindo, que no caso será “dockerapi”.
- . (ponto) : é o diretório onde está o Dockerfile. O ponto, no caso, indica que é a pasta raiz (onde você abriu o CMD).
Agora vamos rodar a imagem. Utilizamos o seguinte comando:
docker run -d -p 8000:80 --name DockerApiContainer dockerapi
- docker run : comando usado para criar e executar um container a partir de uma imagem docker.
- -d : é opcional, usamos para não travar o terminal. Essa opção indica que o contêiner deve ser executado em segundo plano, em modo detached, o que significa que a linha de comando estará disponível para ser utilizada para outros comandos.
- -p 8000:80 : o “p” ou “–publish” é usado para mapear as portas. No Dockerfile nós falamos que a aplicação será exposta na porta 80. Nesse comando estamos mapeando essa porta 80 do container para a 8000 do host (nosso computador). O 8000 poderia ser qualquer outra porta livre, como 5000, 6000, etc.
- -- name DockerApiContainer : usado para atribuir um nome ao container. Você não precisa utilizar essa opção, então o container será criado com um nome aleatório.
- dockerapi : é o nome da imagem a partir da qual o container será criado.
Repare que após o comando de run, no terminal aparecem alguns números/código. Aquele é o ID do nosso container, podemos usar ele ou então o nome para executar outros comandos, como o log, ou verificar as configurações deste container.
Para confirmar que tudo está funcionando, vamos abrir o Docker Desktop:
Como vemos, o container está em execução (running) com o nome que demos e utilizando nossa imagem criada. Agora para abrir a aplicação, basta clicar no link da porta 8000:80.
Um detalhe importante: a página que abrir terá um erro “Não foi possível encontrar a página deste localhost” ou algo parecido, pois precisamos utilizar a URL do Swagger para exibir os endpoints:
http://localhost:8000/swagger/index.html
A porta será a que escolheu, mas precisa do caminho /swagger/index.html. Testando o endpoint, temos o mesmo retorno de sucesso.
Não abordaremos nesse momento essa etapa, mas caso queira compartilhar o projeto com outras pessoas, basta subir essa imagem para o repositório do Dockerhub, que é semelhante ao Github. Assim, apenas com o nome é possível fazer o “pull” (baixar) a imagem e rodá-la facilmente.
Conclusão
Por fim, neste guia exploramos passo a passo a “conteinerização” de uma aplicação .NET, aproveitando as vantagens que o Docker oferece para facilitar o desenvolvimento e compartilhamento de projetos.
Destacamos a importância do Docker, não apenas como uma ferramenta poderosa para o desenvolvedor individual, mas também como um padrão na indústria para o desenvolvimento, entrega e operação de aplicações.
É legal notar que esse guia cobre aspectos básicos dessa tecnologia, sendo apenas uma introdução para quem está começando a explora-la. O Docker oferece recursos avançados e flexibilidade, que serão explorados em futuros posts, abordando temas como orquestração de contêineres, gerenciamento de volumes, redes, entre outros. Ao dominar esses conceitos, os desenvolvedores poderão maximizar o potencial do Docker em seus projetos, garantindo eficiência, escalabilidade e consistência em ambientes de produção.
0 Comentários