Ocelot+Consul 集群搭建实践

转载:Ocelot+Consul 集群搭建实践

2019-10-19 15:52  糯米粥  阅读(1754)  评论(0)  编辑  收藏  举报

博客园已经有很多大神写过consul集群搭建了。大家都在玩,那我也不能托后退呢

不过自己研究下还是好的。毕竟每个人遇到的问题的不同

研究过才能说自己玩过consul,文章有部分名词解释是收集网络

Consul 官网:https://www.consul.io/

帮助文档:https://www.consul.io/docs/upgrading.html

Consul是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个一个分布式的,高度可用的系统,
而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。

 

具体的理论在网上都能找到,我这里就不描述了。主要写部署的实践过程

 

 

我看了网上大部分都是通过一台机器来模拟的集群,我打算通过多台服务器来进行集群搭建

这样才有实战效果,因为官方推荐用3台做server,所以用3台server只部署server,不部署其他程序

conusl集群通信中有很多端口:

复制代码
8300:TCP # Server RPC :agent server 使用的,用于处理其他agent发来的请求
8301:TCP and UDP # Serf LAN: 所有的agent都要使用这个端口,用于处理LAN中的gossip,
8302:TCP and UDP # Serf WAN:agent Server使用的,处理WAN中的与其他server的gossip
8400:TCP # 所有agent都要使用的端口,用于处理从CLI来的RPC。
8500:TCP # 所有agent都要使用的端口,用于处理HTTP API。
8600:TCP and UDP # 用于处理 DNS 查询。
复制代码

 所以需要开通的端口有:8301/tcp 8301/udp 8302/tcp  8302/udp 8300/tcp 8500/tcp  8600/udp   如果我写漏了。自己补上

如果是阿里云服务,需要登陆阿里云配置本地安全组

一些相关命令:

firewall-cmd --list-ports #查看已开放的端口
firewall-cmd --reload # 重启防火墙,刚开通的端口才会生效
firewall-cmd --query-port=80/tcp #查询80端口开通状态
firewall-cmd --add-port=80/udp --permanent   #开通80端口udp协议

 

名词概念

client:

    CLIENT表示consul的client模式,就是客户端模式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到SERVER,本身是不持久化这些信息。

server:

       SERVER表示consul的server模式,表明这个consul是个server,这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。

server-leader:

      中间那个SERVER下面有LEADER的字眼,表明这个SERVER是它们的老大,它和其它SERVER不一样的一点是,它需要负责同步注册的信息给其它的SERVER,同时也要负责各个节点的健康监测。

raft:

   server节点之间的数据一致性保证,一致性协议使用的是raft,而zookeeper用的paxos,etcd采用的也是taft。

    服务发现协议: consul采用http和dns协议,etcd只支持http

    服务注册: consul支持两种方式实现服务注册,一种是通过consul的服务注册http API,由服务自己调用API实现注册,另一种方式是通过json个是的配置文件实现注册,将需要注册的服务以json格式的配置文件给出。consul官方建议使用第二种方式。

    服务发现: consul支持两种方式实现服务发现,一种是通过http API来查询有哪些服务,另外一种是通过consul agent 自带的DNS(8600端口),域名是以NAME.service.consul的形式给出,NAME即在定义的服务配置文件中,服务的名称。DNS方式可以通过check的方式检查服务。

    服务间的通信协议: Consul使用gossip协议管理成员关系、广播消息到整个集群,他有两个gossip pool(LAN pool和WAN pool),LAN pool是同一个数据中心内部通信的,WAN pool是多个数据中心通信的,LAN pool有多个,WAN pool只有一个。

    LAN Gossip——它包含所有位于同一个局域网或者数据中心的所有节点。

    WAN Gossip——它只包含Server。这些server主要分布在不同的数据中心并且通常通过因特网或者广域网通信。

    RPC——远程过程调用。这是一个允许client请求server的请求/响应机制。

 

简单来说就是:client相当于我们平时说的LB,负责将请求转发到Server,Server中有一个leader,负责Server集群的同步和监测,
这个server-leader在不指定的情况下回随机推举出一个,当然也可以手动指定。这个在ACL配置的时候需要保证Server-leader是同一个。

 

