基於Docker的Consul服務發現集群搭建


在去年的.NET Core微服務系列文章中,初步學習了一下Consul服務發現,總結了兩篇文章。本次基於Docker部署的方式,以一個Demo示例來搭建一個Consul的示例集群,最后給出一個HA的架構示范,也會更加貼近於實際應用環境。

一、示例整體架構

  此示例會由一個API Gateway, 一個Consul Client以及三個Consul Server組成,有關Consul的Client和Server這兩種模式的Agent的背景知識,請移步我之前的文章加以了解:《.NET Core微服務之基於Consul實現服務治理》。其中,Consul的Client和Server節點共同構成一個Data Center,而API Gateway則從Consul中獲取到服務的IP和端口號,並返回給服務消費者。這里的API Gateway是基於Ocelot來實現的,它不是這里的重點,也就不過多說明了,不了解的朋友請移步我的另一篇:《.NET Core微服務之基於Ocelot實現API網關服務》。

二、Consul集群搭建

2.1 Consul鏡像拉取

docker pull consul:1.4.4

  驗證:docker images

  

2.2 Consul Server實例創建

  以下我的實踐是在一台機器上(CentOS 7)操作的,因此將三個實例分別使用了不同的端口號(區別於默認端口號8500)。實際環境中,建議多台機器部署。

  (1)Consul實例1

docker run -d -p 8510:8500 --restart=always -v /XiLife/consul/data/server1:/consul/data -v /XiLife/consul/conf/server1:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_1 consul:1.4.4 agent -server -bootstrap-expect=3 -ui -node=consul_server_1 -client='0.0.0.0' -data-dir /consul/data -config-dir /consul/config -datacenter=xdp_dc;

  (2)Consul實例2

  為了讓Consul實例2加入集群,首先獲取一下Consul實例1的IP地址:

  JOIN_IP="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' consul_server_1)";

docker run -d -p 8520:8500 --restart=always -v /XiLife/consul/data/server2:/consul/data -v /XiLife/consul/conf/server2:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_2 consul:1.4.4 agent -server -ui -node=consul_server_2 -client='0.0.0.0' -datacenter=xdp_dc -data-dir /consul/data -config-dir /consul/config -join=$JOIN_IP;  

  (3)Consul實例3

docker run -d -p 8530:8500 --restart=always -v /XiLife/consul/data/server3:/consul/data -v /XiLife/consul/conf/server3:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --privileged=true --name=consul_server_3 consul:1.4.4 agent -server -ui -node=consul_server_3 -client='0.0.0.0' -datacenter=xdp_dc -data-dir /consul/data -config-dir /consul/config -join=$JOIN_IP;

  驗證1:docker exec consul_server_1 consul operator raft list-peers

  

  驗證2:http://192.168.16.170:8500/

  

2.3 Consul Client實例創建

  (1)准備services.json配置文件,向Consul注冊兩個同樣的Product API服務

    {
      "services": [
        {
          "id": "core.product-/192.168.16.170:8000",
          "name": "core.product",
          "tags": [ "xdp-/core.product" ],
          "address": "192.168.16.170",
          "port": 8000,
          "checks": [
            {
              "name": "core.product.check",
              "http": "http://192.168.16.170:8000/api/health",
              "interval": "10s",
              "timeout": "5s"
            }
          ]
        },
        {
          "id": "core.product-/192.168.16.170:8001",
          "name": "core.product",
          "tags": [ "xdp-/core.product" ],
          "address": "192.168.16.170",
          "port": 8001,
          "checks": [
            {
              "name": "core.product.check",
              "http": "http://192.168.16.170:8001/api/health",
              "interval": "10s",
              "timeout": "5s"
            }
          ]
        }
      ]
    }

  有關配置文件的細節,請移步另一篇文章:《.NET Core微服務之基於Consul實現服務治理(續)

  (2)Consul Client實例

docker run -d -p 8550:8500 --restart=always -v /XiLife/consul/conf/client1:/consul/config -e CONSUL_BIND_INTERFACE='eth0' --name=consul_client_1 consul:1.4.4 agent -node=consul_client_1 -join=$JOIN_IP -client='0.0.0.0' -datacenter=xdp_dc -config-dir /consul/config

  (3)驗證

  

  

  

