ASP.NET Core - Load Balancing com Nginx e Docker
O load balancing
, ou balanceamento de carga é a prática que permite que o tráfego de uma aplicação seja distribuído entre diferentes servidores ou recursos computacionais para evitar sobrecargas e garantir um desempenho eficiente.
Imagine um site popular que recebe muitas visitas em uma determinada funcionalidade. Tanto backend como frontend dessa funcionalidade poderiam se tornar muito demorados.
Em vez de deixar um único servidor lidar com todas as solicitações, o load balancing distribui essas solicitações entre vários servidores. Isso ajuda a evitar que um servidor fique sobrecarregado, garantindo que a aplicação continue funcionando de maneira rápida e eficiente, mesmo durante períodos de alto tráfego.
Aqui, veremos como utilizar o Nginx para balancear a carga de uma API com 03 instâncias iniciadas. Cada instância irá rodar em um servidor, no nosso caso, um container docker, e as solicitações para essa API serão divididas entre as diferentes instâncias.
API
Portanto, para verificar se as mesmas chamadas estão sendo realizadas em diversas máquinas, vamos criar uma controller de exemplo que mostre essa determinada funcionalidade.
using Microsoft.AspNetCore.Mvc;
namespace FirstAPI.Controllers;
[ApiController]
[Route("[controller]")]
public class StatusController : ControllerBase
{
private readonly ILogger<StatusController> _logger;
public StatusController(ILogger<StatusController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.LogInformation("Request on Status Controller");
var MachineName = System.Environment.MachineName;
return Ok(new { Status = "online", MachineName });
}
}
Criamos aqui uma API chamada FirstAPI
no ASP.NET Core. A rota GET /status
mostrará uma mensagem fixa de online junto do nome da máquina na qual a aplicação está rodando. Obtemos essa informação através da propriedade de ambiente System.Environment.MachineName
.
Para que a nossa API também seja executada em uma porta parametrizada pela variável de ambiente, devemos forçar essa URL no arquivo Program.cs
ou Startup.cs
com a seguinte instrução:
var port = builder.Configuration["PORT"];
builder.WebHost.UseUrls($"http://*:{port}");
var app = builder.Build();
Isso garantirá que a aplicação suba pela variável de ambiente PORT
.
Dockerfile
Para que essa API possa ser executada em um container, devemos construir um dockerfile para a mesma. Sabendo que o nome da aplicação é FirstAPI
e que estamos utilizando o .NET Framework 7.0, o dockerfile se dará da seguinte forma:
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS publish-environment
WORKDIR /app
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o published
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=publish-environment /app/published .
ENTRYPOINT ["dotnet", "FirstAPI.dll"]
Nginx
O Nginx é um servidor web de código aberto, utilizado para servir conteúdo na internet, sendo um dos servidores web mais leve dentre os existentes. Além de suas capacidades como servidor web, o Nginx também pode agir como proxy reverso, balanceador de carga e servidor de cache.
Utilizaremos o Nginx como balanceador de carga através do simples arquivo de configuração abaixo:
upstream api {
server first-api:3000;
}
server {
listen 80;
include /etc/nginx/mime.types;
location / {
proxy_pass http://api/;
}
}
Nessa configuração, primeramente criamos um upstream
chamado api
. Aqui temos como server, o endereço chamado first-api:3000
. Nosso intuito será criar o container da api utilizando um docker compose de forma que o nome do serviço, e consequentemente, o endereço criado no DNS da rede criada pelo docker compose seja first-api
.
Caso aqui você tivesse outros servers com diferentes nomes e portas, mas que fizessem parte da mesma funcionalidade, você poderia apenas adicionar linhas nesse bloco. Por exemplo:
upstream api {
server first-api:3000;
server second-api:3001;
}
Nesse bloco, o endereço second-api:3001
também seria uma das opções no load balancing.
Depois, temos o bloco server
que definirá o servidor. Esse servidor será criado na porta 80 e o redirecionamento será para um proxy
apontado para o upstream api
, portanto para o endereço http://api/
.
Docker compose
Agora podemos criar um docker compose com dois serviços. O primeiro serviço será a nossa API com o nome first-api
.
version: '3'
services:
first-api:
build: ./APIs/FirstAPI
environment:
- PORT=3000
deploy:
replicas: 3
networks:
- load-balancing
networks:
load-balancing:
Nesse serviço, injetamos a variável de ambiente PORT
com o valor 3000
. Criamos uma rede interna chamada load-balancing
para o nome do DNS funcionar dentro do docker. Também criamos a chamada de replicas igual a 3 para subir 03 containers com a mesma imagem de nossa API.
Para o serviço do Nginx, seguiremos com a seguinte configuração no mesmo docker compose:
nginx:
image: nginx:latest
container_name: reverse_proxy
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "3000:80"
networks:
- load-balancing
depends_on:
- first-api
Nesse serviço, temos 04 configurações importantes. Primeiramente, o volume que fará com que o arquivo nginx.conf
que criamos a pouco seja o arquivo de configuração default dentro do container do nginx.
Fazemos um bind de portas para que a porta 80 do nginx seja refletida como a porta 3000, originalmente a porta do serviço que devemos replicar.
Devemos também manter a mesma rede load-balancing
e dizer que esse serviço depende do serviço first-api
.
Com esse docker-compose.yml
, podemos subir o mesmo pelo comando:
docker compose up -d --build
E esperamos o seguinte resultado:
Uso da API
A partir desse container, o load balancing do Nginx irá distribuir as solicitações dentre as 03 instâncias da WebAPI.
Para facilitar os testes, 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.