微服務注冊發現集群搭建——Registrator + Consul + Consul-template + nginx

在互聯網應用領域,服務的動態性需求十分常見,這就對服務的自動發現和可動態擴展提出了很高的要求。

微服務系統動輒上萬個服務,而且還要動態伸縮。以人工寫好的IP、Port 硬編碼腳本的方式無法做到大規模自動化,稍微多點服務運維就傻了。微服務必然要做到ip和port自動分配,減少人工干預。我們需要讓每個服務能動態的創建地址,同時調用方要能感知地址變化。

這就需要有一個服務注冊與發現的機制,這篇文件就是討論如何實現這個機制。

1. 服務注冊發現的流程

我們做這個事情要達到的目的是:

注冊發現模式 傳統模式
服務啟動后自動被發現 手動注冊
動態變更負載均衡 人工寫入靜態配置
自動伸縮規模 運維較長時間的手動調整

1.1 服務 “自注冊” 與 “第三方注冊”。

按注冊源分

1.自注冊:服務內部啟動客戶端,連接注冊中心,寫入服務信息。

好處:

  • 沒有引入第三方,進程數量少,少依賴。

問題:

  • 服務代碼對注冊中心進行了硬編碼,若更換了注冊中心,服務代碼也必須跟著調整;
  • 注冊中心必須與每個服務都保持通信,來做心跳檢測。如果服務很多時,對注冊中心也是一種額外的開銷;

2.第三方注冊(本文采用方式):采用協同進程的方式,監聽服務進程的變化,將服務信息寫入注冊中心。

  • 好處:做到了服務與注冊中心的解耦,對服務而言,完成了服務的自動化注冊;
  • 問題:協同進程本身也要考慮高可用,否則將成為單點故障的風險點;

1.2 自注冊的實現

自注冊不是我們本篇要討論的,可以自己寫代碼實現,我們討論第三方注冊的實現。

1.3 第三方注冊的實現

Docker 的出現,以及微服務架構的興起,讓眾多開源項目開始關注在松耦合的架構前提下,如何基于 Docker 實現一套真正可動態擴展的服務架構。

這里我們使用 Registrator + Consul + Consul-template + Nginx 這幾個開源組件來實現可動態擴展的服務注冊與發現機制,當然,毫無疑問他們都跑在docker上。

首先看看流程:

這里寫圖片描述

服務注冊中心:作為整個架構中的核心,要支持分布式、持久化存儲,注冊信息變動實時通知消費者。

服務提供者:服務以 docker 容器化方式部署(實現服務端口的動態生成),并以 docker-compose 的方式來管理,通過 Registrator 可以檢測到docker進程信息以完成服務的自動注冊。

服務消費者:要使用服務提供者提供的服務,和服務提供者往往是動態相互轉位置的。

  1. 服務注冊:服務提供者到注冊中心注冊;
  2. 服務訂閱:服務消費者到注冊中心訂閱服務信息,對其進行監聽;
  3. 緩存:本地緩存服務列表,減少與注冊中心的網絡通信;
  4. 服務調用:先查找本地緩存,找不到再去注冊中心拉取服務地址,然后發送服務請求;
  5. 變更通知:服務節點變動時(新增、刪除等),注冊中心將通知監聽節點,更新服務信息。

2. 工具介紹

2.1 Registrator

Registrator:一個由Go語言編寫的,針對docker使用的,通過檢查本機容器進程在線或者停止運行狀態,去注冊服務的工具。所以我們要做的實驗,所有的工具都是在docker上運行的,就是因為registrator是通過檢查docker容器的狀態來判斷服務狀態的,這樣就和我們的代碼實現完全解耦了,對上層透明化,無感知。它有如下特點

  • 通過docker socket直接監聽容器event,根據容器啟動/停止等event來注冊/注銷服務
  • 每個容器的每個exposed端口對應不同的服務
  • 支持可插拔的registry backend,默認支持Consul, etcd and SkyDNS
  • 自身也是docker化的,可以容器方式啟動
  • 用戶可自定義配置,如服務TTL(time-to-live)、服務名稱、服務tag等

2.1 consul

我們上圖所說的服務注冊中心,就是這玩意。Consul 是一個分布式高可用的服務發現和配置共享的軟件。由 HashiCorp 公司用 Go 語言開發。

Consul在這里用來做 docker 實例的注冊與配置共享。

