fengling-gateway/Program.cs
movingsam abe3456ccb feat[gateway]: add K8s service discovery with pending approval workflow
- Add PendingServiceDiscovery model and database migration
- Add PendingServices API controller for service assignment
- Add KubernetesPendingSyncService for background sync
- Add RBAC configuration for K8s service discovery
- Update Dockerfile and K8s deployment configs
- Add service discovery design documentation

Workflow: K8s services with label managed-by=yarp are discovered
and stored in pending table. Admin approves before they become
active gateway downstream services.
2026-02-22 22:14:54 +08:00

135 lines
4.2 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Serilog;
using Yarp.ReverseProxy.Configuration;
using Yarp.ReverseProxy.LoadBalancing;
using YarpGateway.Config;
using YarpGateway.Data;
using YarpGateway.DynamicProxy;
using YarpGateway.LoadBalancing;
using YarpGateway.Middleware;
using YarpGateway.Services;
using StackExchange.Redis;
using Fengling.ServiceDiscovery.Extensions;
using Fengling.ServiceDiscovery.Kubernetes.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(
(context, services, configuration) =>
configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
);
builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("Jwt"));
builder.Services.Configure<RedisConfig>(builder.Configuration.GetSection("Redis"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<RedisConfig>>().Value);
builder.Services.AddDbContextFactory<GatewayDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
);
builder.Services.AddSingleton<DatabaseRouteConfigProvider>();
builder.Services.AddSingleton<DatabaseClusterConfigProvider>();
builder.Services.AddSingleton<IRouteCache, RouteCache>();
builder.Services.AddSingleton<IRedisConnectionManager, RedisConnectionManager>();
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var config = sp.GetRequiredService<RedisConfig>();
var connectionOptions = ConfigurationOptions.Parse(config.ConnectionString);
connectionOptions.AbortOnConnectFail = false;
connectionOptions.ConnectRetry = 3;
connectionOptions.ConnectTimeout = 5000;
connectionOptions.SyncTimeout = 3000;
connectionOptions.DefaultDatabase = config.Database;
var connection = ConnectionMultiplexer.Connect(connectionOptions);
connection.ConnectionFailed += (sender, e) =>
{
Serilog.Log.Error(e.Exception, "Redis connection failed");
};
connection.ConnectionRestored += (sender, e) =>
{
Serilog.Log.Information("Redis connection restored");
};
return connection;
});
builder.Services.AddSingleton<ILoadBalancingPolicy, DistributedWeightedRoundRobinPolicy>();
builder.Services.AddSingleton<DynamicProxyConfigProvider>();
builder.Services.AddSingleton<IProxyConfigProvider>(sp => sp.GetRequiredService<DynamicProxyConfigProvider>());
builder.Services.AddHostedService<PgSqlConfigChangeListener>();
// 添加 Kubernetes 服务发现
var useInClusterConfig = builder.Configuration.GetValue<bool>("ServiceDiscovery:UseInClusterConfig", true);
builder.Services.AddKubernetesServiceDiscovery(options =>
{
options.LabelSelector = "app.kubernetes.io/managed-by=yarp";
options.UseInClusterConfig = useInClusterConfig;
});
builder.Services.AddServiceDiscovery();
builder.Services.AddHostedService<KubernetesPendingSyncService>();
var corsSettings = builder.Configuration.GetSection("Cors");
builder.Services.AddCors(options =>
{
var allowAnyOrigin = corsSettings.GetValue<bool>("AllowAnyOrigin");
var allowedOrigins = corsSettings.GetSection("AllowedOrigins").Get<string[]>() ?? Array.Empty<string>();
options.AddPolicy("AllowFrontend", policy =>
{
if (allowAnyOrigin)
{
policy.AllowAnyOrigin();
}
else
{
policy.WithOrigins(allowedOrigins);
}
policy.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
builder.Services.AddControllers();
builder.Services.AddHttpForwarder();
builder.Services.AddRouting();
builder.Services.AddReverseProxy();
var app = builder.Build();
app.UseCors("AllowFrontend");
app.UseMiddleware<JwtTransformMiddleware>();
app.UseMiddleware<TenantRoutingMiddleware>();
app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow }));
app.MapControllers();
app.MapReverseProxy();
await app.Services.GetRequiredService<IRouteCache>().InitializeAsync();
try
{
Log.Information("Starting YARP Gateway");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}