在互聯網應用領域,服務的動態性需求十分常見,這就對服務的自動發現和可動態擴展提出了很高的要求。
微服務系統動輒上萬個服務,而且還要動態伸縮。以人工寫好的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進程信息以完成服務的自動注冊。
服務消費者:要使用服務提供者提供的服務,和服務提供者往往是動態相互轉位置的。
- 服務注冊:服務提供者到注冊中心注冊;
- 服務訂閱:服務消費者到注冊中心訂閱服務信息,對其進行監聽;
- 緩存:本地緩存服務列表,減少與注冊中心的網絡通信;
- 服務調用:先查找本地緩存,找不到再去注冊中心拉取服務地址,然后發送服務請求;
- 變更通知:服務節點變動時(新增、刪除等),注冊中心將通知監聽節點,更新服務信息。
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服務
這個很好理解吧,client訪問nginx,然后被轉發到后端某一個web server上,傳統的負載均衡。如果后端有添加/刪除web server,運維手動改下nginx.conf,然后重新載入配置,就可以調整負載均衡了。
再看看我們基于微服務自動注冊和發現模式下的負載均衡:
負載均衡的方式沒有變,只是多了一些外圍的組件,當然這些組件對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 的管理端
點擊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,可以看到它右側的服務節點數,這里只有一個,有多個的話會依次列出
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上先將registrator關閉,再關閉host-2上的后端web,我們的consul服務器可以感知到,但是那個consul ui界面沒更新,依然顯示兩個節點。
5. Consul Cluster
以上我們的實驗其實是個單點的consul服務,點擊consul ui頁面的NODES按鈕可以看到
只有一個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節點。
我們這次的架構有些調整,繪制一個服務器的邏輯上的部署圖來說明下
這是一張邏輯上服務部署的圖,我們找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 北京
該文章在以下平臺同步
- LIBERALMAN:
- CSDN:http://blog.csdn.net/socho/article/details/75434733
- 簡書:
- [1] 引用 http://tonybai.com/2015/07/06/implement-distributed-services-registery-and-discovery-by-consul/
- [2] 引用 http://alice.blog.51cto.com/707092/1896078
附錄:
文中架構圖都是用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
}
}