特點:

  • 一致性協議采用 Raft 算法,比Paxos算法好用. 使用 GOSSIP 協議管理成員和廣播消息, 并且支持 ACL 訪問控制.
  • 支持多數據中心以避免單點故障,內外網的服務采用不同的端口進行監聽。而其部署則需要考慮網絡延遲, 分片等情況等.zookeeper 和 etcd 均不提供多數據中心功能的支持.
  • 健康檢查. etcd 沒有的.
  • 支持 http 和 dns 協議接口. zookeeper 的集成較為復雜, etcd 只支持 http 協議.
  • 還有一個web管理界面。

2.3 consul-template

一開始構建服務發現,大多采用的是zookeeper/etcd+confd。但是復雜難用。consul-template,大概取代了confd的位置,以后可以這樣etcd+confd或者consul+consul-template。

consul template的使用場景:consul template可以查詢consul中的服務目錄、key、key-values等。這種強大的抽象功能和查詢語言模板可以使consul template特別適合動態的創建配置文件。例如:創建apache/nginx proxy balancers、haproxy backends、varnish servers、application configurations。

consul-template提供了一個便捷的方式從consul中獲取存儲的值,consul-template守護進程會查詢consul服務,來更新系統上指定的任何模板,當更新完成后,模板可以選擇運行一些任意的命令,比如我們這里用它來更新nginx.conf這個配置文件,然后執行nginx -s reload命令,以更新路由,達到動態調節負載均衡的目的。

consul-template和nginx必須裝到一臺機器,因為consul-template需要動態修改nginx配置文件

2.4 nginx

這個耳熟能詳的名字,不用過多介紹了,它在這里就是做負載均衡,轉發請求用的。當然最擅長負載均衡的是直接用硬件,軟件做性能比不上。但軟件成本低、維護方便。

3. 單機實驗

首先看一個簡單的傳統負載均衡web服務

load balance web servers

這個很好理解吧,client訪問nginx,然后被轉發到后端某一個web server上,傳統的負載均衡。如果后端有添加/刪除web server,運維手動改下nginx.conf,然后重新載入配置,就可以調整負載均衡了。

再看看我們基于微服務自動注冊和發現模式下的負載均衡:

Servies register and find

負載均衡的方式沒有變,只是多了一些外圍的組件,當然這些組件對client是不可見的,client依然只能看到nginx入口,訪問方式也沒變化。

其中,我們用registrator來監控每個web server的狀態。當有新的web server啟動的時候,registrator會把它注冊到consul這個注冊中心上。由于consul_template已經訂閱了該注冊中心上的服務消息,此時consul注冊中心會將新的web server信息推送給consul_template,consul_template則會去修改nginx.conf的配置文件,然后讓nginx重新載入配置以達到自動修改負載均衡的目的。同樣當一個web server掛了,registrator也能感知到,進而通知consul做出響應。

整個過程不需要運維人工的干預,自動完成。接下來我們找一臺機器上實踐下這個方案

3.1 環境

header header
操作系統 ubuntu:16.04 x86_64,內核:4.8.0-58-generic
主機ip 10.111.152.136
docker Docker version 1.12.6, build 78d1802
docker-compose docker-compose version 1.8.0, build unknown

首先安裝 docker 和 docker-compose

$ apt-get install docker docker-compose -y

隨便找個目錄,創建模板文件 docker-compose.yml

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-1
    MY_HOST: host-1
  ports:
  - "80"

#load balancer will automatically update the config using consul-template
lb:
  image: liberalman/nginx-consul-template:latest
  hostname: lb
  links:
  - consulserver:consul
  ports:
  - "80:80"

consulserver:
  image: progrium/consul:latest
  environment:
    SERVICE_TAGS: consul servers
  hostname: consulserver
  ports:
  - "8300"
  - "8400"
  - "8500:8500"
  - "53"
  command: -server -ui-dir /ui -data-dir /tmp/consul -bootstrap-expect 1

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator
  links:
  - consulserver:consul
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -internal consul://consul:8500

注意: liberalman/helloworld和liberalman/nginx-consul-template這兩個鏡像我已經實現了,可以pull下來,大家可以直接使用。想要看他們怎么寫的,訪問https://github.com/liberalman

3.2 啟動

進入模板所在目錄,執行

$ docker-compose up

沒問題的話就啟動成功了,其中的鏡像自動被下。訪問 http://localhost 可以看到一個 web 頁面:

Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.2</font>. I saw that you are 172.17.0.6:35612.

這個內容實際是后端web服務器helloworld所反饋的頁面,它告訴我們它自己的地址是172.17.0.2(docker的內網地址),它所看到的前端訪問過來的ip是172.17.0.6,實際上這個前端是我們的nginx的負載均衡的代理轉發的,所以它看到的實際是nginx的地址。

這里的host-1是我自己設置的物理機的名稱,注釋不是操作系統那hostname,純粹是為了在頁面上好顯示,以及后期多個物理機實驗的時候好區分不同物理機器,所以自定義了一個臨時名稱。它對應docker-compose.yml中的MY_HOST環境變量,會通過docker容器傳遞到helloworld的運行環境中。

要停止服務Ctrl + C就行了,如果有些沒有停止,則

$ docker-compose down

如果要在后臺運行

$ docker-compose up -d

3.3 負載均衡

回到正題,在瀏覽器上多次刷新,可以看到后端地址沒有變化,這是因為只有一個 web 后端服務器。

如果要測試一下nginx負載均衡的效果,則調整后端為 3 個服務器。先停掉服務,然后

$ docker-compose scale web=3
$ docker-compose up

再次訪問 http://localhost ,多次刷新,可以看到頁面的實際目標地址發生了變化,有3個ip輪換。新啟動的 web 后端服務器被自動注冊,并且 nginx 也把新的路由添加上了:

Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.2</font>. I saw that you are 172.17.0.6:36710.
Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.3</font>. I saw that you are 172.17.0.6:35210.
Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.4</font>. I saw that you are 172.17.0.6:58678.

3.4 查看服務狀態

要查看節點注冊狀況,到 http://localhost:8500 可以看到 consul web ui 的管理端

consul ui

點擊SERVICES這個按鈕,列出所有被注冊的服務。

  • consul server,看到有多個是因為監聽多個端口,還有udp端口的。
  • my-web-server就是后端web服務,這個名稱是要在docker-compose模板中設置SERVICE_80_NAME這個變量的,針對80端口,詳情見registrator 用戶指導手冊
    https://gliderlabs.com/registrator/latest/user/services/。
  • nginx-consul-template就是nginx和consul-template的合體服務。

點擊my-web-server,可以看到它右側的服務節點數,這里只有一個,有多個的話會依次列出

host-1 my-web-server

4. 兩臺物理機

以上都是在單臺物理機上完成的,下面我們要測試下多臺物理機情況下,真正分布式的效果。

host name real ip services
host-1 10.111.152.136 registrator、helloworld、consul-server、consul-template、nginx
host-2 10.111.152.135 registrator、helloworld

第一臺物理機host-1的docker-compse.yml

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-1
    MY_HOST: host-1
  ports:
  - "80"

#load balancer will automatically update the config using consul-template
lb:
  image: liberalman/nginx-consul-template:latest
  hostname: lb
  links:
  - consulserver:consul
  ports:
  - "80:80"

consulserver:
  image: progrium/consul:latest
  environment:
    SERVICE_TAGS: consul servers
  hostname: consulserver-node1
  ports:
  - "8300"
  - "8400"
  - "8500:8500"
  - "53"
  command: -server -ui-dir /ui -data-dir /tmp/consul -bootstrap-expect 1

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-1
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -ip=10.111.152.136 consul://10.111.152.136:8500

我們第二臺機器host-2的yml文件:

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-2
    MY_HOST: host-2
  ports:
  - "80"

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-2
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -ip 10.111.152.135 consul://10.111.152.136:8500

這是我們將MY_HOST改為host-2了,以便在頁面查看的時候可以直觀看到。另外的重要改變就是registrator的啟動參數,我們去掉了上報docker內部ip的-internal,轉而使用了外部ip,將自己本機的ip 10.111.152.135上報了。同時要訪問的consul服務器參數配置成host-1的ip地址 10.111.152.136。還有registrator的hostname要和第一臺機器的區別開,我改成registrator-2了,這樣在注冊到consul中的時候,不會覆蓋掉。hostname一樣consul無法區分是哪個機器的,這樣兩個機器的registrator會相互覆蓋。