2.4 服務檢查監控郵件提箱

  (1)為Client添加watches.json

       {
          "watches": [
            {
              "type": "checks",
              "handler_type": "http",
              "state": "critical",
              "http_handler_config": {
                "path": "http://192.168.16.170:6030/api/Notifications/consul",
                "method": "POST",
                "timeout": "10s",
                "header": { "Authorization": [ "token" ] }
              }
            }
          ]
        }

  *.這里的api接口 http://192.168.16.170:6030/api/Notifications/consul是我的一個通知服務接口,下面是實現的代碼

        /// <summary>
        /// 發送Consul服務中心健康檢查Email
        /// </summary>
        [HttpPost("consul")]
        public async Task SendConsulHealthCheckEmail()
        {
            using (var stream = new MemoryStream())
            {
                HttpContext.Request.Body.CopyTo(stream);
                var ary = stream.ToArray();
                var str = Encoding.UTF8.GetString(ary);

                dynamic notifications = JsonConvert.DeserializeObject(str);
                if (notifications == null || notifications.Count == 0)
                {
                    return;
                }

                var title = "XDP服務中心健康檢查通知";
                var emailBody = new StringBuilder($"<span style='font-weight:bold; color:red;'>{title}</span> : <br/>");
                foreach (var notification in notifications)
                {
                    emailBody.AppendLine($"---------------------------------------------------------<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>節點</span>:{notification.Node}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>服務ID</span>:{notification.ServiceID}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>服務名稱</span>:{notification.ServiceName}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>檢查ID</span>:{notification.CheckID}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>檢查名稱</span>:{notification.Name}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>檢查狀態</span>:{notification.Status}<br/>");
                    emailBody.AppendLine($"<span style='font-weight:bold;'>檢查輸出</span>:{notification.Output}<br/>");
                    emailBody.AppendLine($"---------------------------------------------------------<br/>");
                }

                var email = new Email()
                {
                    Username = _configuration["EmailSettings:Username"],
                    Password = _configuration["EmailSettings:Password"],
                    SmtpServerAddress = _configuration["EmailSettings:SmtpServerAddress"],
                    SmtpPort = Convert.ToInt32(_configuration["EmailSettings:SmtpPort"]),
                    Subject = title,
                    Body = emailBody.ToString(),
                    Recipients = _configuration["EmailSettings:Recipients"]
                };

                email.Send();
            }
        }

            /// <summary>
            /// 使用同步發送郵件
            /// </summary>
            public void Send()
            {
                using (SmtpClient smtpClient = GetSmtpClient)
                {
                    using (MailMessage mailMessage = GetClient)
                    {
                        if (smtpClient == null || mailMessage == null) return;
                        Subject = Subject;
                        Body = Body;
                        //EnableSsl = false;
                        smtpClient.Send(mailMessage); //異步發送郵件,如果回調方法中參數不為"true"則表示發送失敗
                    }
                }
            }    
View Code

  (2)驗證

  

三、Ocelot網關配置

3.1 為Ocelot增加Consul支持

  (1)增加Nuget包:Ocelot.Provider.Consul

Nuget>> Install-Package Ocelot.Provider.Consul  

  (2)修改StartUp.cs,增加Consul支持

s.AddOcelot() .AddConsul();

  更多內容,請移步:Ocelot官方文檔-服務發現

3.2 修改Ocelot配置文件增加Consul配置

    "GlobalConfiguration": {
        "BaseUrl": "http://api.xique.com",
        "ServiceDiscoveryProvider": {
             "Host": "192.168.16.170",
             "Port": 8550,
             "Type": "Consul"
        }
    }

  *.這里指向的是Consul Client實例的地址

  此外,Ocelot默認策略是每次請求都去Consul中獲取服務地址列表,如果想要提高性能,也可以使用PollConsul的策略,即Ocelot自己維護一份列表,然后定期從Consul中獲取刷新,就不再是每次請求都去Consul中拿一趟了。例如下面的配置,它告訴Ocelot每2秒鍾去Consul中拿一次。

    "Type": "PollConsul",
    "PollingInterval": 2000

3.3 Service配置

    // -- Service
    {
      "UseServiceDiscovery": true,
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "core.product",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "UpstreamPathTemplate": "/product/{url}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ]
    }

  這里配置了在Consul中配置的服務名(ServiceName),以及告訴Ocelot我們使用輪詢策略(RoundRobin)做負載均衡。

3.4 驗證

  第一次訪問:

  

  第二次訪問:

  

四、HA示例整體架構

  對於實際應用中,我們往往會考慮單點問題,因此會借助一些負載均衡技術來做高可用的架構,這里給出一個建議的HA示例的整體架構:

  對於一個API請求,首先會經歷一個Load Balancer才會到達API Gateway,這個Load Balancer可以是基於硬件的F5,也可以是基於軟件的Nginx或LVS再搭配Keepalived,一般來說大部分團隊都會選擇Nginx。然后API Gateway通過部署多個,來解決單點問題,也達到負載均衡的效果。而對於API Gateway和Consul Client之間的連接,我們往往也會增加一個Load Balancer來實現服務發現的高可用,這個Load Balancer也一般會基於Nginx/LVS搭配Keepalived,API Gateway只需要訪問一個Virtual IP即可。而在Consul Data Center中,Consul Server會選擇3或5個,Consul Client也會部署多個,剛剛提到的Virtual IP則會指向多個Consul Client,從而防止了Consul Client的單點問題。

  最后,祝大家端午安康!

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM