轉載至@蝸牛丨大神的.net core Ocelot Consul 實現API網關 服務注冊 服務發現 負載均衡一文,僅對文中所做部分內容進行更新及修改,版權歸屬原作者。謝謝
文章內容:
大神張善友 分享過一篇 《.NET Core 在騰訊財付通的企業級應用開發實踐》里面就是用.net core 和 Ocelot搭建的可擴展的高性能Api網關。
Ocelot(http://ocelot.readthedocs.io)是一個用.NET Core實現並且開源的API網關,它功能強大,包括了:路由、負載均衡、請求聚合、認證、鑒權、限流熔斷等,這些功能只都只需要簡單的配置即可完成。
Consul(https://www.consul.io)是一個分布式,高可用、支持多數據中心的服務注冊、發現、健康檢查和配置共享的服務軟件,由 HashiCorp 公司用 Go 語言開發。
Ocelot天生集成對Consul支持,在OcelotGateway項目中Ocelot.json配置就可以開啟ocelot+consul的組合使用,實現服務注冊、服務發現、健康檢查、負載均衡。
軟件版本
Asp.net Core:3.0預覽5
Ocelot:13.5.0(開發時最新)
Consul:1.5.0(開發時最新)
本文分開兩部分:1、基於Ocelot搭建Api網關;2、Ocelot+Consul 實現下游服務的服務注冊、服務發現、健康檢查、負載均衡。
項目結構
Snai.Ocelot 網關:
Snai.ApiGateway Asp.net Core 2.0 Api網關(由於在3.0預覽5版上面發生了依賴問題,沒有找到解決方案,所以我退回到了Core2.2穩定版本)
Snai.ApiServiceA Asp.net Core 3.0 Api下游服務A
Snai.ApiServiceB Asp.net Core 3.0 Api下游服務B
ApiServiceA和ApiServiceB其實是一樣的,用於負載,為了測試方便,我建了兩個項目
Consul:(我從官網下載的1.5.0中只有一個可執行文件,並沒有其他的目錄及文件,可能是實現的工具比較新的緣故)
conf 配置目錄
data 緩存數據目錄,可清空里面內容
dist Consul UI目錄
consul.exe 注冊軟件
startup.bat 執行腳本
項目實現
一、基於Ocelot搭建Api網關
新建Snai.Ocelot解決方案
1、搭建Api網關
新建 Snai.ApiGateway 基於Asp.net Core 2.2空網站,在 依賴項 右擊 管理NuGet程序包 瀏覽 找到 Ocelot 版本13.5.0安裝
1.1、在項目根目錄下新建一個 Ocelot.json 文件,打開 Ocelot.json 文件,配置Ocelot參數,Ocelot.json 代碼如下
1 { 2 "ReRoutes": [ 3 { 4 "UpstreamPathTemplate": "/apiservice/{controller}", 5 "UpstreamHttpMethod": [ "Get" ], 6 "DownstreamPathTemplate": "/apiservice/{controller}", 7 "DownstreamScheme": "http", 8 "DownstreamHostAndPorts": [ 9 { 10 "host": "localhost", 11 "port": 5011 12 }, 13 { 14 "host": "localhost", 15 "port": 5012 16 } 17 ], 18 "LoadBalancerOptions": { 19 "Type": "LeastConnection" 20 } 21 } 22 ], 23 24 "GlobalConfiguration": { 25 "BaseUrl": "http://localhost:5000" 26 } 27 }
如果有多個下游服務,把ReRoutes下 {...} 復制多份,最終如: "ReRoutes":[{...},{...}]
Ocelot參數說明如下,詳情查看官網(http://ocelot.readthedocs.io)
ReRoutes 路由配置
UpstreamPathTemplate 請求路徑模板
UpstreamHttpMethod 請求方法數組
DownstreamPathTemplate 下游請求地址模板
DownstreamScheme 請求協議,目前應該是支持http和https
DownstreamHostAndPorts 下游地址和端口
LoadBalancerOptions 負載均衡 RoundRobin(輪詢)/LeastConnection(最少連接數)/CookieStickySessions(相同的Sessions或Cookie發往同一個地址)/NoLoadBalancer(不使用負載)
DownstreamHostAndPorts配了兩個localhost 5011和localhost 5012用於負載均衡,負載均衡已經可以了,但沒有健康檢查,當其中一個掛了,負載可能還是會訪問這樣就會報錯,所以我們要加入Consul,我們稍后再講。
請求聚合,認證,限流,熔錯告警等查看官方配置說明
GlobalConfiguration 全局配置
BaseUrl 告訴別人網關對外暴露的域名
1.2、修改 Program.cs 代碼,讀取Ocelot.json配置,修改網關地址為 http://localhost:5000
代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Threading.Tasks; 6 using Microsoft.AspNetCore; 7 using Microsoft.AspNetCore.Hosting; 8 using Microsoft.Extensions.Configuration; 9 using Microsoft.Extensions.Logging; 10 11 namespace Snai.ApiGateway 12 { 13 public class Program 14 { 15 public static void Main(string[] args) 16 { 17 CreateWebHostBuilder(args).Build().Run(); 18 } 19 20 public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 WebHost.CreateDefaultBuilder(args) 22 .ConfigureAppConfiguration((context, builder) => 23 { 24 builder.SetBasePath(context.HostingEnvironment.ContentRootPath); 25 builder.AddJsonFile("Ocelot.json"); 26 }) 27 .UseUrls("http://localhost:5000") 28 .UseStartup<Startup>(); 29 } 30 }
1.3、修改Startup.cs代碼,注入Ocelot到容器,並使用Ocelot
代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Builder; 6 using Microsoft.AspNetCore.Hosting; 7 using Microsoft.AspNetCore.HttpsPolicy; 8 using Microsoft.AspNetCore.Mvc; 9 using Microsoft.Extensions.Configuration; 10 using Microsoft.Extensions.DependencyInjection; 11 using Microsoft.Extensions.Logging; 12 using Microsoft.Extensions.Options; 13 using Ocelot.DependencyInjection; 14 using Ocelot.Middleware; 15 16 namespace Snai.ApiGateway 17 { 18 public class Startup 19 { 20 public Startup(IConfiguration configuration) 21 { 22 Configuration = configuration; 23 } 24 25 public IConfiguration Configuration { get; } 26 27 // This method gets called by the runtime. Use this method to add services to the container. 28 public void ConfigureServices(IServiceCollection services) 29 { 30 services.AddOcelot(); 31 } 32 33 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 35 { 36 if (env.IsDevelopment()) 37 { 38 app.UseDeveloperExceptionPage(); 39 } 40 app.UseOcelot().Wait(); 41 } 42 } 43 }
最終項目結構如下:
2、搭建服務Snai.ApiServiceA,Snai.ApiServiceB
新建 Snai.ApiServiceA 基於Asp.net Core 3.0 Api網站
2.1、修改Controllers/ValuesController.cs代碼
修改路由為Ocelot 配置的下游地址 apiservice/[controller],注入appsettings.json配置實體,修改Get方法為返回讀取配置內容,其他方法可以刪除
代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Mvc; 6 using Microsoft.Extensions.Configuration; 7 8 namespace Snai.ApiServiceA.Controllers 9 { 10 [Route("apiservice/[controller]")] 11 [ApiController] 12 public class ValuesController : ControllerBase 13 { 14 public IConfiguration _configuration { get; } 15 public ValuesController(IConfiguration configuration) 16 { 17 this._configuration = configuration; 18 } 19 20 [HttpGet] 21 public string Get() 22 { 23 return HttpContext.Request.Host.Port + " " + _configuration["AppName"] + " " + DateTime.Now.ToString(); 24 } 25 } 26 }
2.2、修改appsettings.json配置,加入 "AppName": "ServiceA"
1 { 2 "Logging": { 3 "LogLevel": { 4 "Default": "Information", 5 "Microsoft": "Warning", 6 "Microsoft.Hosting.Lifetime": "Information" 7 } 8 }, 9 "AllowedHosts": "*", 10 "AppName": "ServiceA" 11 }
2.3、修改Program.cs代碼,修改該服務地址為 http://localhost:5011
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Hosting; 6 using Microsoft.Extensions.Configuration; 7 using Microsoft.Extensions.Hosting; 8 using Microsoft.Extensions.Logging; 9 10 namespace Snai.ApiServiceA 11 { 12 public class Program 13 { 14 public static void Main(string[] args) 15 { 16 CreateHostBuilder(args).Build().Run(); 17 } 18 19 public static IHostBuilder CreateHostBuilder(string[] args) => 20 Host.CreateDefaultBuilder(args) 21 .ConfigureWebHostDefaults(webBuilder => 22 { 23 webBuilder.UseUrls("http://localhost:5011"); 24 webBuilder.UseStartup<Startup>(); 25 }); 26 } 27 }
2.4、新建 Snai.ApiServiceB 基於Asp.net Core 2.0 Api網站,幾乎與Snai.ApiServiceA一樣,除了 "AppName": "ServiceB",.UseUrls("http://localhost:5012")
到此 基於Ocelot Api網關 搭建完成
3、啟動 運行 Snai.ApiServiceA,Snai.ApiServiceB,Snai.ApiGateway項目,在瀏覽器打開 http://localhost:5000/apiservice/values 地址
刷新頁面負載得到ServiceA,ServiceB返回內容。
5012沒有返回任何數據,Ocelot已內置負載均衡,但沒有健康檢查,不能踢除壞掉的服務,所以加入Consul,Consul提供服務注冊發現、健康檢查,配合Ocelot負載就能發現壞掉的服務,只負載到正常的服務上,下面介紹加入Consul。
***作者的5012是返回數據的,而我的沒有,這里說一下原因,我在Program.cs設置了啟動端口,在調試的時候並沒有生效,於是我在launchSettings.json中配置了applicationUrl屬性並將sslPort屬性設置為0關閉SSL,5012的數據就能正常返回了。
二、在Ocelot網關加入Consul,實現服務注冊發現、健康檢查
1、啟動Consul,開啟服務注冊、服務發現
首先下載Consul:https://www.consul.io/downloads.html,本項目是Windows下進行測試,得到consul.exe(我下載的壓縮包里面只有一個可執行文件。)
再下載Consul配置文件和Consul UI(配置文件適合本例Demo的,可根據具體項目修改調整):https://github.com/Liu-Alan/Ocelot-Consul/tree/master/Consul
conf:配置文件目錄
data:緩存數據目錄,可清空里面內容
dist:Consul UI,用於瀏覽器查看注冊的服務情況;如果用Consul默認自帶UI,該目錄可以刪除,Consul 啟動腳本 -ui-dir ./dist 改為 -ui
Consul支持配置文件和Api兩種方式服務注冊、服務發現,下面主要講解配置文件方式
在consul.exe同級目錄下新建conf,並創建以下json文件放入其中。
Consul 配置文件service.json配置如下:
1 { 2 "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==", 3 "services": [ 4 { 5 "id": "ApiServiceA", 6 "name": "ApiService", 7 "tags": [ "ApiServiceA" ], 8 "address": "localhost", 9 "port": 5011, 10 "checks": [ 11 { 12 "id": "ApiServiceA_Check", 13 "name": "ApiServiceA_Check", 14 "http": "http://localhost:5011/health", 15 "interval": "10s", 16 "tls_skip_verify": false, 17 "method": "GET", 18 "timeout": "1s" 19 } 20 ] 21 }, 22 { 23 "id": "ApiServiceB", 24 "name": "ApiService", 25 "tags": [ "ApiServiceB" ], 26 "address": "localhost", 27 "port": 5012, 28 "checks": [ 29 { 30 "id": "ApiServiceB_Check", 31 "name": "ApiServiceB_Check", 32 "http": "http://localhost:5012/health", 33 "interval": "10s", 34 "tls_skip_verify": false, 35 "method": "GET", 36 "timeout": "1s" 37 } 38 ] 39 } 40 ] 41 }
兩個服務ApiServiceA和ApiServiceB,跟着兩個健康檢查ApiServiceA_Check和ApiServiceB_Check
由於ApiServiceA和ApiServiceB做負載均衡,現在 "name": "ApiService" 配置一樣
修改Snai.ApiServiceA、Snai.ApiServiceB項目 加入health 健康檢查地址
打開ValuesController.cs 加入 health
代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Mvc; 6 using Microsoft.Extensions.Configuration; 7 8 namespace Snai.ApiServiceB.Controllers 9 { 10 [Route("apiservice/[controller]")] 11 [ApiController] 12 public class ValuesController : ControllerBase 13 { 14 public IConfiguration _configuration { get; } 15 public ValuesController(IConfiguration configuration) 16 { 17 this._configuration = configuration; 18 } 19 [HttpGet] 20 public string Get() 21 { 22 return HttpContext.Request.Host.Port + " " + _configuration["AppName"] + " " + DateTime.Now.ToString(); 23 } 24 25 [HttpGet("health")] 26 public async Task<IActionResult> Heathle() 27 { 28 return await Task.FromResult(Ok()); 29 } 30 } 31 }
重新生成運行項目Snai.ApiServiceA、Snai.ApiServiceB
清除Consul/data 內容,新建startup.bat文件,輸入下面代碼,雙擊啟動Consul,本項目測試時一台機器,所以把 本機IP 改成 127.0.0.1
consul agent -server -datacenter=dc1 -bootstrap -data-dir ./data -config-file ./conf -ui-dir ./dist -node=n1 -bind 本機IP -client=0.0.0.0
consul agent -server -datacenter=dc1 -bootstrap -data-dir ./data -config-file ./conf -ui -node=n1 -bind 本機IP -client=0.0.0.0
再在Consul目錄下啟動另一個cmd命令行窗口,輸入命令:consul operator raft list-peers 查看狀態查看狀態,結果如下
打開Consul UI:http://localhost:8500 查看服務情況,可以看到ApiServiceA、ApiServiceB 服務,且健康檢查都是正常的。
由於ApiServiceA、ApiServiceB是在一台機器上兩個服務做負載 所以在一個Consul里配置了兩個name一樣的服務。
如果用兩個機器做ApiServiceA負載,本機IP是192.168.0.5,另一台IP是192.168.0.6上,以本機上主Consul
本機【192.168.0.5】 Consul配置如下
1 { 2 "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==", 3 "services": [ 4 { 5 "id": "ApiServiceA", 6 "name": "ApiService", 7 "tags": [ "ApiServiceA" ], 8 "address": "192.168.0.5", 9 "port": 5011, 10 "checks": [ 11 { 12 "id": "ApiServiceA_Check", 13 "name": "ApiServiceA_Check", 14 "http": "http://192.168.0.5:5011/health", 15 "interval": "10s", 16 "tls_skip_verify": false, 17 "method": "GET", 18 "timeout": "1s" 19 } 20 ] 21 } 22 ] 23 }
把ApiServiceA和Consul拷到另一個【192.168.0.6】機器,修改Consul配置文件
1 { 2 "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==", 3 "services": [ 4 { 5 "id": "ApiServiceA", 6 "name": "ApiService", 7 "tags": [ "ApiServiceA" ], 8 "address": "192.168.0.6", 9 "port": 5011, 10 "checks": [ 11 { 12 "id": "ApiServiceA_Check", 13 "name": "ApiServiceA_Check", 14 "http": "http://192.168.0.6:5011/health", 15 "interval": "10s", 16 "tls_skip_verify": false, 17 "method": "GET", 18 "timeout": "1s" 19 } 20 ] 21 } 22 ] 23 }
修改啟動Consul腳本的IP為192.168.0.6,-node=n2,去掉 -bootstrap,啟動Consul,在Consul UI下查看服務是否正常
在192.168.0.5下,把192.168.0.6加到集群中,命令如下
consul join 192.168.0.6
注意,consul集群中,consul配置文件中的encrypt,一定要相同,否則無法放加入同一個集群
用consul operator raft list-peers查看狀態,會發現n1,n2在一個集群中了
Node ID Address State Voter RaftProtocol
n1 d02c3cd0-d9c8-705b-283e-121a9105cf52 192.168.0.5:8300 leader true 3
n2 efe954ce-9840-5c66-fa80-b9022167d782 192.168.0.6:8300 follower true 3
2、配置Ocelot,加入Consul,啟用服務健康檢查,負載均衡
2.1 打開 Snai.ApiGateway 網關下的Ocelot.json文件,加入下面配置
完整配置信息如下:
1 { 2 "ReRoutes": [ 3 { 4 "UpstreamPathTemplate": "/apiservice/{controller}", 5 "UpstreamHttpMethod": [ "Get" ], 6 "DownstreamPathTemplate": "/apiservice/{controller}", 7 "DownstreamScheme": "http", 8 "DownstreamHostAndPorts": [ 9 { 10 "host": "localhost", 11 "port": 5011 12 }, 13 { 14 "host": "localhost", 15 "port": 5012 16 } 17 ], 18 "LoadBalancerOptions": { 19 "Type": "LeastConnection" 20 }, 21 "ServiceName": "ApiService", 22 "UseServiceDiscovery": true 23 } 24 ], 25 26 "GlobalConfiguration": { 27 "BaseUrl": "http://localhost:5000", 28 "ServiceDiscoveryProvider": { 29 "Host": "localhost", 30 "Port": 8500, 31 "Type": "Consul" 32 } 33 } 34 }
ServiceName 是Cousul配置中服務的name名字
UseServiceDiscovery 是否啟用Consul服務發現
ServiceDiscoveryProvider 是Consul服務發現的地址和端口
2.2修改Startup.cs添加Consul
完整代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Builder; 6 using Microsoft.AspNetCore.Hosting; 7 using Microsoft.AspNetCore.HttpsPolicy; 8 using Microsoft.AspNetCore.Mvc; 9 using Microsoft.Extensions.Configuration; 10 using Microsoft.Extensions.DependencyInjection; 11 using Microsoft.Extensions.Logging; 12 using Microsoft.Extensions.Options; 13 using Ocelot.DependencyInjection; 14 using Ocelot.Middleware; 15 using Ocelot.Provider.Consul; 16 17 namespace Snai.ApiGateway 18 { 19 public class Startup 20 { 21 public Startup(IConfiguration configuration) 22 { 23 Configuration = configuration; 24 } 25 26 public IConfiguration Configuration { get; } 27 28 // This method gets called by the runtime. Use this method to add services to the container. 29 public void ConfigureServices(IServiceCollection services) 30 { 31 services.AddOcelot().AddConsul(); 32 } 33 34 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 36 { 37 if (env.IsDevelopment()) 38 { 39 app.UseDeveloperExceptionPage(); 40 } 41 app.UseOcelot().Wait(); 42 } 43 } 44 }
重新生成啟動Ocelot網關,到此Ocelot+Consul配置完成
三、運行測試Ocelot+Consul服務發現、負載均衡
打開 http://localhost:5000/apiservice/values 地址,刷新頁面負載得到ServiceA,ServiceB返回內容
當把ApiServiceB服務關掉,再多次刷新頁面,只能得到ServiceA的內容
打開Consul UI去看,ServiceB健康檢查失敗
Ocolot+Consul實現API網關 服務注冊、服務發現、健康檢查和負載均衡已完成
原作者源碼訪問地址:https://github.com/Liu-Alan/Ocelot-Consul