楔子
公司即將新開專案,打算用點時髦的技術,需要探探路。之前沒做過微服務專案,沒有技術棧方面的積(負)累(債),
乾脆就上微軟的分散式執行時Dapr......嗯......用來服務發現,然後等測試用的服務開發好了,就開始糾結用啥反向代理/閘道器,nginx都沒怎麼用過,更別提其他複雜閘道器了,這時看了一篇微軟的YARP(Yet Another Reverse Proxy)的文章,發現已經preview.10了,還挺簡單的,二次開發也方便,就用它先頂上吧。
開發環境
WSL
windows下跨平臺開發的優秀方案,Linux 分發版我用的Ubuntu 20.04。Docker Desktop
雖然Docker不是Dapr開發環境的唯一選擇,但Docker的跨平臺做的很好,尤其Docker Desktop視覺化,還自帶Docker-Compose
,安裝也方便,可以彈射起步。
安裝完開啟後,開啟控制檯,驗證一下:docker --version
dotnet SDK
YARP最低支援.NET Core 3.1
,這個時間點(2021.04),推薦.NET 5
。
驗證:dotnet --version
Dapr
Dapr安裝我記得挺快的,之後的初始化dapr init
,網不好的話,可能要多試幾次。
初始化確認Docker Desktop
裡dapr_placement,dapr_redis,dapr_zipkin 3個容器都在正常執行Tye
Tye 是微軟開發提供的一款簡化分散式應用開發的輔助命令列工具。用.NET寫的,自帶Dapr擴充套件。
dotnet tool全域性安裝後,可以如下驗證:tye --version
知識儲備
- Yarp配置
Yarp主要要配置的東西就是Cluster(叢集)和ProxyRoute(路由),
本例中,ProxyRoute通過配置檔案載入,Cluster指向Dapr-sidecar,由程式動態新增。
原理
- Yarp服務收到http請求
- 自定義Yarp轉換:
http(s)://<Yarp服務>/api/<服務名>/XXXXX 轉為 http://<Dapr-sidecar>/v1.0/invoke/<服務名>/method/XXXXX
注:這裡的
<Dapr-sidecar>
可能是動態的,因此不應該寫死在配置檔案裡 - 請求轉給Dapr-sidecar
- Dapr 服務發現 終端服務並呼叫
- 返回響應
開發
-
建立兩個Asp.Net Core專案:
- GatewayDemo 閘道器Demo
- ServiceSample 示例服務
-
完成 ServiceSample
這個示例比較簡單,所以不需要引用Dapr SDK,只需加一個測試用的Controller就好了:[Controller] [Route("sample")] public class SampleController { [HttpGet("{account}")] public ActionResult Get(string account) { return new JsonResult(new { Account = account, Balance = 100 }); } }
-
新增引用
- GatewayDemo.csproj 增加包引用:
<PackageReference Include="Yarp.ReverseProxy" Version="1.0.0-preview.10.*" />
注:Yarp.ReverseProxy從preview.10開始包名字變了,之前叫"Microsoft.ReverseProxy"。
- 這個示例比較簡單,不需要引用Dapr的SDK
- GatewayDemo.csproj 增加包引用:
-
動態新增Yarp的Clusters自定義配置
public static IConfigurationBuilder AddDaprConfig(this IConfigurationBuilder configurationBuilder) { var httpEndpoint = DaprDefaults.GetDefaultHttpEndpoint(); //參考Dapr.Client,獲取到dapr-sidecar的url return configurationBuilder.AddInMemoryCollection(new[] { new KeyValuePair<string, string>("Yarp:Clusters:dapr-sidecar:Destinations:d1:Address", httpEndpoint), }); } //GatewayDemo的Program.cs Host.CreateDefaultBuilder(args) ... .ConfigureAppConfiguration((_, builder) => builder.AddDaprConfig())
-
GatewayDemo appsettings 增加Yarp相關配置
Yarp: Routes: - RouteId: r-module-master ClusterId: dapr-sidecar Match: Path: api/service-sample/{**catch-all} Metadata: Dapr: method #標註Dapr
不用在意為什麼是yaml,而不是json。
-
新增Yarp的自定義轉換
public class DaprTransformProvider : ITransformProvider { public void ValidateRoute(TransformRouteValidationContext context) { } public void ValidateCluster(TransformClusterValidationContext context) { } public void Apply(TransformBuilderContext context) { string daprAct = null; if (context.Route.Metadata?.TryGetValue(DaprYarpConst.MetaKeys.Dapr, out daprAct) ?? false) //通過後設資料判斷是否是Dapr服務,在配置檔案中設定 { switch (daprAct) { case DaprYarpConst.DaprAct.Method: context.AddRequestTransform(transformContext => { var index = transformContext.Path.Value!.IndexOf('/', 5); // format: /api/<服務>/xxxx var appId = transformContext.Path.Value.Substring(5, index - 5); var newPath = transformContext.Path.Value.Substring(index); transformContext.ProxyRequest.RequestUri = new Uri($"{transformContext.DestinationPrefix}/v1.0/invoke/{appId}/method{newPath}"); return ValueTask.CompletedTask; }); break; } } } } //GatewayDemo的Startup public class Startup { private readonly IConfiguration _configuration; public Startup(IConfiguration configuration) { _configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.AddReverseProxy() .LoadFromConfig(_configuration.GetSection("Yarp")) .AddTransforms<DaprTransformProvider>(); //加上自定義轉換 } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapReverseProxy(); //endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } }
-
配置tye
參考:name: dapr-yarp.sample extensions: - name: dapr components-path: "./components/" config: dapr-config exclude-placement-container: true placement-port: 6050 - name: zipkin services: - name: gateway-demo #服務名稱最好不要有大寫,容易出問題 project: ./GatewayDemo/GatewayDemo.csproj - name: service-sample #服務名稱最好不要有大寫,容易出問題 project: ./ServiceSample/ServiceSample.csproj - name: zipkin #dapr的追蹤服務 external: true bindings: - name: http port: 9411 ingress: - name: ingress rules: - service: gateway-demo path: / bindings: - name: ingress protocol: https port: 44363 #對外埠
./componnets/資料夾下還有一些配置檔案,這裡就不貼了
-
用tye同時執行多個專案
確保dapr那3個容器服務正常執行後,執行 tye run,通過tye的Dapr擴充套件,執行顯示以下幾個服務:- gateway-demo
- service-sample
- zipkin
- gateway-demo-dapr
- service-sample-dapr
- ingress
-
測試:
瀏覽器直接開啟:https://localhost:44363/api/service-sample/sample/1234
如果顯示{"account":"1234","balance":100}
就說明通了。
附上Demo程式碼
尾聲
因為目前沒有用到對外的grpc呼叫和Actor、釋出訂閱等功能,所以本示例沒有相關程式碼演示。
Yarp雖然還在preview,但有興趣的.NET技術棧的玩家已經可以用它來做些簡單的反向代理了。