host-1啟動方式不變,我們現在到host-2上啟動,看看效果,是否新節點被加上了。

Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.2</font>. I saw that you are 172.17.0.5:41464.

Hello World! I'm <font color=blue>host-2</font> <font color=red>addr:172.17.0.2</font>. I saw that you are 10.111.152.136:41578.

刷新兩次,發現一會兒是host-1,一會兒是host-2,說明我們host-2物理機上的服務被添加進來了,并且被nginx路由到了。

同時consul ui,看到新的節點果然被添加上了

host-2 my-web-server

不過發現個問題,如果在host-2上先將registrator關閉,再關閉host-2上的后端web,我們的consul服務器可以感知到,但是那個consul ui界面沒更新,依然顯示兩個節點。

5. Consul Cluster

以上我們的實驗其實是個單點的consul服務,點擊consul ui頁面的NODES按鈕可以看到

single node

只有一個consul server節點,也就是在我們host-1上跑的節點consulserver,另外一個物理機上沒有運行consul節點。一旦它掛了整個服務注冊功能就歇菜了。既然是分布式,一定要發揮集群的優勢以解決單點問題。所以,我們要建立Consul Cluster。

在Consul方案中,每個提供服務的節點上都要部署和運行一個agent,所有運行Consul agent節點的集合構成Consul Cluster。

Consul agent有兩種運行模式:Server和Client。這里的Server和Client只是Consul集群層面的區分,與搭建在Cluster之上的應用服務無關。

以Server模式運行的Consul agent節點用于維護Consul集群的狀態,官方建議每個Consul Cluster至少有3個或以上的運行在Server mode的Agent,Client節點不限。

這里寫圖片描述

每個數據中心的Consul Cluster都會在運行于server模式下的agent節點中選出一個Leader節點,這個選舉過程通過Consul實現的raft協議保證,多個 server節點上的Consul數據信息是強一致的。處于client mode的Consul agent節點比較簡單,無狀態,僅僅負責將請求轉發給Server agent節點。

我們這次的架構有些調整,繪制一個服務器的邏輯上的部署圖來說明下

Services register adn find, consul cluster

這是一張邏輯上服務部署的圖,我們找3臺機器來實驗。每臺機器上部署幾個web server,一個registrator和一個consul client,這是基本需求。另外再建立一個consul cluster集群,用來當我們的注冊中心。當web server啟動后,被registrator感知,進而將注冊信息發送給consul client,consul client則訪問注冊中心的leader節點,上報新加入的服務信息。consul cluster會將新的服務信息推送給已經到它這里訂閱了服務消息的consul-template,consul-template再去修改和自己同一臺機器上的nginx,以達到動態調整負載均衡的目的。

注意:由于資源有限,我們沒有單獨使用機器去搭建consul集群,所以圖中的consul client和consul server節點其實是同一個節點,因為server模式同時可以提供client的功能嘛。那個consul cluster集群其實是分布到3個host中建立起來的,我們就在3個host中分別啟動一個consul進程,每個都同時擔任server和client的功能。

5.1 配置

host name real ip services note
host-1 10.111.152.136 registrator、helloworld*n、consul-server、consul-template、nginx 放置consol web ui和nginx負載均衡
host-2 10.111.152.135 registrator、helloworld*n、consul-server
host-3 10.111.152.168 registrator、helloworld*n、consul-server

host-1作為運行負載均衡的機器,部署consul-template和nginx。每個機器上都部署了consul-server節點,也就是我們有3個節點,接下來就研究這3個節點是如何選舉leader的。

host-1的docker-compose.yml文件

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-1
    MY_HOST: host-1
  ports:
  - "80"

#load balancer will automatically update the config using consul-template
lb:
  image: liberalman/nginx-consul-template:latest
  hostname: lb
  links:
  - consulserver:consul
  ports:
  - "80:80"

consulserver:
  image: progrium/consul:latest
  environment:
    SERVICE_TAGS: consul servers
  hostname: consulserver-node1
  ports:
  - "8300:8300"
  - "8301:8301"
  - "8301:8301/udp"
  - "8302:8302"
  - "8302:8302/udp"
  - "8400:8400"
  - "8500:8500"
  - "53:53/udp"
  command: -server -ui-dir /ui -advertise 10.111.152.136 -bootstrap-expect 3

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-1
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -ip 10.111.152.136 consul://10.111.152.136:8500

