前言
.Net Core 已经发布3.0了在最近的一两年中.NET Core的关注度持续上升, 微服务及云原生应用开发上采用.NET Core也越来越多,Ocelot 作为.NET Core平台下一款开源的API 网关开发库越来越得到社区的认可,应用到生产中的案例也很多,本文分享以下两部分内容
1、基于Ocelot搭建Api网关;2、Ocelot+Consul 实现下游服务的服务注册、服务发现、健康检查、负载均衡 参考 《.NET Core 在腾讯财付通的企业级应用开发实践》。
先给大解释下Ocelot 和 Consul到底是干啥的
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:2.1
Ocelot:7.1.0
Consul:1.1.0 github 分享地址 (https://github.com/zhangbojr/Consul.git)
项目解决方案目录
Bo.ApiGateway Asp.net Core 2.0 Api网关
Bo.ApiServiceA Asp.net Core 2.0 Api下游服务A
Bo.ApiServiceB Asp.net Core 2.0 Api下游服务B
Consul:
conf 配置目录
data 缓存数据目录,可清空里面内容
dist Consul UI目录
consul.exe 注册软件
startup.bat 执行脚本
项目实现
1、搭建Api网关
新建Bo.ApiGateway 基于Asp.net Core 2.0空网站,在 依赖项 右击 管理NuGet程序包 浏览 找到 Ocelot 版本7.1.0-unstable0011安装
1.1、在项目根目录下新建一个 Ocelot.json 文件,打开 Ocelot.json 文件,配置Ocelot参数,Ocelot.json 代码如下
{ "ReRoutes": [ { "UpstreamPathTemplate": "/apiservice/{controller}", "UpstreamHttpMethod": [ "Get" ], "DownstreamPathTemplate": "/apiservice/{controller}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "host": "localhost", "port": 5011 }, { "host": "localhost", "port": 5012 } ], "LoadBalancerOptions": { "Type": "LeastConnection" } } ], "GlobalConfiguration": { "BaseUrl": "http://localhost:5000" } }
如果有多个下游服务,把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
代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Bo.ApiGateway { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { builder.SetBasePath(context.HostingEnvironment.ContentRootPath) .AddJsonFile("Ocelot.json"); }).UseUrls("http://localhost:5000") .UseStartup<Startup>(); } }
1.3、修改Startup.cs代码,注入Ocelot到容器,并使用Ocelot
代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; using Ocelot.Middleware; namespace Bo.ApiGateway { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddOcelot(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseOcelot().Wait(); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
2、搭建服务Bo.ServiceA,Bo.ServiceB 基于Asp.net Core 2.0 Api网站
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; namespace Bo.ServiceA.Controllers { [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { public IConfiguration Configuration { get; } public ValuesController(IConfiguration configuration) { Configuration = configuration; } [HttpGet] public string Get() { return HttpContext.Request.Host.Port + " " + Configuration["AppName"] + " " + DateTime.Now.ToString(); } [HttpGet("/health")] public IActionResult Heathle() { return Ok(); } // GET api/values // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } }
2.2、修改appsettings.json配置,加入 "AppName": "ServiceA"
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "AppName": "ServiceA" }
2.3、修改Program.cs代码,修改该服务地址为 http://localhost:5011
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Bo.ServiceA { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseUrls("http://192.168.2.6:5011") .UseStartup<Startup>(); } }
2.4、新建Bo.ServiceB 基于Asp.net Core 2.0 Api网站,几乎与ServiceA一样,除了 "AppName": "ServiceB",.UseUrls("http://localhost:5012")
3、启动 运行Bo.ServiceA,Bo.ServiceB,Bo.ApiGateway项目,在浏览器打开 http://localhost:5000/apiservice/values 地址
Ocelot已内置负载均衡,但没有健康检查,不能踢除坏掉的服务,所以加入Consul,Consul提供服务注册发现、健康检查,配合Ocelot负载就能发现坏掉的服务,只负载到正常的服务上,下面介绍加入Consul。
二、在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 配置文件service.json配置如下:
{ "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==", "services": [ { "id": "ApiServiceA", "name": "MyService", "tags": ["ApiServiceA"], "address": "192.168.2.6", "port": 5011, "checks": [ { "id": "CK A 5011", "name": "CK A 5011", "http": "http://192.168.2.6:5011/health", "interval": "5s", "tls_skip_verify": false, "method": "GET", "timeout": "1s" } ] } ] }
打开ValuesController.cs 加入 health
重新生成运行项目Bo.ServiceA,Bo.ServiceB
清除Consul/data 内容,新建startup.bat文件,输入下面代码,双击启动Consul,本项目测试时一台机器,所以把 本机IP 改成 192.168.2.6
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目录下启动另一个cmd命令行窗口,输入命令:consul operator raft list-peers 查看状态查看状态,结果如下
由于ServiceA、ServiceB是在一台机器上两个服务做负载 所以在一个Consul里配置了两个name一样的服务。
如果用两个机器做ServiceA负载,本机IP是192.168.2.6,另一台IP是192.168.2.180上,以本机上主Consul
把ServiceB和Consul拷到另一个192.168.2.180 机器,修改Consul配置文件
修改启动Consul脚本的IP为192.168.2.180,-node=n2,去掉 -bootstrap,启动Consul,在Consul UI下查看服务是否正常
在192.168.2.6下,把192.168.2.180加到集群中,命令如下
consul join 192.168.2.180
注意,consul集群中,consul配置文件中的encrypt,一定要相同,否则无法放加入同一个集群
用consul operator raft list-peers查看状态,会发现n1,n2在一个集群中了
2、配置Ocelot,加入Consul,启用服务健康检查,负载均衡
打开 Snai.ApiGateway 网关下的Ocelot.json文件,加入下面配置
ServiceName 是Cousul配置中服务的name名字
UseServiceDiscovery 是否启用Consul服务发现
ServiceDiscoveryProvider 是Consul服务发现的地址和端口
重新生成启动Ocelot网关,到此Ocelot+Consul配置完成
三、运行测试Ocelot+Consul服务发现、负载均衡
打开 http://localhost:5000/api/values 地址,刷新页面负载得到ServiceA,ServiceB返回内容
当把ServiceB服务关掉,再多次刷新页面,只能得到ServiceA的内容
源码地址:(https://github.com/zhangbojr/.Net-core-Ocelot-Consul.git)