Escalando Aplicações com o YARP no .NET

À medida que aplicações web crescem em complexidade e demanda, torna-se cada vez mais importante pensar na escalabilidade. No ecossistema .NET, uma das ferramentas mais poderosas e flexíveis para ajudar nesse processo é o YARP (Yet Another Reverse Proxy).

Neste artigo, vamos explorar como escalar aplicações com o YARP, com exemplos práticos e explicações detalhadas.

O que é o YARP?

YARP é um projeto open source da Microsoft que fornece um proxy reverso altamente customizável para aplicações .NET. Ele é construído sobre o ASP.NET Core e utiliza todos os benefícios da plataforma .NET moderno, incluindo middleware, injeção de dependência e configuração baseada em código ou arquivos.

Com o YARP, é possível:

  • Distribuir requisições entre múltiplas instâncias de uma aplicação (load balancing).
  • Criar gateways de API.
  • Encaminhar requisições com base em headers, caminhos ou outros critérios.
  • Implementar estratégias de failover e segurança.
  • E o foco deste post: escalar aplicações horizontalmente.

Vamos considerar um cenário onde você possui uma aplicação web composta por diferentes microsserviços:

  • api.produtos
  • api.usuarios
  • api.pedidos

Ao invés de expor cada serviço em uma porta diferente ou configurar um API Gateway externo, podemos usar o YARP para orquestrar tudo isso dentro do ecossistema .NET.

Instalando o YARP

Crie um novo projeto .NET 9.0 (Web Application) e adicione o pacote do YARP:

dotnet add package Yarp.ReverseProxy

Configure o arquivo appsettings.json com o exemplo de múltiplos destino com o código abaixo:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",

  "ReverseProxy": {
    "Routes": {
      "produtos": {
        "ClusterId": "cluster-produtos",
        "Match": {
          "Path": "/produtos/{**catch-all}"
        }
      },
      "usuarios": {
        "ClusterId": "cluster-usuarios",
        "Match": {
          "Path": "/usuarios/{**catch-all}"
        }
      },
      "pedidos": {
        "ClusterId": "cluster-pedidos",
        "Match": {
          "Path": "/pedidos/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "cluster-produtos": {
        "Destinations": {
          "destino1": {
            "Address": "http://localhost:5001/"
          },
          "destino2": {
            "Address": "http://localhost:5002/"
          }
        }
      },
      "cluster-usuarios": {
        "Destinations": {
          "destino1": {
            "Address": "http://localhost:6001/"
          }
        }
      },
      "cluster-pedidos": {
        "Destinations": {
          "destino1": {
            "Address": "http://localhost:7001/"
          },
          "destino2": {
            "Address": "http://localhost:7002/"
          },
          "destino3": {
            "Address": "http://localhost:7003/"
          }
        }
      }
    }
  }
}

Neste exemplo, os serviços produtos e pedidos estão com múltiplas instâncias (ótimo para escalabilidade horizontal).

Configurando o YARP

Na classe Program.cs, configure com o código abaixo:

try
{
    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddRouting(options => options.LowercaseUrls = true);
    builder.Services.AddControllers();

    // Adiciona suporte ao Reverse Proxy
    builder.Services.AddReverseProxy()
	    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

    builder.Services.AddOpenTelemetry();

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

    var app = builder.Build();

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

    app.MapControllers();
    app.MapReverseProxy();

	app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
    Log.Information("Server Shutting down...");
    Log.CloseAndFlush();
}

Load Balancing e Estratégias

Por padrão, o YARP já faz round-robin entre os destinos configurados, mas você pode personalizar o comportamento com políticas:

"cluster-pedidos": {
  "LoadBalancingPolicy": "LeastRequests",
  "Destinations": {
    "destino1": { "Address": "http://localhost:7001/" },
    "destino2": { "Address": "http://localhost:7002/" },
    "destino3": { "Address": "http://localhost:7003/" }
  }
}

Políticas suportadas incluem:

  • FirstAlphabetical
  • Random
  • RoundRobin (default)
  • PowerOfTwoChoices
  • LeastRequests

As políticas detalhadas de cada um você encontra neste link.

Escalando com Kubernetes ou Docker

Em produção, os destinos não seriam portas locais, mas sim containers com réplicas gerenciadas pelo Kubernetes ou Docker Swarm. Você pode configurar o endereço como um DNS interno de um serviço no cluster:

"Address": "http://api-pedidos-service/"

Com isso, o YARP se conecta às instâncias expostas pelo serviço, automaticamente distribuindo as requisições conforme as réplicas disponíveis.

Cluster de Pedidos com 3 Instâncias

Vamos imaginar que temos três instâncias do serviço de pedidos rodando em:

  • http://pedidos-instance1.internal
  • http://pedidos-instance2.internal
  • http://pedidos-instance3.internal

Configuração YARP:

"cluster-pedidos": {
  "LoadBalancingPolicy": "RoundRobin",
  "Destinations": {
    "i1": { "Address": "http://pedidos-instance1.internal/" },
    "i2": { "Address": "http://pedidos-instance2.internal/" },
    "i3": { "Address": "http://pedidos-instance3.internal/" }
  }
}

Agora, todas as requisições que chegam em /pedidos serão distribuídas de forma balanceada entre as três instâncias.

Monitoramento e Observabilidade

Por ser uma aplicação ASP.NET Core, você pode adicionar o middleware de logging, tracing com OpenTelemetry e usar ferramentas como Application Insights ou Prometheus/Grafana para acompanhar as métricas do seu proxy.

No Program.cs, você pode adicionar o seguinte setup:

var builder = WebApplication.CreateBuilder(args);

// Logging com Serilog (exemplo)
builder.Host.UseSerilog((context, services, configuration) =>
{
    configuration
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .WriteTo.Console();
});

// Telemetria com OpenTelemetry
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddOtlpExporter(); // Exporta para Jaeger, Zipkin ou outros
    })
    .WithMetrics(metrics =>
    {
        metrics
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation()
            .AddProcessInstrumentation()
            .AddPrometheusExporter();
    });

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

app.UseSerilogRequestLogging(); // Middleware de logging

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapReverseProxy();
    endpoints.MapPrometheusScrapingEndpoint(); // Exposição das métricas
});

app.Run();

Com isso:

  • As requisições roteadas pelo YARP serão monitoradas.
  • Métricas de latência, volume e status codes estarão disponíveis via Prometheus.
  • Traces distribuídos podem ser visualizados no Jaeger ou Zipkin.
  • Logs estruturados vão facilitar a análise de comportamento e erros.

Vantagens de Usar o YARP

  • Totalmente integrado ao ecossistema .NET.
  • Fácil de configurar.
  • Ideal para microsserviços e cenários de escalabilidade.
  • Permite roteamento inteligente baseado em headers, path, claims, etc.
  • Open Source e com suporte ativo da Microsoft.

Conclusão

O YARP é uma solução robusta, flexível e poderosa para escalar aplicações .NET. Seja para balancear carga, consolidar endpoints de microsserviços ou montar um gateway de API, ele oferece tudo o que você precisa com a simplicidade do ASP.NET Core.

Se você ainda está usando proxies reversos externos ou soluções complexas para algo que poderia estar sob seu controle no código, vale a pena dar uma chance ao YARP.

Os detalhes completo desta série você encontra no meu GitHubhttps://github.com/hgmauri/sample-yarp

Leave a Comment

O seu endereço de email não será publicado. Campos obrigatórios marcados com *