參數解釋下

  • hostname,將來consul節點都靠這個來標識了,所以每個物理機上的節點名稱都要區別開,以免沖突。
  • -bootstrap-expect 3,這個參數的作用是,當consulserver-node1節點啟動之后,等待另外兩個節點的加入,3個節點聚齊后,之后才開始選舉leader。
  • -advertise 10.111.152.136,如果要讓節點在WAN網絡中被發現,就要配置這個參數,暴露出外網ip。如果只在LAN中被發現,就不用配置這個了,默認綁定內網ip。
  • -ui-dir /ui,這個配置是指定當前節點支持consul ui的web頁面。

host-2的docker-compose.yml文件

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-2
    MY_HOST: host-2
  ports:
  - "80"

consulserver:
  image: progrium/consul:latest
  environment:
    SERVICE_TAGS: consul servers
  hostname: consulserver-node2
  ports:
  - "8300:8300"
  - "8301:8301"
  - "8301:8301/udp"
  - "8302:8302"
  - "8302:8302/udp"
  - "8400:8400"
  - "8500:8500"
  - "53:53/udp"
  command: -server -advertise 10.111.152.135  -join 10.111.152.136

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-2
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command:  -ip 10.111.152.135 consul://10.111.152.136:8500

與host-1不同的是,host-2使用了參數
-join 10.111.152.136 意思是把本節點加入到10.111.152.136這個ip的節點中,這是consulserver-node1的地址。我們上一個host的配置中表明,consulserver-node1這個節點啟動后,會等待另外兩個節點的加入,我們這里就是加入它。

host-3的docker-compose.yml文件

#backend web application, scale this with docker-compose scale web=3
web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-3
    MY_HOST: host-3
  ports:
  - "80"

consulserver:
  image: progrium/consul:latest
  environment:
    SERVICE_TAGS: consul servers
  hostname: consulserver-node3
  ports:
  - "8300:8300"
  - "8301:8301"
  - "8301:8301/udp"
  - "8302:8302"
  - "8302:8302/udp"
  - "8400:8400"
  - "8500:8500"
  - "53:53/udp"
  command: -server -advertise 10.111.152.168 -join 10.111.152.136

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-3
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -ip 10.111.152.168 consul://10.111.152.136:8500

注意:到這里你可能有疑問,上文的3個節點都是server節點,那client節點哪里去了,沒有client節點怎么訪問集群?。课覀兒图航换タ墒窃L問client,client再轉發到server節點的。

我們前篇也提到過,其實每個server節點,本身就具有client的功能,只是多了一些把所有的信息持久化的本地以及選舉leader的功能呢,這樣遇到故障,信息是可以被保留的。

所以,這里我們每個主機上部署registrator的時候,配置的訪問consul服務的地址也是就近訪問本機上的consul節點,把它當成一個consul client訪問就可以了。當然也可以單獨部署一個client節點,只是我們至少要保證有3個server節點,才能完成leader選舉,如果再多一臺機器我會考慮專門加一個client節點。

5.2 啟動

依次在host-1、host-2和host-3上啟動3個節點。注意執行docker-compose up之后,不要關閉終端,讓它一直打印,后續我們還要在這里看日志,別的操作都轉到新開終端上執行。訪問 http://10.111.152.136:8500/ui/#/dc1/nodes 看到節點都被添加上了

這里寫圖片描述

除了查看ui界面外,也可以使用命令行看看有哪些服務注冊了,在新終端下執行

~# curl 10.111.152.136:8500/v1/catalog/services|jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   308  100   308    0     0  54892      0 --:--:-- --:--:-- --:--:-- 61600
{
  "consul": [],
  "consul-53": [
    "consul servers",
    "udp"
  ],
  "consul-8300": [
    "consul servers"
  ],
  "consul-8301": [
    "consul servers",
    "udp"
  ],
  "consul-8302": [
    "consul servers",
    "udp"
  ],
  "consul-8400": [
    "consul servers"
  ],
  "consul-8500": [
    "consul servers"
  ],
  "my-web-server": [
    "backend-1",
    "backend-2",
    "backend-3"
  ],
  "nginx-consul-template": []
}

訪問 http://10.111.152.136 查看nginx負載均衡的效果,依次刷新,得到

