欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微服务之:从零搭建ocelot网关和consul集群

程序员文章站 2022-03-19 22:16:28
介绍 微服务中有关键的几项技术,其中网关和服务服务发现,服务注册相辅相成。 首先解释几个本次教程中需要的术语 网关 Gateway(API GW / API 网关),顾名思义,是企业 IT 在系统边界上提供给外部访问内部接口服务的统一入口,简化了外部由于多服务协同完成任务时的繁琐配置。网关组件有Ko ......

介绍

微服务之:从零搭建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 agent -server -ui -bootstrap-expect 2 -data-dir opt/consul/data -node servermaster -bind 192.168.74.55 -client 192.168.74.55
关键参数说明
-server:server模式启动
-ui :开启ui界面(consul.exe内部带了gui图形界面操作)
 -bootstrap-expect 2:server端到2个时集群生效
-data-dir:consul产生的文件路径(consul自己会产生一下数据存储的位置)
-node:此节点名称
-bind:集群内部通信地址,默认0.0.0.0
-client:此节点绑定的通讯地址
以上只是关键参数,以下是完整参数说明: 
 
 微服务之:从零搭建ocelot网关和consul集群

但是命令启动很繁琐,所以推荐下面的配置文件的方式启动

在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集群到这里就搭建成功,只要访问三台服务器任意一个都可数据同步,演示:

微服务之:从零搭建ocelot网关和consul集群

 

微服务之:从零搭建ocelot网关和consul集群

 netcore集成consul服务注册

首先新建一个consulclient的类库

微服务之:从零搭建ocelot网关和consul集群

 

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网关和consul集群

将以下依赖加入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调用信息:

微服务之:从零搭建ocelot网关和consul集群

这其实是consul进行健康检查进行的调用。

启动多个程序后,打开浏览器打开consuld界面会发现注册了两个服务

微服务之:从零搭建ocelot网关和consul集群

 

 微服务之:从零搭建ocelot网关和consul集群

微服务之:从零搭建ocelot网关和consul集群

这里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即可

项目地址:

github地址