前言
VIP是虛擬的IP地址,並不對應於一個實際的物理網路介面。通過為一臺機器提供備用故障轉移選項,VIP可用於提供連線冗餘。
現在常用keepalived來實現VIP,結構圖如下
Keepalived問題
腦裂問題
在master和backup無法正常通行時(比如master和backup之間網路出現問題),backup會認為master已經掛掉,從而選舉自己成為master,開始履行master的職責,對外廣播GARP和VRRP報文。這時舊的master依然在正常工作,那麼我們在子網內將有兩個主機在同時告訴其他主機:“我是192.168.1.2。”這就是“腦裂問題”。
一般採用兩個辦法來防止腦裂問題。
- 仲裁。當兩個節點出現分歧時,由第三方的仲裁者決定聽誰的。
- fencing。當不能確定某個節點的狀態時,通過fencing把對方幹掉,確認共享資源被完全釋放。
成本問題
VRRP是一種典型的2N冗餘,往往需要兩個或以上的例項來保證單個VIP的高可用。但是在實際工程中,多個VIP同時出問題可能性是比較低的,在這種情況下,如果能用N+M(M<N)冗餘來達到效果無疑能達到節省成本的效果。
利用keepalived其實也可以得到部分N+M的效果,如下圖所示。這種方式需要注意配置檔案的編寫,這無疑也意味著運維成本的增加。
設計
本文會提出一種方法利用zookeeper來解決腦裂和成本問題,同時提供一些工具來幫助管理VIP。下面用ZVIP來指代這種方法。
整體設計比較簡單,如下圖所示:
演示
現在探討這些不同的角色以及每個角色需要執行的確切步驟。
假設要建立地址為192.168.1.2和192.168.1.3的VIP,如下圖所示。其中192.168.1.4(node1),192.168.1.5(node2)和192.168.1.5(node3)為實際的IP地址,在node1,node2和node3上執行agent。master執行在主機example.com上。
現在有了一個group,這個group裡有2個VIP和3個節點。這個group中有兩個角色
- master。master負責管理group
- agent。agent承擔真正VIP的功能。
下面通過zkCli工具來演示工作的流程。
系統初始化
建立/groups, /nodes, /tasks這些znode。
[zk: localhost:2181(CONNECTED) 0] create /groups ""
Created /groups
[zk: localhost:2181(CONNECTED) 1] create /nodes ""
Created /nodes
[zk: localhost:2181(CONNECTED) 4] create /tasks ""
Created /tasks
[zk: localhost:2181(CONNECTED) 5] ls /
[groups, nodes, zookeeper, tasks]
複製程式碼
當一個agent服務起來時,會在/nodes下注冊一個znode並且watch建立的znode。也會在/tasks下執行同樣的操作。
# For node1
[zk: localhost:2181(CONNECTED) 7] create /nodes/node-192-168-1-4 ""
Created /nodes/node-192-168-1-3
[zk: localhost:2181(CONNECTED) 9] ls /nodes/node-192-168-1-4 true
[]
# For node1
[zk: localhost:2181(CONNECTED) 7] create /tasks/node-192-168-1-4 ""
Created /nodes/node-192-168-1-4
[zk: localhost:2181(CONNECTED) 9] ls /tasks/node-192-168-1-4 true
[]
複製程式碼
設定一個group
一個group總是被手動建立。建立者一定知道VIP的地址並且會知道真實機器的資訊。建立者通過master來設定一個group(通過ui介面或者呼叫介面)。
master建立一個znode /groups/group1;然後在各個node下建立這個group。
# For master
[zk: localhost:2181(CONNECTED) 10] create /groups/group1 ""
Created /groups/group1
[zk: localhost:2181(CONNECTED) 11] create /groups/group1/vips ""
Created /groups/group1
[zk: localhost:2181(CONNECTED) 12] create /groups/group1/nodes ""
Created /groups/group1/nodes
[zk: localhost:2181(CONNECTED) 13] create /groups/group1/vips/vip-192-168-1-2 some-config
Created /groups/group1/vips/vip-192-168-1-2
[zk: localhost:2181(CONNECTED) 14] create /groups/group1/vips/vip-192-168-1-3 some-config
Created /groups/group1/vips/vip-192-168-1-3
[zk: localhost:2181(CONNECTED) 15] create /nodes/node-192-168-1-4/group1 some-config
Created /nodes/node-192-168-1-4/group1
[zk: localhost:2181(CONNECTED) 16] create /nodes/node-192-168-1-5/group1 some-config
Created /nodes/node-192-168-1-5/group1
[zk: localhost:2181(CONNECTED) 17] create /nodes/node-192-168-1-6/group1 some-config
Created /nodes/node-192-168-1-6/group1
複製程式碼
node1因為之前監聽了*/nodes/node-192-168-1-4這個節點,所以會接受到zookeeper的通知,node1去讀取/nodes/node-192-168-1-4下面的znode,更新自己的配置。然後在/groups/group1/nodes*下去建立一個臨時znode。
# For node1
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/nodes/node-192-168-1-4
[zk: localhost:2181(CONNECTED) 18] create -e -s /groups/group1/nodes/node-192-168-1-4 ""
Created /groups/group1/nodes/node-192-168-1-4
[zk: localhost:2181(CONNECTED) 19] ls /groups/group1/vips
[vip-192-168-1-2, vip-192-168-1-3]
[zk: localhost:2181(CONNECTED) 20] ls /groups/group1/vips/vip-192-168-1-2 true
[]
[zk: localhost:2181(CONNECTED) 21] ls /groups/group1/vips/vip-192-168-1-3 true
[]
複製程式碼
當master為*/groups/group1/vips*下面的節點增加子節點時,agent會得到zookeeper的通知,通過獲取節點的內容,可以知道自己是不是被master選中,從而選擇是否去執行VIP的職責。
agent執行命令
現在master向node1傳送命令,需要node1去執行。流程如下。 master在*/tasks/node-192-168-1-4*下建立znode,並且監聽其變化
# For master
[zk: localhost:2181(CONNECTED) 5] create -s /tasks/node-192-168-1-4/task- "job definition"
Created /tasks/node-192-168-1-4/task-0000000000
[zk: localhost:2181(CONNECTED) 6] ls /tasks/node-192-168-1-4/task-0000000000 true
[]
複製程式碼
node1之前已經監聽過了*/tasks/node-192-168-1-4*,所以會得到zookeeper的通知,這時node1會去讀取*/tasks/node-192-168-1-4下面的節點,拿到要執行的任務,在執行完成後,在/tasks//tasks/node-192-168-1-4/task-0000000000*中新增一個狀態znode
# For agent
[zk: localhost:2181(CONNECTED) 5] create /tasks/node-192-168-1-4/task-0000000000/status "done"
Created /tasks/node-192-168-1-4/task-0000000000/status
複製程式碼
master因為監聽了/tasks/node-192-168-1-4/task-0000000000節點,所以會接到zookeeper的通知,知道任務已經完成。
VIP選舉
VIP由master選擇,master會從*/groups/group1/nodes中獲取nodes列表,並選擇一個node,向其傳送一個任務,告訴其讓來執行VIP的職責;node執行完成後通知上面的方式告知master任務完成,同時在/groups/group1/vips/vip-192-168-1-3下注冊一個臨時節點。master會監聽這個/groups/group1/vips/vip-192-168-1-3*這個節點,在node掛掉的時候master就能獲取通知。
VIP Failover
master Failover
要處理master掛掉的情況,我們需要有一個備份的master。當主master掛掉的時候,備份master會接管。
因為只有一個程式能成為為master,所以master程式必須通過某種方式不讓其他master獲取master許可權。
每個master啟動時,會在/masters/下建立一個臨時節點。當節點建立成功,其他嘗試建立同名znode的master會報錯,就此得知master這個角色已經被佔有的。其他的master會監聽這個znode。也會和主master一樣監聽/groups和/tasks下面的節點。
# For master example.shopee.com
[zk: localhost:2181(CONNECTED) 11] create /masters/master-shopee-com:1212 ""
Created /masters/master-shopee-com:1212
複製程式碼
master每次執行分配任務時,必須確認自己的master身份。
Agent Failover
當一個agent掛掉時,比如node1掛掉時,/groups/group1/nodes/node-192-168-1-4這個臨時節點會被刪除,master會得到zookeeper 的通知,從而開始選擇新的節點作為VIP。
,歡迎大家關注我的微信公眾號《派森公園》。