Hello World! I'm <font color=blue>host-1</font> <font color=red>addr:172.17.0.2</font>. I saw that you are 172.17.0.1:49728.
Hello World! I'm <font color=blue>host-2</font> <font color=red>addr:172.17.0.3</font>. I saw that you are 10.111.152.136:54640.
Hello World! I'm <font color=blue>host-3</font> <font color=red>addr:172.17.0.3</font>. I saw that you are 10.111.152.136:58660.

OK,看起來一切正常。那我們現在分析下到底哪個節點是leader,有節點退出會怎樣?

現在新開一個終端,在host-1上,執行

docker ps -f name=consul

查到consul節點的容器id是4364cd41f2ba。登錄這個容器

docker exec -it 4364cd41f2ba /bin/sh

然后就進入容器的操作系統環境了,在該環境下執行

/ # consul members
Node                Address              Status  Type    Build  Protocol  DC
consulserver-node3  10.111.152.168:8301  alive   server  0.5.2  2         dc1
consulserver-node1  10.111.152.136:8301  alive   server  0.5.2  2         dc1
consulserver-node2  10.111.152.135:8301  alive   server  0.5.2  2         dc1

一目了然的看到了我們的3個consul節點。查看當前節點信息

/ # consul info
......
consul:
        bootstrap = false
        known_datacenters = 1
        leader = false
        server = true
raft:
        applied_index = 192
        commit_index = 192
        fsm_pending = 0
        last_contact = 15.960533ms
        last_log_index = 192
        last_log_term = 2
        last_snapshot_index = 0
        last_snapshot_term = 0
        num_peers = 2
        state = Follower
        term = 2
......

輸出信息很多,省略掉,只給出重要的。server = true確實是server節點??吹絣eader=false,說明這個節點不是leader。state = Follower,看來確實是個Follower節點哦。last_contact = 15.960533ms心跳剩余時間,term = 2說是第二個term,已經選過2回了。

執行上述命令的同時,由于之前在host-1上執行docker-compose up命令的時候,日志是直接輸出到屏幕上的,我們此時可以節點1輸出的日志

consulserver_1  |     2017/07/26 05:08:20 [INFO] agent.rpc: Accepted client: 127.0.0.1:47084
consulserver_1  |     2017/07/26 05:08:24 [INFO] agent.rpc: Accepted client: 127.0.0.1:47086
......

我們剛才執行的命令都是客戶端發到當前consul server上的。

同樣的方式,在節點在conserver-node3上

consul:
        bootstrap = false
        known_datacenters = 1
        leader = true
        server = true

原來leader是節點3.

5.3 去掉節點

讓一個節點掛掉,看看會發生什么。

5.3.1 關閉一個節點

在host-1上新開終端執行

 docker stop 4364cd41f2ba

看到host-1的日志滾動

gocode_consulserver_1 exited with code 1
lb_1            | 2017/07/26 06:02:51.211894 [WARN] (view) health.service(my-web-server|passing): Get http://consul:8500/v1/health/service/my-web-server?index=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: i/o timeout (retry attempt 1 after "250ms")
ex=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: i/o timeout (retry attempt 1 after "250ms")
lb_1            | 2017/07/26 06:03:10.099572 [WARN] (view) health.service(my-web-server|passing): Get http://consul:8500/v1/health/service/my-web-server?index=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: getsockopt: no route to host (retry attempt 2 after "500ms")
......

lb_1會不斷的打印重試到http://consul:8500的健康檢查。

不過此時訪問 http://10.111.152.136 發現nginx并沒有被破壞,還是可以正常路由到后端三個節點的,后端的web server也正常可用。沒有受到一個consul server節點掛掉的影響。

只是consul web ui無法訪問了,http://10.111.152.136:8500/ui/#/dc1/services 因為剛好把這個節點停掉了。

另外兩個節點的日志情況

host-2機器上,consulserver-node2節點,也是一個Follower狀態的節點上

consulserver_1  |     2017/07/26 06:02:24 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received
consulserver_1  |     2017/07/26 06:02:27 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received
consulserver_1  |     2017/07/26 06:02:27 [INFO] memberlist: Marking consulserver-node1 as failed, suspect timeout reached
consulserver_1  |     2017/07/26 06:02:27 [INFO] serf: EventMemberFailed: consulserver-node1 10.111.152.136
consulserver_1  |     2017/07/26 06:02:27 [INFO] consul: removing server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)
consulserver_1  |     2017/07/26 06:03:19 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301
consulserver_1  |     2017/07/26 06:03:49 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301
consulserver_1  |     2017/07/26 06:05:19 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301
......

每隔30s嘗試重連node1.

host-3機器上,consulserver-node3節點,我們的leader

consulserver_1  |     2017/07/26 06:02:21 [INFO] raft: aborting pipeline replication to peer 10.111.152.136:8300
consulserver_1  |     2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: EOF
consulserver_1  |     2017/07/26 06:02:21 [ERR] raft: Failed to heartbeat to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused
consulserver_1  |     2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused
consulserver_1  |     2017/07/26 06:02:21 [ERR] raft: Failed to heartbeat to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused
consulserver_1  |     2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: dial 
......

也在嘗試重連,而且它間隔2s就嘗試一次,頻率上更快。由于一直連不上,后來干脆去掉node1節點了。

consulserver_1  |     2017/07/26 06:02:27 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received
consulserver_1  |     2017/07/26 06:02:27 [INFO] memberlist: Marking consulserver-node1 as failed, suspect timeout reached
consulserver_1  |     2017/07/26 06:02:27 [INFO] serf: EventMemberFailed: consulserver-node1 10.111.152.136
consulserver_1  |     2017/07/26 06:02:27 [INFO] consul: removing server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

不過雖然去掉了node1,但是其他節點依然沒有放棄嘗試重連node1。重連的操作一直都在繼續中。

5.3.2 恢復節點

把剛才在host-1上關閉的容器重新啟動

docker start 4364cd41f2ba

看看3個機器都會輸出什么。

host-1上

consulserver_1  | ==> Starting raft data migration...
consulserver_1  | ==> Starting Consul agent...
consulserver_1  | ==> Starting Consul agent RPC...
consulserver_1  | ==> Consul agent running!
consulserver_1  |          Node name: 'consulserver-node1'
consulserver_1  |         Datacenter: 'dc1'
consulserver_1  |             Server: true (bootstrap: false)
consulserver_1  |        Client Addr: 0.0.0.0 (HTTP: 8500, HTTPS: -1, DNS: 53, RPC: 8400)
consulserver_1  |       Cluster Addr: 10.111.152.136 (LAN: 8301, WAN: 8302)
consulserver_1  |     Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
consulserver_1  |              Atlas: <disabled>

......

consulserver_1  |     2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2
......

consulserver-node1節點又重新啟動了,并且整個集群選舉了新的leader上來:consulserver-node2

host-2上

consulserver_1  |     2017/07/26 06:23:05 [INFO] consul: adding server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

......

consulserver_1  |     2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2
......

感知到了consulserver-node1的復活,并且也參與了選舉,選出新leader,是自己,哈哈。

host-3上

consulserver_1  |     2017/07/26 06:23:05 [INFO] consul: adding server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

......

consulserver_1  |     2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2
......

同樣感知到了consulserver-node1的復活,并且也參與了選舉,選出新leader。

此時ngxin依然沒受影響,web服務正常。而且consul web ui也可以正常訪問了。一切都恢復如初。具體這3個節點是如何選舉leader和處理節點的退出和重入的,參考我另外一篇文章 consul實現的raft算法選舉過程解析。


創建于 2017-07-23 北京,更新于 2017-07-27 北京

該文章在以下平臺同步

附錄:

文中架構圖都是用graphviz繪制的,附上圖源碼

digraph G {
    size="6,6";
    label="services register"
    node [colorscheme=paired12, color=1, style=filled];
    register_center    [label="注冊中心", color=5, shape="record"]
    consumer    [label="服務消費者", color=4, shape="record"]
    service    [label="服務提供者", color=2, shape="record"]

    consumer -> register_center [label="2.訂閱"]
    register_center -> consumer [label="5.通知" style=dashed]
    
    consumer -> service [label="4.調用"]
    consumer -> consumer [label="3.緩存" style=dashed]
    service -> register_center [label="1.注冊"]
}


digraph G {
    size="6,6";
    label="load balance web servers"
    node [colorscheme=paired12, color=1, style=filled];
    nginx    [label="nginx", color=3, shape="record"]
    my_web_server_1    [label="my_web_server_1", color=4, shape="record"]
    my_web_server_2    [label="my_web_server_2", color=4, shape="record"]
    my_web_server_3    [label="my_web_server_3", color=4, shape="record"]

    {Client1 Client2 Client3} -> nginx [label="訪問"]
    
    nginx -> {my_web_server_1 my_web_server_2 my_web_server_3} [label="轉發"]
}

