Desvendando o Poder do Docker, Nginx e dotnet 8: Configurando um Proxy Reverso com Facilidade
Tags: docker, dotnet, nginx, proxy
Published: 2024-02-14 03:08:08

Você está pronto para levar suas aplicações .NET para o próximo nível? Nesta postagem, vamos explorar como configurar um proxy reverso usando **Docker**, **Nginx** e o **.NET 8**. Essa configuração poderosa não apenas otimiza o desempenho de suas aplicações, mas também oferece maior segurança e escalabilidade.
Antes de começar vamos entender o que é um **proxy reverso**, você sabe o que é um **proxy**?
Um **proxy** é um intermediário entre o usuário e o destino final na comunicação pela internet. Ele atua como um servidor que recebe solicitações de um cliente e as encaminha para o destino pretendido. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3ni4gfi1c8nt6rrbqjv.jpg)
Existem vários tipos de **proxies**, cada um com suas próprias funcionalidades e finalidades:

- **Proxy de Encaminhamento (Forward Proxy)**: Neste tipo de proxy, o cliente envia suas solicitações para o proxy, que por sua vez as encaminha para o destino. É comumente usado para contornar restrições de rede ou acessar conteúdo bloqueado.

- **Proxy Reverso (Reverse Proxy)**: Ao contrário do forward proxy, o reverse proxy recebe solicitações de clientes em nome de um ou mais servidores de destino. Ele pode ser usado para equilibrar a carga entre servidores, ocultar a infraestrutura de servidores de destino ou fornecer cache e segurança adicional.

- **Proxy Transparente**: Este tipo de proxy não requer configuração no cliente. Ele intercepta todas as solicitações de rede sem que o cliente tenha conhecimento disso. Geralmente usado em firewalls para aplicar políticas de segurança.

- **Proxy de Nível de Aplicativo**: Este tipo de proxy opera no nível de aplicativo e é específico para um protocolo, como HTTP ou FTP. Ele pode modificar os dados transmitidos entre o cliente e o servidor de destino.

Os **proxies** desempenham um papel crucial na otimização de rede, segurança e gerenciamento de tráfego na internet. Eles são amplamente utilizados em ambientes corporativos, provedores de serviços de internet e aplicativos que exigem alto desempenho e segurança.

## **Proxy Reverso**

Um **Proxy Reverso** é um tipo de servidor **proxy** que opera na frente de um ou mais servidores de destino. Ele recebe solicitações de clientes em nome desses servidores e, em seguida, encaminha essas solicitações para o servidor apropriado. O servidor de destino, por sua vez, envia a resposta de volta ao **proxy reverso**, que a encaminha de volta ao cliente.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tte4q8mwq9zei9e20y79.jpg)
Aqui está um exemplo para ilustrar o funcionamento de um proxy reverso:

Imagine que você tenha três servidores web em execução: Servidor A, Servidor B e Servidor C. Cada servidor executa uma parte diferente do seu site ou aplicação. O **Proxy Reverso** está configurado para lidar com todas as solicitações dos clientes.

_1 - Um cliente faz uma solicitação para acessar o seu site.
2 - A solicitação do cliente chega ao **Proxy Reverso**.
3 - O **Proxy Reverso**, baseando-se em regras de roteamento definidas, encaminha a solicitação para o servidor apropriado. Por exemplo, se a solicitação for para uma página específica do seu site que está sendo servida pelo Servidor B, o **Proxy Reverso** encaminhará a solicitação para o Servidor B.
4 - O Servidor B processa a solicitação e envia a resposta de volta ao **Proxy Reverso**.
5 - O **Proxy Reverso** recebe a resposta do Servidor B e a envia de volta ao cliente._
Este é um cenário básico, mas as configurações do **Proxy Reverso** podem ser muito mais complexas. Ele pode ser configurado para balanceamento de carga entre os servidores, cache de conteúdo estático, **SSL** e filtragem de conteúdo, entre outras funcionalidades.

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

Em resumo, o **Proxy Reverso** atua como um intermediário entre os clientes e os servidores de destino, ajudando a melhorar o desempenho, a segurança e a confiabilidade de um sistema distribuído na web.

## **Minimal API**

Para o exemplo, vamos usar uma simples **API** padrão do .NET. Como o mais importante será a configuração do **Nginx** e a criação do **docker-compose**, vou adotar a **API** criada por padrão.

Para criar uma API, execute o comando:
```bash
dotnet new webapi -n WeatherForecastApi
```

Após a execução do comando, será gerado o projeto WebAPI. No arquivo **Program.cs**, haverá um código inicial semelhante ao seguinte:

```cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
```
Entre no diretório do projeto e no terminal rode os comandos:
```bash
dotnet build
```
```bash
dotnet run
```
Se tudo estiver certo e nada errado, ao acessar a url http://localhost:5037/swagger/index.html será redirecionado para o swagger. **Obs.: No meu caso a aplicação subiu na porta _5037_, verifique o seu e mude caso necessário.**


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