参数解释

1、命令参数

 View Code

2、配置参数

 View Code

摘自:https://www.cnblogs.com/qidakang/p/7837519.html

 View Code

 

现在手中资源有:4台linux和2台windows server

Linux:

   43.243.49.55

   43.243.49.60

  103.250.12.20

   39.105.144.51(阿里云)

windows:

   43.243.49.57

  129.28.195.120(腾讯云)

 

分配:

service服务器:(43.243.49.55,43.243.49.60,103.250.12.20)

client:(129.28.195.120)

api部署两个,用于测试负载:(43.243.49.57,39.105.144.51)

 

linux上我都是基于docker部署,毕竟docker方便

首先部署所有的server端

43.243.49.55上面执行:

docker run --name s1 -d --restart=always -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8301:8301/udp -p 8302:8302/udp -p 8600:8600 
consul agent -server -node s1 -bootstrap-expect 3 -advertise 43.243.49.55 --bind 0.0.0.0 -client=0.0.0.0 -ui -datacenter=dc-test

 

通过docker s1 logs 可以看到consul 已经运行成功,但提示没有leader,因为设置了,-bootstrap-expect 3

这个集群需要3台server,所以要等3台server加如集群后才会选举一个leader

 

 

 

 

 现在可以尝试访问:http://43.243.49.55:8500  会出现异常,因为集群中没有3台server,没有leader

 

 

 

 

43.243.49.60上面执行:

docker run --name s1 -d --restart=always -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8301:8301/udp -p 8302:8302/udp -p 8600:8600 
consul agent -server -node s2 -bootstrap-expect 3 -advertise 43.243.49.60  --bind 0.0.0.0 -client=0.0.0.0 -join 43.243.49.55  -datacenter=dc-test

注意上面的-join 43.243.49.55,是加入集群,这里没有-ui ,因为我不想把60开通ui功能,具体根据自己需求来

如果执行命令的时候,没有join。也就是没有加入任何集群,如果想加入集群,可以手动加入: docker exec -t s1 consul join 43.243.49.55 

 

 

 

 

 

103.250.12.20上面执行:

docker run --name s1 -d --restart=always -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8301:8301/udp -p 8302:8302/udp 
-p 8600:8600 consul agent -server -node s3 -bootstrap-expect 3 -advertise 103.250.12.20 --bind 0.0.0.0 -client=0.0.0.0 -join 43.243.49.55 -datacenter=dc-test

 

这样3台server都已经部署完成,通过docker logs s1 (任意一台server都可以)可以看到已经选举了leader为s1节点,s2和s3为跟随节点Follower

 

 

 

通过命令: docker exec -t s1 consul members 可以查看集群节点信息,Status状态正常,Type为server

 

 

 

通过: docker exec -t s1 consul operator raft list-peers 查看集群信息,可以看到集群中的leader为note为s1的节点,ip为:43.243.49.55

 

 

 

还有很多命令比如: docker exec -t s1 consul members -detailed 

 

 

 

因为3台server,只有43.243.49.55开通了ui,尝试访问,集群部署成功

 

 

 

 

 3台节点也正常

 

 

 

 接下来搭建client端

因为129.28.195.120是windows server,所以不能跑docker

首先下载consul是一个exe运行程序

 

 

 

 打开cmd,切换到当前目录,执行命令:

consul agent -ui -advertise 129.28.195.120 -bind=0.0.0.0 -client=0.0.0.0 
-data-dir C:\consul -config-dir=C:\consul\conf -node=c1 -join 43.243.49.55 -datacenter=dc-test
-data-dir是当前consul路径,
-config-dir是配置文件,也就是配置服务的,后面会说

 

 

 

 

 

 执行成功了,再一次查看集群信息。多了一个client

 

 

 

 

 

 

 因为 129.28.195.120开通了ui,所以同样可以访问

 

 

 

 好了。接下来3个server和1个client的集群就搭建成功了,接下来就是服务的注册了

服务注册有2种方式:

1:直接配置文件的方式,也就是上面-config-dir的目录

2:通过代码http调用consul接口方式注册

 -config-dir是上面部署widows的方式

如果是docker 跑client,需要挂载文件

--数据挂载方式
/consul/data:持久化数据存储
/consul/config:配置文件

可以这样:

docker run -d -p 8500:8500 --restart=always -v=/volume/consul/data:/consul/data -v=/volume/consul/config:/consul/config --name c2 
consul agent -ui -bind=0.0.0.0 -client 0.0.0.0 -node=c2 -join 43.243.49.55 -datacenter=dc-test

 

 那么写几个api试试,既然有2中方式注册服务,那么都试试,

还有,安装上面的计划,分别在43.243.49.57,39.105.144.51上部署,因为都是linux,我依然选择用docker方式

准备工作

待会为了测试在不同的服务器跑负载,我这里创建2个api,分别用不同方式注册,来比较

1:Order.Api(用配置文件的方式注册)

2:User.Api(用代码方式注册)

 

 

 

2个api分别添加检查检查方法,该方法返回ok。说明服务器没有挂掉

 

 

 

 

 为了区分不同的服务器,2个api分别添加返回当前服务器ip地址的方法,仅仅是为了测试,不用过多纠结

 

 

 

 

User.Api添加配置文件:这里填写client地址,当然你填写任意一台server地址也是可以的

看到网上有人提问说,只有一个client,如果这个client挂了。那不就注册失败了吗?怎么解决,

我认为这担心是多余的。挂了肯定会注册失败,client挂了。修复即可,

就好比我们一个项目部署在服务器,如果服务器不挂,你也不会老是担心,如果服务器挂了怎么办吧,比如,机房断电。线路故障

这些也只能等待修复,如果硬是担心。那就只能部署多个client,然后轮询判断??

复制代码
{
  "ServiceDiscovery": {
    "ServiceName": "UserService",
    "Consul": {
      "HttpEndpoint": "http://129.28.195.120:8500",
      "DnsEndpoint": {
        "Address": "129.28.195.120",
        "Port": 8600
      }
    }
  }
}
复制代码

然后配置对应的实体类,我这里就不写了

 

 在User.Api 添加consul包

 

 

 在ConfigureServicesz中配置如下:

复制代码
  public void ConfigureServices(IServiceCollection services)
        {
                 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //services.AddOptions();
            services.Configure<ServiceDiscoveryOptions>(Configuration.GetSection("ServiceDiscovery"));

            services.AddSingleton<IConsulClient>(p => new ConsulClient(cfg =>
            {
                var serviceConfiguration = p.GetRequiredService<IOptions<ServiceDiscoveryOptions>>().Value;

                if (!string.IsNullOrEmpty(serviceConfiguration.Consul.HttpEndpoint))
                {
                    // if not configured, the client will use the default value "127.0.0.1:8500"
                    cfg.Address = new Uri(serviceConfiguration.Consul.HttpEndpoint);
                }
            }));

        }
复制代码

Configure配置如下:

复制代码
 public void Configure(IApplicationBuilder app, IHostingEnvironment env,
            IApplicationLifetime lifetime,
        ILoggerFactory loggerFactory,
        IOptions<ServiceDiscoveryOptions> serviceOptions,
        IConsulClient consul)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            //app.UseHttpsRedirection();
            app.UseMvc();

            //InitTable(app);

            lifetime.ApplicationStarted.Register(() =>
            {
                RegisterService(app, lifetime, serviceOptions, consul);
            });
            //lifetime.ApplicationStopped.Register(OnStoped);
        }
复制代码

 

然后是RegisterService注册方法:

