微服务之:从零搭建ocelot网关和consul集群
介绍
微服务中有关键的几项技术,其中网关和服务服务发现,服务注册相辅相成。
首先解释几个本次教程中需要的术语
网关 gateway(api gw / api 网关),顾名思义,是企业 it 在系统边界上提供给外部访问内部接口服务的统一入口,简化了外部由于多服务协同完成任务时的繁琐配置。网关组件有kong,ocelot,
服务发现:通过网关访问内部各个微服务,网关要找到所需服务的过程称为服务发现
服务注册:既然有服务发现,前提是要把所需服务提前“录入”,这个录入的过程称为服务注册。服务注册可配置文件(人肉方式不推荐),也可用服务注册组件如consul或者eureka等等(推荐)
搭建consul集群(windows)
官网下载consul程序,https://www.consul.io/downloads.html
下载下来就是一个可执行文件consul.exe
consul有两种代理模式,一种server,一种client,官方建议server端达到3台以上才可高可用,但不要太多,太多会给集群间数据同步造成压力,client数量不限。
多个server端之间会选择出一个leader,当一个server的leader宕机则会从其他server端”投票“选择新的leader
实践
这里server我们用2台实验
192.168.74.55
192.168.74.54
1台client
192.168.74.161
consul启动有两种方式一种是命令行,一种是配置文件的方式。
命令行方式启动一个consul的server端
但是命令启动很繁琐,所以推荐下面的配置文件的方式启动
在consul同文件夹下建立一个server.json的配置文件
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-server01", "server": true, "bootstrap_expect": 2, "bind_addr": "192.168.74.55", "client_addr": "192.168.74.55", "ui":true }
为了快速启动,再建立一个bat批处理文件runconsul.bat
consul agent -config-dir server.json pause
双击runconsul.bat启动consul
在192.168.74.54服务器开启一个server端继续以上操作。
命令方式启动
consul agent -server -ui -data-dir opt/consul/data -node server01 -bind 192.168.74.54 -client 192.168.74.54 -join=192.168.74.55
-join将192.168.74.54加入到192.168.74.55服务器
配置文件方式:
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-server2", "server": true, "bind_addr": "192.168.74.54", "client_addr": "192.168.74.54", "ui":true, "retry_join": ["192.168.74.55"], "retry_interval": "30s", "rejoin_after_leave": true, "start_join":["192.168.74.55"] }
在192.168.74.161服务器开启一个consul的client端
命令方式:
consul agent -ui -data-dir opt/consul/data -node serverslave -bind 192.168.74.161 -client 192.168.74.161 -join 192.168.74.55
配置文件方式:
{ "datacenter": "dc1", "data_dir": "opt/consul/data", "node_name": "consul-client01", "server": false, "bind_addr": "192.168.74.161", "client_addr": "192.168.74.161", "ui":true, "retry_join": ["192.168.74.55"], "retry_interval": "30s", "rejoin_after_leave": true, "start_join":["192.168.74.55"] }
效果
简单consul集群到这里就搭建成功,只要访问三台服务器任意一个都可数据同步,演示:
netcore集成consul服务注册
首先新建一个consulclient的类库
consulregister.csproj所需组件如下:
<project sdk="microsoft.net.sdk"> <propertygroup> <targetframework>netstandard2.0</targetframework> </propertygroup> <itemgroup> <packagereference include="consul" version="0.7.2.6" /> <packagereference include="microsoft.aspnetcore.hosting.abstractions" version="2.1.0" /> <packagereference include="microsoft.aspnetcore.http.abstractions" version="2.1.0" /> <packagereference include="microsoft.extensions.configuration.abstractions" version="2.1.0" /> <packagereference include="microsoft.extensions.dependencyinjection.abstractions" version="2.1.0" /> <packagereference include="microsoft.extensions.options.configurationextensions" version="2.1.0" /> </itemgroup> </project>
服务发现自动注册,无需手动绑定本机地址,会自动扫描本地ipv4地址和localhost地址,项目中无需再手动创建健康检查接口
servicediscoveryoptions.cs using system; using system.collections.generic; using system.text; namespace consulregister { /// <summary> /// 服务治理第三方组件consul相关配置参数 /// </summary> public class servicediscoveryoptions { public string servicename { get; set; } public consuloptions consul { get; set; } } public class consuloptions { public string httpendpoint { get; set; } } }
registertoconsulextension.cs using consul; using microsoft.aspnetcore.builder; using microsoft.aspnetcore.hosting; using microsoft.aspnetcore.hosting.server.features; using microsoft.aspnetcore.http; using microsoft.aspnetcore.http.features; using microsoft.extensions.configuration; using microsoft.extensions.dependencyinjection; using microsoft.extensions.options; using system; using system.linq; using system.net; using system.net.networkinformation; using system.net.sockets; namespace consulregister { public static class registertoconsulextension { /// <summary> /// add consul /// 添加consul /// </summary> /// <param name="services"></param> /// <param name="configuration"></param> /// <returns></returns> public static iservicecollection addconsul(this iservicecollection services, iconfiguration configuration) { // configuration consul register address //配置consul注册地址 services.configure<servicediscoveryoptions>(configuration.getsection("servicediscovery")); //configuration consul client //配置consul客户端 services.addsingleton<iconsulclient>(sp => new consul.consulclient(config => { var consuloptions = sp.getrequiredservice<ioptions<servicediscoveryoptions>>().value; if (!string.isnullorwhitespace(consuloptions.consul.httpendpoint)) { config.address = new uri(consuloptions.consul.httpendpoint); } })); return services; } /// <summary> /// use consul /// 使用consul /// the default health check interface format is http://host:port/healthcheck /// 默认的健康检查接口格式是 http://host:port/healthcheck /// </summary> /// <param name="app"></param> /// <returns></returns> public static iapplicationbuilder useconsul(this iapplicationbuilder app) { iconsulclient consul = app.applicationservices.getrequiredservice<iconsulclient>(); iapplicationlifetime applife = app.applicationservices.getrequiredservice<iapplicationlifetime>(); ioptions<servicediscoveryoptions> serviceoptions = app.applicationservices.getrequiredservice<ioptions<servicediscoveryoptions>>(); var features = app.properties["server.features"] as featurecollection; var port = new uri(features.get<iserveraddressesfeature>() .addresses .firstordefault()).port; console.foregroundcolor = consolecolor.blue; console.writeline($"application port is :{port}"); var addressipv4hosts = networkinterface.getallnetworkinterfaces() .orderbydescending(c => c.speed) .where(c => c.networkinterfacetype != networkinterfacetype.loopback && c.operationalstatus == operationalstatus.up); foreach (var item in addressipv4hosts) { var props = item.getipproperties(); //this is ip for ipv4 //这是ipv4的ip地址 var firstipv4address = props.unicastaddresses .where(c => c.address.addressfamily == addressfamily.internetwork) .select(c => c.address) .firstordefault().tostring(); var serviceid = $"{serviceoptions.value.servicename}_{firstipv4address}:{port}"; var httpcheck = new agentservicecheck() { deregistercriticalserviceafter = timespan.fromminutes(1), interval = timespan.fromseconds(30), //this is default health check interface //这个是默认健康检查接口 http = $"{uri.urischemehttp}://{firstipv4address}:{port}/healthcheck", }; var registration = new agentserviceregistration() { checks = new[] { httpcheck }, address = firstipv4address.tostring(), id = serviceid, name = serviceoptions.value.servicename, port = port }; consul.agent.serviceregister(registration).getawaiter().getresult(); //send consul request after service stop //当服务停止后想consul发送的请求 applife.applicationstopping.register(() => { consul.agent.servicederegister(serviceid).getawaiter().getresult(); }); console.foregroundcolor = consolecolor.blue; console.writeline($"health check service:{httpcheck.http}"); } //register localhost address //注册本地地址 var localhostregistration = new agentserviceregistration() { checks = new[] { new agentservicecheck() { deregistercriticalserviceafter = timespan.fromminutes(1), interval = timespan.fromseconds(30), http = $"{uri.urischemehttp}://localhost:{port}/healthcheck", } }, address = "localhost", id = $"{serviceoptions.value.servicename}_localhost:{port}", name = serviceoptions.value.servicename, port = port }; consul.agent.serviceregister(localhostregistration).getawaiter().getresult(); //send consul request after service stop //当服务停止后想consul发送的请求 applife.applicationstopping.register(() => { consul.agent.servicederegister(localhostregistration.id).getawaiter().getresult(); }); app.map("/healthcheck", s => { s.run(async context => { await context.response.writeasync("ok"); }); }); return app; } } }
再新建一个.netcore的webapi项目weba,并且引用consulregister项目
在weba项目中的startup.cs文件中加入consul服务
public void configureservices(iservicecollection services) { services.addconsul(configuration); services.addmvc().setcompatibilityversion(compatibilityversion.version_2_1); } // 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.useconsul(); app.usemvc(); }
在weba项目的appsettings.json配置文件中加入以下consul服务端配置
{ "logging": { "loglevel": { "default": "warning" } }, "allowedhosts": "*", "servicediscovery": { "servicename": "a", "consul": { "httpendpoint": "http://192.168.74.161:8500" } } }
这里服务注册就算完成
ocelot网关搭建
接下来继续ocelot借助于consul实现服务发现
新建项目ocelot.gateway
将以下依赖加入ocelot.gateway.csproj中:
<project sdk="microsoft.net.sdk.web"> <propertygroup> <targetframework>netcoreapp2.1</targetframework> </propertygroup> <itemgroup> <packagereference include="microsoft.aspnetcore.app" /> <packagereference include="ocelot" version="12.0.1" /> <packagereference include="ocelot.provider.consul" version="0.1.2" /> </itemgroup> <itemgroup> <content update="ocelot.json"> <copytooutputdirectory>preservenewest</copytooutputdirectory> </content> </itemgroup> </project>
新建ocelot.json文件
{ "reroutes": [ { "useservicediscovery": true, "downstreampathtemplate": "/{url}", "downstreamscheme": "http", "servicename": "a", "loadbalanceroptions": { "type": "roundrobin" }, "upstreampathtemplate": "/a/{url}", "upstreamhttpmethod": [ "get", "post" ], "reroutescasesensitive": false } ], "globalconfiguration": { // 使用consul服务治理 "servicediscoveryprovider": { "host": "192.168.74.161", "port": 8500, "configurationkey": "oceolot_a" //存储在consul上的key } } }
修改startup.cs文件如下:
public class startup { public startup(iconfiguration configuration) { configuration = configuration; } public iconfiguration configuration { get; } // this method gets called by the runtime. use this method to add services to the container. public void configureservices(iservicecollection services) { services.addmvc().setcompatibilityversion(compatibilityversion.version_2_1); services.addocelot( new configurationbuilder() .addjsonfile("ocelot.json", optional: false, reloadonchange: true).build()) .addconsul() .addconfigstoredinconsul(); } // 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(); } else { app.usehsts(); } app.usehttpsredirection(); app.useocelot().wait(); } }
发布weba后复制两份分别启动
dotnet weba.dll --urls="http://0.0.0.0:2001"
dotnet weba.dll --urls="http://0.0.0.0:2002"
到这里相当于2001和2002程序简单集群了一下
可以发现日志中有 http://192.168.74.161:2002/healthcheck调用信息:
这其实是consul进行健康检查进行的调用。
启动多个程序后,打开浏览器打开consuld界面会发现注册了两个服务
这里ocelot网关和consul的服务注册和发现就算初步集成。
如果生成环境是windows的情况,将consul做成windwos服务即可
sc create "consulserver" binpath="f:\xxx\consul.exe agent -config-dir xxx.json"
生产环境是linux则借助systemd做成守护进程即可
目前集群搭建成功,但是连接的话如果指定某个端点的ip进行连接,端点宕机,就会导致网关一样无法连接consul进行服务发现。所以还需进行配置暴露一个端点让客户端连接,配置详情:https://www.consul.io/docs/connect/configuration.html
不过也可以做成虚拟ip进行多台consul的负载。客户端连接虚拟ip即可
项目地址: