Consul 除了如前篇文章所提的,用來儲存設定檔之外,最重要的功能就是負載平衡。而本篇文章將會介紹如何將 Web API 在啟動時向 Consul 註冊。

由於向 Consul 註冊 API 的參數相當複雜,這裡傾向使用第三方的 library,可以在 nuget 中搜尋 Consul,如以下畫面:

安裝後,在專案新增 ConsulInitExtension.cs 這支檔案,它是用來在網站啟動時對 Consul 做註冊。

public static class ConsulInitExtension
{
    public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IApplicationLifetime lifetime, ServiceEntity serviceEntity)
    {
        var consulClient = new Consul.ConsulClient(x => x.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}"));//請求註冊的 Consul 地址
        var httpCheck = new Consul.AgentServiceCheck()
        {
            DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), //服務啟動多久後註冊
            Interval = TimeSpan.FromSeconds(10), //health check 的間隔
            HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}/api/Health", //url of health check
            Timeout = TimeSpan.FromSeconds(5)
        };

        // register service with consul
        var registration = new Consul.AgentServiceRegistration()
        {
            Checks = new[] { httpCheck },
            ID = Guid.NewGuid().ToString(),
            Name = serviceEntity.ServiceName,
            Address = serviceEntity.IP,
            Port = serviceEntity.Port,
            // for Fabio
            Tags = new[] { $"urlprefix-/{serviceEntity.ServiceName}" }
        };

        var registerResult = consulClient.Agent.ServiceRegister(registration);
        registerResult.Wait();
        lifetime.ApplicationStopping.Register(() =>
        {
            consulClient.Agent.ServiceDeregister(registration.ID).Wait(); //服務停止時取消註冊
        });
        return app;
    }
}

然後在 Startup.cs 新增下列程式碼:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
{
    //...

    // register service
    string strHostName = Dns.GetHostName();
    IPAddress[] ipaddresses = Dns.GetHostAddresses(strHostName);
    ServiceEntity serviceEntity = new ServiceEntity
    {
        IP = ipaddresses[1].ToString(),
        Port = Convert.ToInt32(Configuration["Service:Port"]),
        ServiceName = Configuration["Service:Name"],
        ConsulIP = Configuration["Consul:IP"],
        ConsulPort = Convert.ToInt32(Configuration["Consul:Port"])
    };
    app.RegisterConsul(lifetime, serviceEntity);
}

前面的程式碼會讀取設定檔,而 appsettings.json 檔案的內容如下:

{
  "AllowedHosts": "*",
  "Service": {
    "Name": "API.ConsulTestService",
    "Port": "9000"
  },
  "Consul": {
    "IP": "127.0.0.1",
    "Port": "8500"
  },
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }
}

之後將網站佈署到 IIS,測試時記得暫時關閉防火牆,以免 Consul 沒辦法做 health check。網站啟動後,到 Consul 頁面,可以看到以下成功的畫面:

要取得 API 位址時,可透過下列網址取得 json value:

http://[FQDN]]:8500/v1/catalog/service/[API Name]

json value 如下:

[
  {
    "ID": "3f95b2f2-85f9-8059-f77d-e0c308125790",
    "Node": "1918da7ef151",
    "Address": "172.17.0.2",
    "Datacenter": "dc1",
    "TaggedAddresses": {
      "lan": "172.17.0.2",
      "wan": "172.17.0.2"
    },
    "NodeMeta": {
      "consul-network-segment": ""
    },
    "ServiceKind": "",
    "ServiceID": "62af3850-5d29-4110-90fe-3d29fcaf5b78",
    "ServiceName": "API.ConsulTestService",
    "ServiceTags": [
      "urlprefix-/API.ConsulTestService"
    ],
    "ServiceAddress": "192.168.50.41",
    "ServiceWeights": {
      "Passing": 1,
      "Warning": 1
    },
    "ServiceMeta": {},
    "ServicePort": 9000,
    "ServiceEnableTagOverride": false,
    "ServiceProxyDestination": "",
    "ServiceProxy": {},
    "ServiceConnect": {},
    "CreateIndex": 182330,
    "ModifyIndex": 182330
  }
]

從裡面的 ServiceAddress 跟 ServicePort 就可以抓到 API 的位址。

參考資料