digraph G {
    size="6,6";
    label="Services register and find"
    node [colorscheme=paired12, color=1, style=filled];
    consul     [label="consul", color=1]
    consul_template     [label="consul_template", color=2]
    nginx    [label="nginx", color=3, shape="record"]
    registrator    [label="registrator", color=5]
    my_web_server_1    [label="my_web_server_1", color=4, shape="record"]
    my_web_server_2    [label="my_web_server_2", color=4, shape="record"]
    my_web_server_3    [label="my_web_server_3", color=4, shape="record"]

    {Client1 Client2 Client3} -> nginx [label="訪問"]
    nginx -> {my_web_server_1 my_web_server_2 my_web_server_3} [label="轉發"]
    {my_web_server_1 my_web_server_2 my_web_server_3} -> registrator [color="red",style="dashed",label="監控"]
    registrator -> consul [color="red",style="dashed",label="注冊"]
    consul -> consul_template [dir=both color=red style="dashed" label="訂閱服務"]
    
    consul_template -> nginx [color=red,style="dashed",label="配置更新"]
}


digraph G {
    size="6,6";
    label="Services register and find, consul cluster"
    node [colorscheme=paired12, color=1, style=filled];
    consul_node1     [label="consul_node1(leader)", color=7]
    consul_node2     [label="consul_node2", color=7]
    consul_node3     [label="consul_ndoe3", color=7]
    consul_client1     [label="consul_client1", color=7]
    consul_client2     [label="consul_client2", color=7]
    consul_client3     [label="consul_client3", color=7]
    consul_template     [label="consul_template", color=2]
    nginx    [label="nginx", color=3, shape="record"]
    registrator_1    [label="registrator_1", color=5]
    registrator_2    [label="registrator_2", color=5]
    registrator_3    [label="registrator_3", color=5]
    my_web_server_1    [label="my_web_server_1", color=4, shape="record"]
    my_web_server_2    [label="my_web_server_2", color=4, shape="record"]
    my_web_server_3    [label="my_web_server_3", color=4, shape="record"]
    my_web_server_4    [label="my_web_server_4", color=4, shape="record"]
    my_web_server_5    [label="my_web_server_5", color=4, shape="record"]
    my_web_server_6    [label="my_web_server_6", color=4, shape="record"]

    {Client1 Client2 Client3} -> nginx [label="訪問"]
    nginx -> {my_web_server_1 my_web_server_2 my_web_server_3 my_web_server_4 my_web_server_5 my_web_server_6} [label="轉發"]
    {my_web_server_1 my_web_server_2} -> registrator_1 [color="red",style="dashed",label="監控"]
    {my_web_server_3 my_web_server_4} -> registrator_2 [color="red",style="dashed",label="監控"]
    {my_web_server_5 my_web_server_6} -> registrator_3 [color="red",style="dashed",label="監控"]
    registrator_1 -> consul_client1 [color="red",style="dashed",label="注冊"]
    registrator_2 -> consul_client2 [color="red",style="dashed",label="注冊"]
    registrator_3 -> consul_client3 [color="red",style="dashed",label="注冊"]
    {consul_client1 consul_client2 consul_client3} -> consul_node1 [color="red",style="dashed",label="注冊"]
    consul_node1 -> consul_node2 -> consul_node3 [dir=both style=dashed color=blue]
    consul_node1 -> consul_template [dir=both color=red style="dashed" label="訂閱服務"]
   
    consul_template -> nginx [color=red,style="dashed",label="配置更新"]
    
    subgraph cluster_host_1 {
        label="host_1"
        my_web_server_1
        my_web_server_2
        registrator_1
        consul_client1
    }
    subgraph cluster_host_2 {
        label="host_2"
        my_web_server_3
        my_web_server_4
        registrator_2
        consul_client2
    }
    subgraph cluster_host_3 {
        label="host_3"
        my_web_server_5
        my_web_server_6
        registrator_3
        consul_client3
    }
    subgraph cluster_clu {
        label="consul cluster"
        consul_node1
        consul_node2
        consul_node3
    }
}


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容