复制代码
 private void RegisterService(IApplicationBuilder app, IApplicationLifetime appLife,
     IOptions<ServiceDiscoveryOptions> serviceOptions,
     IConsulClient consul)
        {
            var features = app.Properties["server.Features"] as FeatureCollection;
            var addresses = features.Get<IServerAddressesFeature>()
                .Addresses
                .Select(p => new Uri(p));

            foreach (var address in addresses)
            {
                var serviceId = $"{serviceOptions.Value.ServiceName}_{address.Host}:{address.Port}";

                //健康检查
                var httpCheck = new AgentServiceCheck()
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1),
                    Interval = TimeSpan.FromSeconds(30),
                    HTTP = new Uri(address, "api/HealthCheck").OriginalString
                };

                //服务注册
                var registration = new AgentServiceRegistration()
                {
                    Checks = new[] { httpCheck },
                    /*
                     测试的时候用 的localhost
                     那么在DnsClient取值的时候
                     AddressList会为空,不过HostName有值
                     */
                    Address = address.Host, // "127.0.0.1",
                    ID = serviceId,
                    Name = serviceOptions.Value.ServiceName,
                    Port = address.Port
                };

                consul.Agent.ServiceRegister(registration).GetAwaiter().GetResult();

                appLife.ApplicationStopping.Register(() =>
                {
                    consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult();
                });
            }
        }
复制代码

 

因为是边写边测试,所以先把User.Api 放到 服务器,43.243.49.57上试试效果,用Kestrel跑起来测试

执行命令: dotnet User.Api.dll --urls http://43.243.49.57:5001  当然5001端口得开通

 

运行成功后,可以看到服务注册成功

 

 一切正常

 

 

直接访问试试

 

稍后把Order.Api直接用配置文件的方式注册,不过在这之前,讲讲服务的发现,

上面就是consul服务的注册,那么看看服务发现该怎么做。

上面出现了8500和8600端口,8500是ui界面的。8600是DNS其实就是一个是注册服务,一个是服务发现

 

 

创建一个Api:SD.Api (ServiceDiscovery简称)

服务发现需要依赖包:

 

 

 ConfigureServices配置

 View Code

 

 创建Userservice控制器

 View Code

 

 运行执行成功

 

 截至目前服务的注册和发现已经介绍完成了,那么最后来看看配置方式:

现在只有43.243.49.57这台服务器部署了User.Api

接下来的工作是

1:把Order.Api也部署在43.243.49.57上。用配置文件的方式注册服务

2:在39.105.144.51上用docker部署User.Api和Order.Api,用配置文件的方式注册服务

既然是docker。就必须要写dockerfile,既然是多个一起跑。就免不了docker-compose

这里就不放dockerfile了。后面源码会有,

直接放docker-compose

复制代码
version: '3'

services:
  order.api:
    build: 
      context: Order.Api
      dockerfile: dockerfile
    container_name: 'orderapi'
    restart: always
    volumes:
      - /volume/api/OrderApi/appsettings.json:/app/appsettings.json
    ports:
      - '6002:80'
  user.api:
    build: 
      context: User.Api
      dockerfile: dockerfile
    container_name: 'userapi'
    restart: always
    volumes:
      - /volume/api/UserApi/appsettings.json:/app/appsettings.json
    ports:
      - '6001:80'
复制代码
这里注意下:
数据挂载:appsettings.json 要提前创建,因为是映射的文件,如果当前不存在,会变成文件夹
平常我们一个项目打包的时候,一般都是docker-compose.yml和dockerfile在同一个目录的,
但这里是打包多个项目,要区分好路径,我这里的docker-compose.yml是在项目最外层的


dockerfile:是项目的dockerfile 路径
context:是上下文的路径 所以 dockerfile 必须在 User.Api下 即:User.Api/dockerfile 就是当前项目下 的路径

 

当我docker-compose up 的时候遇到了一个错误

 

 

 

大体意思就是地址无效,

如果大家是跟着上面来的。应该会记得,我在演示User.Api项目的时候是通过代码调用consul http api 注册服务的,

部署在了43.243.49.57上,我并没有把代码注释掉,当然,我数据挂载也是配置了文件的client注册地址的

 

 

 

 

既然遇到了,那么我们就一起来解决下,本来是想在39.105.144.51上的api都是通过配置注册的。

那就这样吧:39.105.144.51上的api,userapi通过http api注册,orderapi通过服务方式

因为在http api注册的时候,是通过获取程序运行的ip和端口注册的

 

 

 我们在43.243.49.57 上跑userapi  是这样的,还记得吧 dotnet User.Api.dll --urls http://43.243.49.57:5001

当你没有指定程序运行的ip和端口肯定就是无效的,而抛出异常

这里要说明下:userapi只是注册失败,并不代表程序没跑起来

 

 

 访问也是成功的

 

 

 只是服务注册失败

 

 

 

 

尝试过几种方法都不行

比如:UseUrls("http://39.105.144.51:90")会提示 Cannot assign requested address,查了下资料好像是linux分配地址问题

当然这样就更不行了 UseUrls("http://*:6001")外网是可以访问,但注册是不行的

 

既然这样。那就不折腾了。都通过过配置的方式吧

用docker部署userapi和orderapi后,编写配置文件

复制代码
{
    "services":[
        {
            "ID":"UserService_39.105.144.51:6001",
            "Name":"UserService",
            "Tags":[
                "UserApi",
                "获取用户信息"
            ],
            "Address":"39.105.144.51",
            "Port":6001,
            "Check":{
                "HTTP":"http://39.105.144.51:6001/api/HealthCheck",
                "Interval":"10s",
                "DeregisterCriticalServiceAfter":"1m"
            }
        },
        {
            "id":"OrderService_39.105.144.51:6002",
            "name":"OrderService",
            "tags":[
                "获取订单信息"
            ],
            "Address":"39.105.144.51",
            "Port":6002,
            "Check":[
                {
                    "HTTP":"http://39.105.144.51:6002/api/HealthCheck",
                    "Interval":"10s",
                    "DeregisterCriticalServiceAfter":"1m"
                }
            ]
        }
    ]
}
复制代码

129.28.195.120是client,上面配置过
-config-dir=C:\consul\conf 

那么就把这个json文件放到这个目录下,里面可以多个,

比如:order.json user.json  这样就可以根据名字来区分服务,修改起来也方便,consul启动的时候,会读取所有json文件

 

 

 

重启consul,查看webui页面

 

 

 

单击UserService进去,可以看到注册了2个服务,分别在不通的机器上

 

 

 这样一个完整的consul集群就搭建好了

网关这一块 Gateway 用ocelot

添加nuget包:Ocelot.Provider.Consul

它包含了Consul,Ocelot,Ocelot.Provider.Consul

如果你是单独安装了Ocelot,那么就还得安装Ocelot.Provider.Consul

 

 

 创建一个Ocelot.json,写入下面配置

测试下负载均衡,因为userapi有2台,所以配置2个

 

复制代码
{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/users",
      "DownstreamScheme": "http",
      "LoadBalancerOptions": {
        "Type": "LeastConnection"
      },
      "DownstreamHostAndPorts": [
        {
          "Host": "39.105.144.51",
          "Port": 6001
        },
        {
          "Host": "43.243.49.57",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/users",
      "UpstreamHttpMethod": [ "Get" ]
      //"AuthenticationOptions": {
      //  "AuthenticationProviderKey": "TestKey",
      //  "AllowedScopes": []
      //}
    }
  ],
  "GlobalConfiguration": {
    //这是暴露给外部的url
    "BaseUrl": "http://localhost"
  }
}
复制代码

 

 

然后注册:

services.AddOcelot().AddConsul();

 app.UseOcelot();

加载Ocelot.json配置,暴露81端口

 

 

 

运行测试,输入 http://localhost:81/users 多次刷新访问

会在2台电脑之前切换访问,达到了负载均衡的效果

 

 

 

 

 

 源码:

https://github.com/byniqing/ConsulExample

 

Ocelot知识远不止这些,这里就不说了。具体自己看文档吧,

参考:

http://www.jessetalk.cn/2018/03/19/net-core-apigateway-ocelot-docs/

https://www.cnblogs.com/bossma/p/9756809.html

https://blog.csdn.net/weixin_33953249/article/details/88759709

https://www.cnblogs.com/RainingNight/p/servicediscovery-consul-in-asp-net-core.html

posted @ 2021-07-04 18:46  竹林听雨行  阅读(119)  评论(0编辑  收藏  举报