## **Dockerfile**
Vamos criar a **image** da **API** que acabamos de desenvolver. Para isso, crie um arquivo **Dockerfile** no projeto com o seguinte conteúdo:

```
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y wget
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "WeatherForecastApi.dll"]
```
Vamos testar a criação da nossa **image** e do **container**. Para criar a imagem, execute o seguinte comando:

```bash
docker build -t wandersonalves/weather-forecast-api .
```
Para criar o **container**, execute:
```bash
docker run --rm -it -p 5069:8080 --name api wandersonalves/weather-forecast-api 
```
Se estiver correto vamos conseguir acessar o **Swagger** via http://localhost:8080/swagger/index.html

## **Configurar Nginx**

Para podemos fazer que o **Nginx** seja **proxy reverso** da nossa **API**, vamos ter que criar o arquivo **nginx.conf**. A configuração para esse exemplo será bem simples, porém podemos ter configurações mais complexa.

```bash
events {}

http {
    server {
        listen 80;

        location / {
            proxy_pass http://api:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
```
O trecho de código no qual realizamos o redirecionamento ao acessar o **Nginx** é a linha **_proxy_pass http://api:8080;_**. É nesta linha que seremos redirecionados para a nossa **API**, que está rodando na porta **8080**. Uma observação bastante importante é a forma como acessamos nossa **API**, não é via **IP** direto e sim pelo nome. Quem fica responsável pela resolução de nome para **IP** é o **Docker**. Para isso, o **Nginx** e a **API** precisam estar na mesma rede.

## **Docker-compose**
Agora vamos juntar tudo que fizemos até agora para podemos acessar nossa **API** através do proxy reverso **Nginx**. Nosso arquivo vai ficar igual o trecho abaixo:

```
version: "3"

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    networks:
      - mynet
    depends_on:
      api:
        condition: service_healthy

  api:
    build:
      context: .
    container_name: api
    tty: true
    ports:
      - "8080:8080"
    networks:
      - mynet
    healthcheck:
      test: wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1
      interval: 30s
      retries: 5
      start_period: 20s
      timeout: 10s

networks:
  mynet:
    driver: bridge
```
O devemos saber:

- Nossa rede é do tipo **bridge** e o nome dela será **mynet**, e a **API** e **Nginx** estão na mesma rede;
- Como estão na mesma rede podemos usar o nome ao invés do **IP**, quem fica responsável pela resolução de nome para **IP** é o **docker**;
- Para evitar que o **container** **Nginx** suba antes da nossa **API**, implementamos um **healthcheck**. Além disso, o **Nginx** tem uma dependência da nossa **API** usando **depends_on**. Para saber se nossa **API** subiu sem nenhum problema vamos criar uma chamada de **API Health Checks**, para isso adicione as linhas no arquivo **Program.cs**:

```cs
builder.Services.AddHealthChecks();
app.MapHealthChecks("/healthz");
```

Agora vamos subir nossa infra e ver se nosso **proxy reverso** vai funcionar, para isso execute o comando:
```bash
docker-compose up -d --build
```

Após subir os dois **container**, poderemos acessar nossa **API** na porta **80** do **Nginx** http://localhost/swagger/index.html

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

Nossos dois contêineres, a **API** e o **Nginx**, estão em execução, e o **Nginx** está atuando como um servidor de **proxy reverso** para a **API**.

## **Conclusão**

A combinação de **Docker**, **Nginx** e o **.NET 8** oferece uma solução poderosa para otimizar o desempenho, a segurança e a escalabilidade de suas aplicações. Configurando um **proxy reverso**, podemos direcionar o tráfego de entrada de maneira eficiente para nossas aplicações **.NET**, garantindo uma experiência de usuário confiável e responsiva.

Ao seguir os passos delineados nesta postagem, você aprendeu como preparar seu ambiente, criar uma aplicação **.NET**, configurar o **Dockerfile** e o **Nginx** como um **proxy reverso**, e executar o ambiente usando o **Docker Compose**. Essa abordagem simplifica o processo de implantação e gerenciamento de suas aplicações, permitindo que você se concentre no desenvolvimento de recursos essenciais.

Sinta-se à vontade para explorar e personalizar ainda mais essa configuração de acordo com suas necessidades específicas. E lembre-se, se surgirem dúvidas ou questões adicionais, estou aqui para ajudar!

O código completo pode ser acessado: [Github](https://github.com/wandealves/proxy_reverso_nginx_dotnet)

## **Referências**

[Create a minimal API with ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-8.0&tabs=visual-studio)

[Criando um proxy de encaminhamento usando o roteamento de solicitação de aplicativo](https://learn.microsoft.com/pt-br/iis/extensions/configuring-application-request-routing-arr/creating-a-forward-proxy-using-application-request-routing)

[Health checks in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-8.0)

[Host ASP.NET Core no Linux com Nginx](https://learn.microsoft.com/pt-br/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-8.0&tabs=linux-ubuntu)