将 API 网关从 Ocelot 迁移到 YARP
本指南提供了将您现有的微服务应用程序的 API 网关从 Ocelot 迁移到 YARP 的指导。由于 YARP 在 ABP v8.0+ 中可用,您需要更新现有的应用程序才能应用 YARP 更改。
历史背景
在此版本之前,ABP 在微服务启动模板 中使用 Ocelot 作为 API 网关。由于 Ocelot 库没有得到积极维护,我们寻找了替代方案,并决定将 API 网关从 Ocelot 切换到 YARP。YARP 由 Microsoft 维护,正在积极开发中,似乎是比 Ocelot 更好的替代方案,并提供相同的功能集甚至更多。
YARP 迁移步骤
您需要更新不同项目中的各种文件,以将您的 API 网关从 Ocelot 升级到 YARP。所有必需的更改均在以下不同部分列出,请按照以下步骤从 Ocelot 升级到 YARP。
或者,您可以创建一个微服务启动模板,并将您的更改与微服务模板进行比较。这是确保所有必需更改都已完成的好方法。
共享主机网关项目
- 移除 Ocelot 包并添加
YARP包,如下所示:
<ItemGroup>
- <PackageReference Include="Ocelot" Version="17.0.0" />
- <PackageReference Include="Ocelot.Provider.Polly" Version="17.0.0" />
+ <PackageReference Include="Yarp.ReverseProxy" Version="2.0.0" />
</ItemGroup>
- 打开
*SharedHostingGatewaysModule.cs并进行以下更改:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var env = context.Services.GetHostingEnvironment();
- var ocelotBuilder = context.Services.AddOcelot(configuration)
- .AddPolly();
- if (!env.IsProduction())
- {
- ocelotBuilder.AddDelegatingHandler<AbpRemoveCsrfCookieHandler>(true);
- }
+ context.Services.AddReverseProxy()
+ .LoadFromConfig(configuration.GetSection("ReverseProxy"));
}
- 按如下所示更新
GatewayHostBuilderExtensions.cs文件:
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.Hosting;
public static class AbpHostingHostBuilderExtensions
{
public const string AppYarpJsonPath = "yarp.json";
public static IHostBuilder AddYarpJson(
this IHostBuilder hostBuilder,
bool optional = true,
bool reloadOnChange = true,
string path = AppYarpJsonPath)
{
return hostBuilder.ConfigureAppConfiguration((_, builder) =>
{
builder.AddJsonFile(
path: AppYarpJsonPath,
optional: optional,
reloadOnChange: reloadOnChange
);
});
}
}
- 从解决方案中删除
OcelotConfiguration.cs文件。
公共 Web 网关项目
- 删除 ocelot.json 文件,并创建一个新的 yarp.json 文件,并按如下所示更新其内容(在
PublicWebGateway项目的根目录下):
{
"ReverseProxy": {
"Routes": {
"AbpApi": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/{**catch-all}"
}
},
"AdministrationSwagger": {
"ClusterId": "Administration",
"Match": {
"Path": "/swagger-json/Administration/swagger/v1/swagger.json"
},
"Transforms": [
{
"PathRemovePrefix": "/swagger-json/Administration"
}
]
},
"Account": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account/{**catch-all}"
}
},
"AuthServerSwagger": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/swagger-json/AuthServer/swagger/v1/swagger.json"
},
"Transforms": [
{
"PathRemovePrefix": "/swagger-json/AuthServer"
}
]
},
"Product": {
"ClusterId": "Product",
"Match": {
"Path": "/api/product-service/{**catch-all}"
}
},
"ProductSwagger": {
"ClusterId": "Product",
"Match": {
"Path": "/swagger-json/Product/swagger/v1/swagger.json"
},
"Transforms": [
{
"PathRemovePrefix": "/swagger-json/Product"
}
]
}
},
"Clusters": {
"AuthServer": {
"Destinations": {
"AuthServer": {
"Address": "https://localhost:44322/"
}
}
},
"Administration": {
"Destinations": {
"Administration": {
"Address": "https://localhost:44367/"
}
}
},
"Product": {
"Destinations": {
"Product": {
"Address": "https://localhost:44361/"
}
}
}
}
}
}
- 打开
Program.cs文件并进行以下更改:
builder.Host
.AddAppSettingsSecretsJson()
- .AddOcelotJson()
+ .AddYarpJson()
.UseAutofac()
.UseSerilog();
- 打开
PublicWebGateway项目的模块类,并按如下所示更新OnApplicationInitialization方法:
+ using Yarp.ReverseProxy.Configuration;
//其他配置...
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
var configuration = context.GetConfiguration();
+ var proxyConfig = app.ApplicationServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCorrelationId();
app.UseStaticFiles();
+ app.UseCors();
+ app.UseRouting();
+ app.UseAuthorization();
app.UseSwagger();
+ app.UseAbpSwaggerUI(options => { ConfigureSwaggerUI(proxyConfig, options, configuration); });
app.UseAbpSerilogEnrichers();
+ app.UseRewriter(CreateSwaggerRewriteOptions());
app.UseEndpoints(endpoints => endpoints.MapReverseProxy());
}
+ private static void ConfigureSwaggerUI(
+ IProxyConfig proxyConfig,
+ SwaggerUIOptions options,
+ IConfiguration configuration)
+ {
+ foreach (var cluster in proxyConfig.Clusters)
+ {
+ options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API");
+ }
+ options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
+ options.OAuthScopes(
+ "AdministrationService",
+ "AccountService",
+ "ProductService"
+ );
+ }
+ private static RewriteOptions CreateSwaggerRewriteOptions()
+ {
+ var rewriteOptions = new RewriteOptions();
+ rewriteOptions.AddRedirect("^(|\\|\\s+)$", "/swagger"); // 用于 "/" 和 ""(空白)的正则表达式
+ return rewriteOptions;
+ }
Web 网关项目
- 删除 ocelot.json 文件,并创建一个新的 yarp.json 文件,并按如下所示更新其内容(在
WebGateway项目的根目录下):
{
"ReverseProxy": {
"Routes": {
"AbpApi": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/{**catch-all}"
}
},
"SettingManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/setting-management/{**catch-all}"
}
},
"FeatureManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/feature-management/{**catch-all}"
}
},
"PermissionManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/permission-management/{**catch-all}"
}
},
"AuditLogging": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/audit-logging/{**catch-all}"
}
},
"LanguageManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/language-management/{**catch-all}"
}
},
"TextTemplateManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/text-template-management/{**catch-all}"
}
},
"LeptonThemeManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/lepton-theme-management/{**catch-all}"
}
},
"GDPR": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/gdpr/{**catch-all}"
}
},
"AdministrationSwagger": {
"ClusterId": "Administration",
"Match": {
"Path": "/swagger-json/Administration/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Administration" }
]
},
"Account": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account/{**catch-all}"
}
},
"AccountAdmin": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account-admin/{**catch-all}"
}
},
"AuthServerSwagger": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/swagger-json/AuthServer/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/AuthServer" }
]
},
"Identity": {
"ClusterId": "Identity",
"Match": {
"Path": "/api/identity/{**catch-all}"
}
},
"OpenIddict": {
"ClusterId": "Identity",
"Match": {
"Path": "/api/openiddict/{**catch-all}"
}
},
"IdentitySwagger": {
"ClusterId": "Identity",
"Match": {
"Path": "/swagger-json/Identity/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Identity" }
]
},
"Saas": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/saas/{**catch-all}"
}
},
"Payment": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/payment/{**catch-all}"
}
},
"PaymentAdmin": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/payment-admin/{**catch-all}"
}
},
"SaasSwagger": {
"ClusterId": "Saas",
"Match": {
"Path": "/swagger-json/Saas/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Saas" }
]
},
"Product": {
"ClusterId": "Product",
"Match": {
"Path": "/api/product-service/{**catch-all}"
}
},
"ProductSwagger": {
"ClusterId": "Product",
"Match": {
"Path": "/swagger-json/Product/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Product" }
]
}
},
"Clusters": {
"AuthServer": {
"Destinations": {
"AuthServer": {
"Address": "https://localhost:44322/"
}
}
},
"Administration": {
"Destinations": {
"Administration": {
"Address": "https://localhost:44367/"
}
}
},
"Identity": {
"Destinations": {
"Identity": {
"Address": "https://localhost:44388/"
}
}
},
"Saas": {
"Destinations": {
"Saas": {
"Address": "https://localhost:44381/"
}
}
},
"Product": {
"Destinations": {
"Product": {
"Address": "https://localhost:44361/"
}
}
}
}
}
}
- 打开
Program.cs文件并进行以下更改:
builder.Host
.AddAppSettingsSecretsJson()
- .AddOcelotJson()
+ .AddYarpJson()
.UseAutofac()
.UseSerilog();
- 打开
WebGateway项目的模块类,并按如下所示更新OnApplicationInitialization方法:
+ using Yarp.ReverseProxy.Configuration;
//其他配置...
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
var configuration = context.GetConfiguration();
+ var proxyConfig = app.ApplicationServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCorrelationId();
app.UseStaticFiles();
+ app.UseCors();
+ app.UseRouting();
+ app.UseAuthorization();
app.UseSwagger();
+ app.UseAbpSwaggerUI(options => { ConfigureSwaggerUI(proxyConfig, options, configuration); });
app.UseAbpSerilogEnrichers();
+ app.UseRewriter(CreateSwaggerRewriteOptions());
app.UseEndpoints(endpoints => endpoints.MapReverseProxy());
}
+ private static void ConfigureSwaggerUI(
+ IProxyConfig proxyConfig,
+ SwaggerUIOptions options,
+ IConfiguration configuration)
+ {
+ foreach (var cluster in proxyConfig.Clusters)
+ {
+ options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API");
+ }
+ options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
+ options.OAuthScopes(
+ "AdministrationService",
+ "AccountService",
+ "IdentityService",
+ "SaasService",
+ "ProductService"
+ );
+ }
+ private static RewriteOptions CreateSwaggerRewriteOptions()
+ {
+ var rewriteOptions = new RewriteOptions();
+ rewriteOptions.AddRedirect("^(|\\|\\s+)$", "/swagger"); // 用于 "/" 和 ""(空白)的正则表达式
+ return rewriteOptions;
+ }
图表更新
- 按如下所示更新
gateway-web-public-configmap.yaml文件(etc/k8s//charts/gateway-web-public/templates/gateway-web-public-configmap.yaml ):
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-configmap
data:
yarp.json: |-
{
"ReverseProxy": {
"Routes": {
"AbpApi": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/{**catch-all}"
}
},
"AdministrationSwagger": {
"ClusterId": "Administration",
"Match": {
"Path": "/swagger-json/Administration/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Administration" }
]
},
"Account": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account/{**catch-all}"
}
},
"AuthServerSwagger": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/swagger-json/AuthServer/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/AuthServer" }
]
},
"Product": {
"ClusterId": "Product",
"Match": {
"Path": "/api/product-service/{**catch-all}"
}
},
"ProductSwagger": {
"ClusterId": "Product",
"Match": {
"Path": "/swagger-json/Product/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Product" }
]
}
},
"Clusters": {
"AuthServer": {
"Destinations": {
"AuthServer": {
"Address": "{{ .Values.reRoutes.accountService.url }}"
}
}
},
"Administration": {
"Destinations": {
"Administration": {
"Address": "{{ .Values.reRoutes.administrationService.url }}"
}
}
},
"Product": {
"Destinations": {
"Product": {
"Address": "{{ .Values.reRoutes.productService.url }}"
}
}
}
}
}
}
- 在
gateway-web-public-deployment.yaml文件中进行以下更改(etc/k8s//charts/gateway-web-public/templates/gateway-web-public-deployment.yaml ):
- mountPath: /app/ocelot.json
+ mountPath: /app/yarp.json
- subPath: ocelot.json
+ subPath: yarp.json
- 按如下所示更新
values.yaml文件(etc/k8s//charts/gateway-web-public/values.yaml ):
config:
selfUrl: # https://gateway-public.myprojectname.dev
corsOrigins: # https://myprojectname-st-gateway-web,https://myprojectname-st-gateway-public-web
authServer:
authority: # http://myprojectname-st-authserver
requireHttpsMetadata: # "false"
metadataAddress: # https://authserver.myprojectname.dev/.well-known/openid-configuration
swaggerClientId: # WebGateway_Swagger
dotnetEnv: Staging
redisHost: #
rabbitmqHost: #
elasticsearchUrl: #
AbpLicenseCode: #
reRoutes:
accountService:
url: http://myprojectname-st-authserver
saasService:
url: http://saas-st-administration
administrationService:
url: http://myprojectname-st-administration
productService:
url: http://myprojectname-st-product
ingress:
host: gateway-public.myprojectname.dev
tlsSecret: myprojectname-tls
image:
repository: mycompanyname/myprojectname-gateway-web-public
tag: latest
pullPolicy: IfNotPresent
env: {}
- 按如下所示更新
gateway-web-configmap.yaml文件(etc/k8s//charts/gateway-web/templates/gateway-web-configmap.yaml ):
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-configmap
data:
yarp.json: |-
{
"ReverseProxy": {
"Routes": {
"AbpApi": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/{**catch-all}"
}
},
"SettingManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/setting-management/{**catch-all}"
}
},
"FeatureManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/feature-management/{**catch-all}"
}
},
"PermissionManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/permission-management/{**catch-all}"
}
},
"AuditLogging": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/audit-logging/{**catch-all}"
}
},
"LanguageManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/language-management/{**catch-all}"
}
},
"TextTemplateManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/text-template-management/{**catch-all}"
}
},
"LeptonThemeManagement": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/lepton-theme-management/{**catch-all}"
}
},
"GDPR": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/gdpr/{**catch-all}"
}
},
"AdministrationSwagger": {
"ClusterId": "Administration",
"Match": {
"Path": "/swagger-json/Administration/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Administration" }
]
},
"Account": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account/{**catch-all}"
}
},
"AccountAdmin": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account-admin/{**catch-all}"
}
},
"AuthServerSwagger": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/swagger-json/AuthServer/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/AuthServer" }
]
},
"Identity": {
"ClusterId": "Identity",
"Match": {
"Path": "/api/identity/{**catch-all}"
}
},
"OpenIddict": {
"ClusterId": "Identity",
"Match": {
"Path": "/api/openiddict/{**catch-all}"
}
},
"IdentitySwagger": {
"ClusterId": "Identity",
"Match": {
"Path": "/swagger-json/Identity/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Identity" }
]
},
"Saas": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/saas/{**catch-all}"
}
},
"Payment": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/payment/{**catch-all}"
}
},
"PaymentAdmin": {
"ClusterId": "Saas",
"Match": {
"Path": "/api/payment-admin/{**catch-all}"
}
},
"SaasSwagger": {
"ClusterId": "Saas",
"Match": {
"Path": "/swagger-json/Saas/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Saas" }
]
},
"Product": {
"ClusterId": "Product",
"Match": {
"Path": "/api/product-service/{**catch-all}"
}
},
"ProductSwagger": {
"ClusterId": "Product",
"Match": {
"Path": "/swagger-json/Product/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Product" }
]
}
},
"Clusters": {
"AuthServer": {
"Destinations": {
"AuthServer": {
"Address": "{{ .Values.reRoutes.accountService.url }}"
}
}
},
"Administration": {
"Destinations": {
"Administration": {
"Address": "{{ .Values.reRoutes.administrationService.url }}"
}
}
},
"Identity": {
"Destinations": {
"Identity": {
"Address": "{{ .Values.reRoutes.identityService.url }}"
}
}
},
"Saas": {
"Destinations": {
"Saas": {
"Address": "{{ .Values.reRoutes.saasService.url }}"
}
}
},
"Product": {
"Destinations": {
"Product": {
"Address": "{{ .Values.reRoutes.productService.url }}"
}
}
}
}
}
}
- 在
gateway-web-deployment.yaml文件中进行以下更改(etc/k8s//charts/gateway-web/templates/gateway-web-deployment.yaml ):
- mountPath: /app/ocelot.json
+ mountPath: /app/yarp.json
- subPath: ocelot.json
+ subPath: yarp.json
- 按如下所示更新
values.yaml文件(etc/k8s//charts/gateway-web/values.yaml ):
config:
selfUrl: # https://gateway-web.myprojectname.dev
corsOrigins: # https://myprojectname-st-angular
globalConfigurationBaseUrl: # http://myprojectname-st-gateway-web
authServer:
authority: # http://myprojectname-st-authserver
requireHttpsMetadata: # "false"
metadataAddress: # https://authserver.myprojectname.dev/.well-known/openid-configuration
swaggerClientId: # WebGateway_Swagger
dotnetEnv: #
redisHost: #
rabbitmqHost: #
elasticsearchUrl: #
AbpLicenseCode: #
reRoutes:
accountService:
url: http://myprojectname-st-authserver
saasService:
url: http://saas-st-administration
administrationService:
url: http://myprojectname-st-administration
identityService:
url: http://myprojectname-st-identity
productService:
url: http://myprojectname-st-product
ingress:
host: # gateway-web.myprojectname.dev
tlsSecret: myprojectname-tls
image:
repository: mycompanyname/myprojectname-gateway-web
tag: latest
pullPolicy: IfNotPresent
env: {}
- 按如下所示更新
values.yaml文件(etc/k8s//values.yaml ):
- globalConfigurationBaseUrl: http://myprojectname-st-gateway-web
reRoutes:
accountService:
+ url: http://myprojectname-st-authserver
- dns: https://authserver.myprojectname.dev
- schema: http
- host: myprojectname-st-authserver
- port: 80
identityService:
+ url: http://myprojectname-st-identity
- dns: https://identity.myprojectname.dev
- schema: http
- host: myprojectname-st-identity
- port: 80
administrationService:
+ url: http://myprojectname-st-administration
- dns: https://administration.myprojectname.dev
- schema: http
- host: myprojectname-st-administration
- port: 80
saasService:
+ url: http://myprojectname-st-saas
- dns: https://saas.myprojectname.dev
- schema: http
- host: myprojectname-st-saas
- port: 80
productService:
+ url: http://myprojectname-st-product
- dns: https://product.myprojectname.dev
- schema: http
- host: myprojectname-st-product
- port: 80
抠丁客


