ASP.NET Core - Load Balancing com Nginx e Docker
Tags: braziliandevs, csharp, nginx, webdev
Published: 2024-01-30 14:25:38

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.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sevapoh3bot2jrb4rvf.png)

## 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.

```csharp
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:

```csharp
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:

```Dockerfile
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:

```json
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:

```json
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`.

```yaml
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:

```yaml
  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:

```shell
docker compose up -d --build
```

E esperamos o seguinte resultado:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahzpnxr4rhehcq7ojsj6.png)

## Uso da API

A partir desse container, o load balancing do Nginx irá distribuir as solicitações dentre as 03 instâncias da WebAPI.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phiyg8bmckf5jab3ipot.gif)

Para facilitar os testes, você pode clonar o [Repositório completo](https://github.com/Danilo-Oliveira-Silva/aspnet-load-balancing).

<h3 style="color:#000066">Danilo Silva</h3>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tj4i67b3yid2wlx90oky.png" />

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.

<a href="https://www.linkedin.com/in/danilo-silva-44518956/">Linkedin</a>
<a href="https://github.com/Danilo-Oliveira-Silva">Github</a>
<a href="https://twitter.com/danilosdev">Twitter</a>
<a href="mailto:danilo.o.s@hotmail.com">E-mail</a>