使用Docker部署Consul叢集並由Ocelot呼叫

2023-03-20 12:03:35

關於consul的介紹就不寫了百度就行,我們直接開幹。

一、部署consul叢集

拉取consul的映象

docker pull consul

然後部署consul容器

 docker run --name consul1 -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600 consul:latest agent -server -bootstrap-expect 2 -ui -bind='0.0.0.0' -client='0.0.0.0'

8500 http 埠,用於 http 介面和 web ui
8300 server rpc 埠,同一資料中心 consul server 之間通過該埠通訊
8301 serf lan 埠,同一資料中心 consul client 通過該埠通訊
8302 serf wan 埠,不同資料中心 consul server 通過該埠通訊
8600 dns 埠,用於服務發現

-bbostrap-expect 2: 叢集至少兩臺伺服器,才能選舉叢集leader
-ui:執行 web 控制檯
-bind: 監聽網口,0.0.0.0 表示所有網口,如果不指定預設為127.0.0.1,則無法和容器通訊
-client : 限制某些網口可以存取

-server:表示該節點是server節點,不宣告的話預設為client節點,它們的不同是server節點持久化資訊,但是client不持久化會轉發給server節點,並且server節點會有leader節點進行健康檢測和同步資訊到其他server節點

然後我們獲取該consul1容器的ip,使用如下語句

docker inspect --format '{{ .NetworkSettings.IPAddress }}' consul1

獲取到了172.17.0.5

然後我們再新建另一個consul容器,使用join來加入第一個consul1容器的叢集

docker run --name consul2 -d -p 18500:8500 -p 18300:8300 -p 18301:8301 -p 18302:8302 -p 18600:8600 consul:latest agent -server -ui -bind='0.0.0.0' -client='0.0.0.0' -join 172.17.0.5

 我就建兩個節點能執行即可,server一般建3-5個,client沒有上限,所以根據上面的語句自己建立即可。

然後我們存取地址http://localhost:8500/可以進到控制介面。

 二、將服務註冊到Consul中

為測試方便我們使用-dev引數允許啟動一個Consul服務。

docker run --name consul1 -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600 consul:latest agent -server -ui -bind='0.0.0.0' -client='0.0.0.0' -dev

我們新建一個專案,然後下載Consul包

Install-Package Consul

 然後我們新增一個健康檢查的介面

    [Route("[controller]/[action]")]
    [ApiController]
    public class HealthController : Controller
    {
        [HttpGet("/healthCheck")]
        public IActionResult Check() => Ok("ok");
    }

之後我們在appsetting.json中設定我們的Consul引數,這些引數我們用來註冊服務的一些資訊,引數解釋如下:

ServiceName:是服務的名稱,同一個服務名的服務將會註冊到同一個服務下的範例

ServiceIP:服務請求的主機地址

ServicePort:服務請求的埠

ServiceHealthCheck:服務健康檢測介面地址,此處是host.docker.internal是因為Consul在容器內需要存取宿主主機執行的服務

AddressConsul服務的請求地址

  "Consul": {
    "ServiceName": "service-a",
    "ServiceIP": "127.0.0.1",
    "ServicePort": 5001,
    "ServiceHealthCheck": "http://host.docker.internal:5001/healthCheck",
    "Address": "http://127.0.0.1:8500"
  }

之後我們建立一個類名叫ConsulOption,用於使用過Option模式載入appsetting.json中我們設定的引數,用於註冊

    public class ConsulOption
    {
        /// <summary>
        /// 服務名稱
        /// </summary>
        public string ServiceName { get; set; }
        /// <summary>
        /// 服務IP
        /// </summary>
        public string ServiceIP { get; set; }
        /// <summary>
        /// 伺服器埠
        /// </summary>
        public int ServicePort { get; set; }
        /// <summary>
        /// 服務健康檢查地址
        /// </summary>
        public string ServiceHealthCheck { get; set; }
        /// <summary>
        /// Consul 地址
        /// </summary>
        public string Address { get; set; }
    }
View Code

然後建立一個Consul服務註冊類ConsulBuilderExtensions,對將本服務推播到Consul中去,具體的引數解釋在程式碼註釋中了

public static class ConsulBuilderExtensions
    {
        public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IHostApplicationLifetime lifetime, ConsulOption consulOption)
        {
            var consulClient = new ConsulClient(x =>
            {
                x.Address = new Uri(consulOption.Address);
            });
            var registration = new AgentServiceRegistration()
            {
                ID = Guid.NewGuid().ToString(),
                Name = consulOption.ServiceName,// 服務名
                Address = consulOption.ServiceIP, // 服務繫結IP
                Port = consulOption.ServicePort, // 服務繫結埠
                Check = new AgentServiceCheck()
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務啟動多久後註冊
                    Interval = TimeSpan.FromSeconds(10),//健康檢查時間間隔
                    HTTP = consulOption.ServiceHealthCheck,//健康檢查地址
                    Timeout = TimeSpan.FromSeconds(5)
                }
            };
            // 服務註冊
            consulClient.Agent.ServiceRegister(registration).Wait();
            // 應用程式終止時,服務取消註冊

            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(registration.ID).Wait();
            });
            return app;

        }
    }

最後我們注入本服務

builder.Services.AddSingleton(builder.Configuration.GetSection("Consul").Get<ConsulOption>());
//....
app.RegisterConsul(app.Lifetime, app.Services.GetRequiredService<ConsulOption>());

啟動專案就能看到服務已經註冊

Consul提供了http api可以讓我們進行查詢、註冊、接觸註冊等操作

http://127.0.0.1:8500/v1/health/service/service-a?passing

 三、使用Ocelot呼叫

我們新建一個專案然後安裝包

Install-Package Ocelot.Provider.Consul

然後註冊服務

builder.Services.AddOcelot().AddPolly().AddConsul();

之後在組態檔中的GlobalConfiguration節點下新增如下引數,這是必須的如果沒有指定主機Host和埠Port將會使用Consul預設的,Scheme預設為httpType說明此服務發現由Consul提供

"ServiceDiscoveryProvider": {
    "Scheme": "http",
    "Host": "localhost",
    "Port": 8500,
    "Type": "Consul"
}

 然後我們設定路由,新增我們剛註冊的服務,最好是配合負載均衡引數咯,我這裡沒寫

  "Routes": [
    {
      "DownstreamPathTemplate": "/{everything}",
      "DownstreamScheme": "http",
      "ServiceName": "service-a",
      "UpstreamPathTemplate": "/api/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }

然後執行就可以看到結果了,需要注意的是請求是http還是https這些需要注意,我就卡在了這裡很久,請求路徑需要注意噢

最後,Ocelot是每次請求都去Consul獲取最新的服務,如果需要設定間隔多久去獲取Consul的最新服務(可能會有微小的效能改進,但是不知道原有服務是否可用,可能會有錯誤的返回噢)可以如下設定:

"ServiceDiscoveryProvider": {
    "Host": "localhost",
    "Port": 8500,
    "Type": "PollConsul",
    "PollingInterval": 100
}