- 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.
135 lines
4.2 KiB
C#
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();
|
|
}
|