Consul內(nèi)核
簡(jiǎn)介
這篇文檔將介紹Consul的內(nèi)核知識(shí),想要在生產(chǎn)環(huán)境中正確地使用Consul,必須了解其內(nèi)部原理,我們會(huì)從以下幾個(gè)方面了解Consul的運(yùn)行方式:
- 架構(gòu)
- Consensus 協(xié)議
- Gossip 協(xié)議
- 網(wǎng)絡(luò)協(xié)作
- 會(huì)話
- 反熵(Anti-Entropy)
- 安全模式
- Jepsen 測(cè)試
架構(gòu)
Consul是一個(gè)復(fù)雜的系統(tǒng),具有許多不同的活動(dòng)組件,為了幫助Consul用戶(hù)及開(kāi)發(fā)人員更好的了解其工作模型,我們?cè)诒菊鹿?jié)中會(huì)描述Consul的系統(tǒng)體系結(jié)構(gòu)。
在討論體系結(jié)構(gòu)之前,我們建議用戶(hù)先閱讀術(shù)語(yǔ)表,了解Consul的一些概念術(shù)語(yǔ),在生產(chǎn)中部署Consul時(shí),可以結(jié)合本文檔做一些參考。
俯瞰圖
Consul的架構(gòu)俯瞰圖如下:

我們分解一下圖像,對(duì)每個(gè)部分逐一進(jìn)行描述。首先,我們可以看到有兩個(gè)數(shù)據(jù)中心,分別標(biāo)記為DATACENTER 1和DATACENETR 2。 Consul對(duì)多個(gè)數(shù)據(jù)中心提供一流的支持,并且在生產(chǎn)環(huán)境是常見(jiàn)現(xiàn)象。
在每個(gè)數(shù)據(jù)中心內(nèi),都有一些客戶(hù)端和服務(wù)端機(jī)器。服務(wù)端機(jī)器推薦使用3臺(tái)或5臺(tái),在出現(xiàn)故障的情況下,可用性和性能之間容易取得平衡,如果服務(wù)端添加更多服務(wù)器,那么服務(wù)器間達(dá)成Consensus協(xié)議會(huì)比較慢。然而,客戶(hù)端服務(wù)器數(shù)量沒(méi)有限制,他們可以輕松擴(kuò)展到成千上萬(wàn)個(gè)。
數(shù)據(jù)中心中的所有代理都會(huì)參與Gossip協(xié)議,這會(huì)建立一個(gè)Gossip Pool,其中包含給定數(shù)據(jù)中心的所有代理。這樣做有幾個(gè)目的:首先,不需要為客戶(hù)端配置服務(wù)端地址,地址發(fā)現(xiàn)是自動(dòng)完成的;其次,檢測(cè)代理故障的工作沒(méi)有局限在服務(wù)端,而是分散處理了,這使得故障檢測(cè)機(jī)制相比心跳方案更具可伸縮性,它還為節(jié)點(diǎn)提供故障檢測(cè),如果代理不可訪問(wèn),則說(shuō)明該節(jié)點(diǎn)可能已發(fā)生故障;第三,它用作傳遞消息,在發(fā)生重要事件時(shí)發(fā)出通知(例如,領(lǐng)導(dǎo)者選舉)。
每個(gè)數(shù)據(jù)中心中的服務(wù)端都是單個(gè)Raft對(duì)等集的一部分。這意味著它們將共同選舉一個(gè)領(lǐng)導(dǎo)者(該領(lǐng)導(dǎo)者機(jī)器具有一些額外職責(zé))。領(lǐng)導(dǎo)者負(fù)責(zé)處理所有查詢(xún)和事務(wù)。作為Consensus協(xié)議的一部分,事務(wù)必須復(fù)制到所有片區(qū)(peer),所以當(dāng)非領(lǐng)導(dǎo)服務(wù)器收到RPC請(qǐng)求時(shí),它將先轉(zhuǎn)發(fā)給群集領(lǐng)導(dǎo)者。
服務(wù)器代理還充當(dāng)WAN gossip pool的一部分運(yùn)行,該池與LAN池不同,因?yàn)樗槍?duì)Internet的更高延遲進(jìn)行了優(yōu)化,并且將僅包含其他Consul服務(wù)端代理。該池的目的是允許數(shù)據(jù)中心以低接觸(low-touch)的方式發(fā)現(xiàn)彼此。在線連接新的數(shù)據(jù)中心就像加入現(xiàn)有的WAN gossip pool一樣容易。由于服務(wù)端都在該池中運(yùn)行,因此它也支持跨數(shù)據(jù)中心(cross-datacenter)請(qǐng)求。服務(wù)端收到對(duì)其他數(shù)據(jù)中心的請(qǐng)求時(shí),會(huì)將其轉(zhuǎn)發(fā)到正確數(shù)據(jù)中心中的隨機(jī)服務(wù)器,該服務(wù)器然后可以轉(zhuǎn)發(fā)給本地領(lǐng)導(dǎo)者。
這樣,數(shù)據(jù)中心之間的耦合會(huì)非常低,但是由于故障檢測(cè),連接緩存和多路復(fù)用等機(jī)制,跨數(shù)據(jù)中心的請(qǐng)求相對(duì)較快且可靠。
通常情況下,不同的Consul數(shù)據(jù)中心之間不會(huì)復(fù)制數(shù)據(jù)。當(dāng)請(qǐng)求另一個(gè)數(shù)據(jù)中心中的資源時(shí),本地Consul服務(wù)器會(huì)將RPC請(qǐng)求轉(zhuǎn)發(fā)到該資源的遠(yuǎn)程Consul服務(wù)器并返回結(jié)果。如果遠(yuǎn)程數(shù)據(jù)中心不可用,那么那些資源也將不可用,但是不會(huì)以其他方式影響本地?cái)?shù)據(jù)中心。在某些特殊情況下,可以復(fù)制有限的數(shù)據(jù)子集,例如使用Consul的內(nèi)置ACL復(fù)制功能,或使用外部工具(如consul-replicate)。
在某些地方,客戶(hù)端代理可能會(huì)緩存來(lái)自服務(wù)端的數(shù)據(jù),以提高本地的性能和可靠性。示例包括連接證書(shū)和意圖(intention),這些證書(shū)和意圖使客戶(hù)端代理可以在無(wú)需請(qǐng)求服務(wù)器的情況下就做出相應(yīng)。一些API端點(diǎn)還支持緩存結(jié)果可選,這也有助于提高可靠性,因?yàn)榧词古c服務(wù)器連接中斷,或服務(wù)器暫時(shí)不可用時(shí),本地代理也可以使用緩存繼續(xù)響應(yīng)某些查詢(xún),例如服務(wù)發(fā)現(xiàn)或者鏈接授權(quán)。
Consensus 協(xié)議
Consul使用Consensus協(xié)議來(lái)提供一致性(由CAP定義)。 Consensus協(xié)議基于Raft協(xié)議,有關(guān)Raft協(xié)議的直觀說(shuō)明,可以參考http://thesecretlivesofdata.com/raft/
Raft 協(xié)議簡(jiǎn)介
Raft協(xié)議是基于Paxos算法的,與Paxos相比,Raft具有更少狀態(tài),更簡(jiǎn)單,也更易理解,在討論Raft時(shí),首先需要了解一些專(zhuān)業(yè)術(shù)語(yǔ):
-
日志(Log):Raft系統(tǒng)中的主要工作單元是日志記錄,一致性問(wèn)題可以分解為日志副本。日志是記錄的有序序列,記錄包括任意群集更改:添加節(jié)點(diǎn),添加服務(wù),新建鍵值對(duì)等。如果所有成員都認(rèn)可記錄及其順序,則我們認(rèn)為日志是一致的。 -
有限狀態(tài)機(jī)(FSM):FSM是有限狀態(tài)集合,啟用新日志時(shí),F(xiàn)SM狀態(tài)會(huì)跟隨著切換,應(yīng)用相同的日志序列最后會(huì)得到相同的狀態(tài),這意味著行為有必然性。 -
副本集(Peer Set):副本集是指所有參與日志復(fù)制的成員集合,在Consul中,所有服務(wù)端節(jié)點(diǎn)都在本地?cái)?shù)據(jù)中心的副本集中。 -
仲裁人數(shù)(Quorum):仲裁人數(shù)對(duì)應(yīng)副本集中的大多數(shù)成員:對(duì)于大小為n的集合,至少需要(n + 1/ 2個(gè)節(jié)點(diǎn)。例如,如果在副本集中有5個(gè)成員,則需要3個(gè)節(jié)點(diǎn)來(lái)形成仲裁。如果由于某些原因無(wú)法達(dá)到法定數(shù)量的節(jié)點(diǎn),則群集將變得不可用,并且無(wú)法提交新日志。 -
提交的記錄(Committed Entry):當(dāng)提交記錄存儲(chǔ)在一定數(shù)量的節(jié)點(diǎn)上時(shí),該記錄被視為已提交,記錄提交后即可應(yīng)用。 -
領(lǐng)導(dǎo)者(Leader):在任何時(shí)間,副本集都會(huì)選擇一個(gè)節(jié)點(diǎn)作為領(lǐng)導(dǎo)者,領(lǐng)導(dǎo)者會(huì)負(fù)責(zé)拉取新的日志記錄,并將其復(fù)制到跟隨者(follower),最后提交日志。
Raft是一個(gè)復(fù)雜的協(xié)議,此處不做詳細(xì)介紹,我們僅從更高層次對(duì)其構(gòu)建的框架稍加描述。
Raft節(jié)點(diǎn)有三種狀態(tài):跟隨者(follower)、候選者(candidate)或領(lǐng)導(dǎo)者(leader)。所有節(jié)點(diǎn)的最初狀態(tài)為跟隨者,在這種狀態(tài)下,節(jié)點(diǎn)可以接受來(lái)自領(lǐng)導(dǎo)者的日志記錄并進(jìn)行投票。如果一段時(shí)間未收到任何記錄,則節(jié)點(diǎn)會(huì)自動(dòng)升級(jí)為候選狀態(tài)。在候選狀態(tài)下,節(jié)點(diǎn)會(huì)請(qǐng)求其他節(jié)點(diǎn)等待投票。如果候選人獲得法定人數(shù)的選票,則將晉升為領(lǐng)導(dǎo)人。領(lǐng)導(dǎo)者必須接受新的日志記錄并復(fù)制到所有其他關(guān)注者。此外,如果讀取過(guò)時(shí)數(shù)據(jù)(stale reads)是不可接受的,那么所有查詢(xún)也必須在領(lǐng)導(dǎo)者上執(zhí)行。
當(dāng)集群擁有領(lǐng)導(dǎo)者后,就可以接受新的日志記錄??蛻?hù)端可以請(qǐng)求領(lǐng)導(dǎo)者添加新日志記錄(從Raft的角度來(lái)看,日志記錄是二進(jìn)制blob格式)。然后,領(lǐng)導(dǎo)者會(huì)將記錄寫(xiě)入持久性存儲(chǔ),并嘗試復(fù)制到一定數(shù)量的跟隨者。一旦日志記錄被認(rèn)為已提交,就可以將其應(yīng)用到有限狀態(tài)機(jī)。有限狀態(tài)機(jī)是專(zhuān)用的,在Consul中,我們使用MemDB維護(hù)集群狀態(tài)。Consul在提交并使用日志記錄后再執(zhí)行寫(xiě)操作,查詢(xún)使用強(qiáng)一致性模型時(shí),可以實(shí)現(xiàn)寫(xiě)后再讀。
顯然,我們不希望備份的日志無(wú)限制地增長(zhǎng)。 Raft提供了一種機(jī)制,通過(guò)該機(jī)制可以對(duì)當(dāng)前狀態(tài)捕捉快照并壓縮日志。由于FSM抽象,恢復(fù)FSM狀態(tài)需要重播老的日志記錄,這使Raft可以在某個(gè)時(shí)間點(diǎn)捕獲FSM狀態(tài),然后刪除用于達(dá)到該狀態(tài)的所有日志。這些是自動(dòng)執(zhí)行的,無(wú)需用戶(hù)干預(yù),這樣可以防止磁盤(pán)無(wú)限制使用,同時(shí)還可以最大程度地減少重播日志所花費(fèi)的時(shí)間。使用MemDB的優(yōu)點(diǎn)之一是,即使對(duì)舊狀態(tài)打快照時(shí),它也允許Consul繼續(xù)接受新事務(wù),從而避免了任何可用性問(wèn)題。
Consensus 協(xié)議是可以容錯(cuò)的,前提是需要達(dá)到法定人數(shù)下限。如果沒(méi)有達(dá)到法定節(jié)點(diǎn)數(shù),則無(wú)法處理日志記錄。例如,假設(shè)只有2個(gè)節(jié)點(diǎn):A和B,那么仲裁人數(shù)也為2,這意味著兩個(gè)節(jié)點(diǎn)必須都同意提交日志記錄。如果A或B失敗,則現(xiàn)在無(wú)法達(dá)到法定人數(shù),這意味著群集無(wú)法添加或刪除節(jié)點(diǎn),也無(wú)法提交任何其他日志記錄,這將導(dǎo)致集群不可用。此時(shí),將需要手動(dòng)干預(yù)才能刪除A或B節(jié)點(diǎn),并以引導(dǎo)程序模式重新啟動(dòng)其余節(jié)點(diǎn)。
3個(gè)節(jié)點(diǎn)的Raft群集可以容忍單個(gè)節(jié)點(diǎn)故障,而5個(gè)節(jié)點(diǎn)的Raft群集可以容忍2個(gè)節(jié)點(diǎn)故障。推薦的配置是每個(gè)數(shù)據(jù)中心運(yùn)行3或5個(gè)Consul服務(wù)器。這樣可以在不嚴(yán)重降低性能的情況下最大化可用性。在部署參考一節(jié)中總結(jié)了可用的群集大小選項(xiàng)以及每個(gè)選項(xiàng)的容錯(cuò)能力。
在性能方面,Raft可以媲美Paxos。假設(shè)領(lǐng)導(dǎo)者穩(wěn)定,提交日志記錄需要單次往返集群一半機(jī)器。因此,性能受磁盤(pán)I/O和網(wǎng)絡(luò)延遲的約束。盡管Consul并非旨在成為高吞吐量寫(xiě)入系統(tǒng),但它應(yīng)根據(jù)網(wǎng)絡(luò)和硬件配置每秒處理數(shù)百至數(shù)千個(gè)事務(wù)。
Consul中的Raft協(xié)議
只有Consul服務(wù)端節(jié)點(diǎn)參與了Raft協(xié)議,并作為復(fù)制集的一部分。所有客戶(hù)端節(jié)點(diǎn)都將請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)端。這樣設(shè)計(jì)的部分原因是,隨著將更多成員添加到復(fù)制集,仲裁的數(shù)量也會(huì)增加,這會(huì)引入性能問(wèn)題,因?yàn)橛脩?hù)可能在等待數(shù)百臺(tái)機(jī)器就一項(xiàng)輸入達(dá)成共識(shí),而不是一小部分。
開(kāi)始時(shí),單個(gè)Consul服務(wù)器將進(jìn)入引導(dǎo)(bootstrap)模式。此模式可以選舉自己為領(lǐng)導(dǎo)者。選出領(lǐng)導(dǎo)者后,可以將其他服務(wù)器添加到復(fù)制集,以保持一致性和安全性,一旦添加了頭幾臺(tái)服務(wù)器,就可以禁用引導(dǎo)模式了。
由于所有服務(wù)端機(jī)器都作為復(fù)制集的一部分,因此它們都知道當(dāng)前的領(lǐng)導(dǎo)者是誰(shuí)。當(dāng)RPC請(qǐng)求到達(dá)非領(lǐng)導(dǎo)者服務(wù)器時(shí),它們會(huì)將請(qǐng)求轉(zhuǎn)發(fā)給領(lǐng)導(dǎo)。如果RPC是查詢(xún)(query)類(lèi)型,則表示它是只讀的,領(lǐng)導(dǎo)程序?qū)⒏鶕?jù)FSM的當(dāng)前狀態(tài)生成結(jié)果。如果RPC是事務(wù)(transaction)類(lèi)型,這意味著它會(huì)修改狀態(tài),則領(lǐng)導(dǎo)者將生成一個(gè)新的日志記錄,并使用Raft應(yīng)用它。一旦提交了日志記錄并將其應(yīng)用于FSM,事務(wù)就完成了。
由于Raft復(fù)制的性質(zhì),性能對(duì)網(wǎng)絡(luò)延遲很敏感。因此,每個(gè)數(shù)據(jù)中心選舉一個(gè)獨(dú)立的領(lǐng)導(dǎo)者,并維護(hù)一個(gè)不相交的復(fù)制集。數(shù)據(jù)按數(shù)據(jù)中心進(jìn)行分區(qū),因此每個(gè)領(lǐng)導(dǎo)者僅負(fù)責(zé)其數(shù)據(jù)中心中的數(shù)據(jù)。收到對(duì)遠(yuǎn)程數(shù)據(jù)中心的請(qǐng)求后,該請(qǐng)求將轉(zhuǎn)發(fā)給正確的領(lǐng)導(dǎo)者。這種設(shè)計(jì)可在不犧牲一致性的情況下實(shí)現(xiàn)較低的事務(wù)延遲和較高的可用性。
一致性模式
盡管所有復(fù)制日志的寫(xiě)入都通過(guò)Raft協(xié)議,但讀取操作比較靈活,為了支持開(kāi)發(fā)人員可能需要的各種折衷方案,Consul支持3種不同的讀取一致性模式,分別是:
-
默認(rèn)(default):Raft利用領(lǐng)導(dǎo)者的租賃(leasing),提供了一個(gè)時(shí)間窗口,領(lǐng)導(dǎo)者在該時(shí)間窗口內(nèi)假設(shè)為穩(wěn)定狀態(tài)。但是,如果將領(lǐng)導(dǎo)者與其余服務(wù)器分開(kāi),則可以在舊領(lǐng)導(dǎo)者持有租約的同時(shí)選出新領(lǐng)導(dǎo)者。這意味著有2個(gè)領(lǐng)導(dǎo)節(jié)點(diǎn),由于老領(lǐng)導(dǎo)者將無(wú)法提交新日志,因此不會(huì)出現(xiàn)腦裂的風(fēng)險(xiǎn)。但是,如果舊的領(lǐng)導(dǎo)者提供任何讀取服務(wù),則這些值可能會(huì)過(guò)時(shí)。默認(rèn)的一致性模式僅依賴(lài)于領(lǐng)導(dǎo)者租賃,使客戶(hù)可能讀取到過(guò)時(shí)的數(shù)據(jù)。此模式下讀取速度快,通常具有很強(qiáng)的一致性,僅在非常意外的情況下才會(huì)發(fā)生數(shù)據(jù)過(guò)時(shí)。 -
強(qiáng)一致(consistent):該模式可以提供強(qiáng)一致性,它要求領(lǐng)導(dǎo)者及其仲裁者核實(shí)它依然是領(lǐng)導(dǎo)者,這為所有服務(wù)器節(jié)點(diǎn)都引入了額外的往返。此模式下,讀取的數(shù)據(jù)始終是一致的,但會(huì)因?yàn)轭~外的交互增加延遲。 -
弱一致(stale):該模式下,任何服務(wù)器都可以提供查詢(xún)服務(wù),不管其是否為引導(dǎo)服務(wù)器。這意味著讀取的數(shù)據(jù)可能是非常陳舊的,但通常在領(lǐng)導(dǎo)者50ms范圍內(nèi)。此模式下,查詢(xún)非常迅速并支持橫向擴(kuò)展,但是可能會(huì)查詢(xún)到過(guò)時(shí)的值。此模式允許無(wú)領(lǐng)導(dǎo)下的讀取,這意味著不可用的群集仍將能夠響應(yīng)請(qǐng)求。
部署參考
下面的表格顯示了不同服務(wù)器個(gè)數(shù)的集群的仲裁數(shù)大?。╭uorum)和容錯(cuò)能力(failure tolerance),服務(wù)器推薦使用3臺(tái)或者5臺(tái),不建議使用單臺(tái)服務(wù)器部署,這樣在發(fā)生故障時(shí),不可避免會(huì)丟失數(shù)據(jù)。
| 服務(wù)器數(shù) | 仲裁數(shù) | 容錯(cuò)能力 |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |
Gossip 協(xié)議
Consul使用Gossip協(xié)議來(lái)管理成員資格并向集群廣播消息,所有這些都是通過(guò)使用Serf庫(kù)來(lái)實(shí)現(xiàn)的。 Serf使用的Gossip協(xié)議基于SWIM:可擴(kuò)展的弱一致性感染型過(guò)程組成員身份協(xié)議(Scalable Weakly-consistent Infection-style Process Group Membership Protocol),并進(jìn)行了一些小改動(dòng),有關(guān)Serf的更多細(xì)節(jié)可以參考其他文檔。
Consul中的Gossip協(xié)議
Consul中維護(hù)兩個(gè)不同的Gossip Pool,分別局域網(wǎng)池(LAN pool)和廣域網(wǎng)池(WAN pool)。每個(gè)Consul數(shù)據(jù)中心內(nèi)部都有一個(gè)局域網(wǎng)池(LAN pool),其中包含了數(shù)據(jù)中心的所有成員(客戶(hù)端和服務(wù)端)。局域網(wǎng)池(LAN pool)有多種用途,成員資格信息(Membership Information)使客戶(hù)端可以自動(dòng)發(fā)現(xiàn)服務(wù)端,從而減少了所需的配置量。分布式故障檢測(cè)允許故障檢測(cè)工作在整個(gè)群集中共享,而不是集中在幾臺(tái)服務(wù)器上。最后,Gossip Pool支持進(jìn)行可靠且快速的事件廣播。
廣域網(wǎng)池(WAN pool)在全局范圍內(nèi)是唯一的,所有服務(wù)端機(jī)器都參與WAN pool,與數(shù)據(jù)中心無(wú)關(guān)。 廣域網(wǎng)池提供的成員資格信息允許服務(wù)器執(zhí)行跨數(shù)據(jù)中心請(qǐng)求,集成的故障檢測(cè)機(jī)制使Consul可以?xún)?yōu)雅地處理整個(gè)數(shù)據(jù)中心失去連接的情況,或僅處理遠(yuǎn)程數(shù)據(jù)中心中的單個(gè)服務(wù)器異常。
Lifeguard 增強(qiáng)
所有這些功能都是通過(guò)使用Serf第三方庫(kù)提供的。從用戶(hù)的角度來(lái)看,這些并不重要,因?yàn)檫@些都被Consul覆蓋了。但是,對(duì)于開(kāi)發(fā)人員而言,了解如何利用該庫(kù)可能會(huì)比較有用。
SWIM假定本地節(jié)點(diǎn)是健康的,從某種意義上來(lái)說(shuō),可以對(duì)數(shù)據(jù)包進(jìn)行軟實(shí)時(shí)處理(real-time processing)。但是,在本地節(jié)點(diǎn)遇到CPU或網(wǎng)絡(luò)耗盡的情況下,可能會(huì)違反此假設(shè)。 結(jié)果是,serfHealth檢查狀態(tài)可能會(huì)發(fā)生抖動(dòng),從而會(huì)導(dǎo)致錯(cuò)誤的監(jiān)控報(bào)警,給遙測(cè)增加噪音,并導(dǎo)致整個(gè)群集浪費(fèi)了CPU和網(wǎng)絡(luò)資源。
通過(guò)對(duì)SWIM進(jìn)行增強(qiáng),Lifeguard可以完全解決此問(wèn)題,有關(guān)Lifeguard的更多細(xì)節(jié)可以參考其他文檔。
網(wǎng)絡(luò)坐標(biāo)
Consul使用網(wǎng)絡(luò)層析成像(network tomography)系統(tǒng)為群集中的節(jié)點(diǎn)計(jì)算網(wǎng)絡(luò)坐標(biāo)。這些坐標(biāo)允許使用非常簡(jiǎn)單的算法來(lái)估計(jì)任意兩個(gè)節(jié)點(diǎn)之間的網(wǎng)絡(luò)往返時(shí)間。這個(gè)功能可以構(gòu)建很多實(shí)用的功能,例如查找離請(qǐng)求節(jié)點(diǎn)最近的服務(wù)節(jié)點(diǎn),或?qū)⒐收限D(zhuǎn)移到離當(dāng)前節(jié)點(diǎn)最近的數(shù)據(jù)中心。
所有這些都是通過(guò)使用Serf庫(kù)實(shí)現(xiàn)的。Serf的網(wǎng)絡(luò)斷層掃描基于Vivaldi(分散式網(wǎng)絡(luò)坐標(biāo)系),并在其基礎(chǔ)上進(jìn)行了一些改進(jìn)。
Consul中的網(wǎng)絡(luò)坐標(biāo)
網(wǎng)絡(luò)坐標(biāo)在Consul內(nèi)部表現(xiàn)為以下幾種方式:
-
consul rtt命令可以查詢(xún)?nèi)魏蝺蓚€(gè)節(jié)點(diǎn)之間的網(wǎng)絡(luò)往返時(shí)間; -
Catalog Endpoint和Health Endpoint可以拼接?near=參數(shù),根據(jù)給定節(jié)點(diǎn)的網(wǎng)絡(luò)往返時(shí)間對(duì)查詢(xún)結(jié)果進(jìn)行排序; -
Prepared Queries可以根據(jù)網(wǎng)絡(luò)往返時(shí)間自動(dòng)將服務(wù)故障轉(zhuǎn)移到其他Consul數(shù)據(jù)中心; -
Coordinate Endpoint公開(kāi)原始網(wǎng)絡(luò)坐標(biāo)以供其他應(yīng)用程序使用。
Consul使用Serf管理兩個(gè)不同的Gossip Pool,一個(gè)是用于管理本地?cái)?shù)據(jù)中心的LAN,另一個(gè)是用于管理所有數(shù)據(jù)中心的WAN。重要的是要注意,這兩個(gè)池之間的網(wǎng)絡(luò)坐標(biāo)不兼容。LAN坐標(biāo)僅在與其他LAN坐標(biāo)一起計(jì)算時(shí)才有意義,而WAN坐標(biāo)也僅在與其他WAN坐標(biāo)一起使用才有意義。
使用網(wǎng)絡(luò)坐標(biāo)
一旦有了它們的坐標(biāo),就可以很容易計(jì)算出任意兩個(gè)節(jié)點(diǎn)之間的估計(jì)網(wǎng)絡(luò)往返時(shí)間,這是從Coordinate Endpoint返回的示例坐標(biāo):
"Coord": {
"Adjustment": 0.1,
"Error": 1.5,
"Height": 0.02,
"Vec": [0.34,0.68,0.003,0.01,0.05,0.1,0.34,0.06]
}
所有值都是以秒為單位的浮點(diǎn)數(shù),除誤差項(xiàng)不用于距離計(jì)算外,下面是Go語(yǔ)言實(shí)現(xiàn)的完整示例,展示如何計(jì)算兩個(gè)坐標(biāo)之間的距離:
import (
"math"
"time"
"github.com/hashicorp/serf/coordinate"
)
func dist(a *coordinate.Coordinate, b *coordinate.Coordinate) time.Duration {
// Coordinates will always have the same dimensionality, so this is
// just a sanity check.
if len(a.Vec) != len(b.Vec) {
panic("dimensions aren't compatible")
}
// Calculate the Euclidean distance plus the heights.
sumsq := 0.0
for i := 0; i < len(a.Vec); i++ {
diff := a.Vec[i] - b.Vec[i]
sumsq += diff * diff
}
rtt := math.Sqrt(sumsq) + a.Height + b.Height
// Apply the adjustment components, guarding against negatives.
adjusted := rtt + a.Adjustment + b.Adjustment
if adjusted > 0.0 {
rtt = adjusted
}
// Go's times are natively nanoseconds, so we convert from seconds.
const secondsToNanoseconds = 1.0e9
return time.Duration(rtt * secondsToNanoseconds)
}
會(huì)話
Consul提供了一種會(huì)話機(jī)制,可用于構(gòu)建分布式鎖。會(huì)話充當(dāng)節(jié)點(diǎn)(node),健康檢查(health check)和鍵/值數(shù)據(jù)集之間的綁定層。它們旨在提供細(xì)粒度的鎖策略,這個(gè)概念大部分來(lái)源自http://research.google.com/archive/chubby.html。
會(huì)話設(shè)計(jì)
Consul中的會(huì)話包含許多概念信息,當(dāng)創(chuàng)建完會(huì)話后,會(huì)有對(duì)應(yīng)的節(jié)點(diǎn)名稱(chēng)(node name)、健康檢查列表(health check)、行為(behavior)、TTL和鎖定延遲(lock-delay)。新建會(huì)話可以得到一個(gè)唯一ID,該ID可以與KV集合一起使用可以獲取鎖(用于互斥機(jī)制)。
下圖顯示了這些組件之間的關(guān)系:

當(dāng)發(fā)生以下任何一種情況時(shí),會(huì)話將失效:
- 節(jié)點(diǎn)被注銷(xiāo)
- 所有健康檢查都被注銷(xiāo)
- 所有健康檢查都進(jìn)入預(yù)警狀態(tài)
- 會(huì)話被顯式銷(xiāo)毀
- TTL過(guò)期
當(dāng)會(huì)話失效后,該會(huì)話會(huì)被銷(xiāo)毀,并無(wú)法再使用。與會(huì)話關(guān)聯(lián)的鎖可能會(huì)發(fā)生多種情況,這取決于創(chuàng)建鎖時(shí)指定的行為,Consul支持釋放和刪除兩種行為,默認(rèn)是選擇釋放。
如果選擇釋放行為,那么與會(huì)話關(guān)聯(lián)的所有鎖都將被釋放,并且密鑰的ModifyIndex會(huì)累加;如果選擇刪除行為,則會(huì)刪除所有與之對(duì)應(yīng)的密鑰,這可以用于創(chuàng)建由Consul自動(dòng)刪除的臨時(shí)項(xiàng)(ephemeral entry)。
盡管這是一個(gè)簡(jiǎn)單的設(shè)計(jì),但它可以實(shí)現(xiàn)多種使用模式。默認(rèn)情況下,基于Gossip 協(xié)議的故障檢測(cè)器用于健康檢查,該故障檢測(cè)器使Consul可以檢測(cè)到持有鎖的節(jié)點(diǎn)何時(shí)發(fā)生故障,并自動(dòng)釋放鎖。此功能為Consul鎖機(jī)制提供了生命力,也就是說(shuō),在發(fā)生錯(cuò)誤的情況下,系統(tǒng)可以繼續(xù)維持運(yùn)轉(zhuǎn)。但是,也因?yàn)闆](méi)有更完善的故障檢測(cè)機(jī)制,有時(shí)即使鎖擁有者仍然存活著,也可能會(huì)產(chǎn)生誤報(bào)(檢測(cè)到故障),這會(huì)導(dǎo)致鎖被意外釋放,這樣我們機(jī)會(huì)犧牲一些安全性。
相反,可以創(chuàng)建不關(guān)聯(lián)任何健康檢查的會(huì)話,這樣就消除了誤報(bào)的可能性,以可用性換取安全性。用戶(hù)可以確定的是,即使現(xiàn)在持有鎖的節(jié)點(diǎn)發(fā)生故障,Consul也不會(huì)釋放鎖。由于Consul API允許顯式銷(xiāo)毀會(huì)話,因此構(gòu)建系統(tǒng)時(shí),可以允許運(yùn)維人員在發(fā)生故障時(shí)可以進(jìn)行人工干預(yù),同時(shí)避免出現(xiàn)裂腦的可能性。
第三種健康檢查機(jī)制是會(huì)話TTL。創(chuàng)建會(huì)話時(shí),可以指定TTL值。如果TTL間隔過(guò)期而沒(méi)有更新,則說(shuō)明會(huì)話已過(guò)期,將觸發(fā)失效機(jī)制。這種類(lèi)型的故障檢測(cè)器也稱(chēng)為心跳故障檢測(cè)器。它比基于Gossip的故障檢測(cè)器可擴(kuò)展性差,因?yàn)樗o服務(wù)器增加了額外負(fù)擔(dān),但在某些情況下可能適用。TTL約定了失效的下限時(shí)間,也就是說(shuō),Consul不會(huì)在達(dá)到TTL之前使會(huì)話過(guò)期,允許將過(guò)期時(shí)間延遲到TTL之后。在創(chuàng)建會(huì)話,更新會(huì)話和領(lǐng)導(dǎo)者故障轉(zhuǎn)移時(shí),會(huì)更新TTL。使用TTL時(shí),客戶(hù)端應(yīng)注意時(shí)鐘偏斜問(wèn)題(clock skew issue):即客戶(hù)端上的時(shí)間進(jìn)度可能與Consul服務(wù)器上的速率不相同。
最后的一個(gè)細(xì)節(jié)是會(huì)話可以提供鎖定延遲(lock-delay),鎖定延遲是一個(gè)持續(xù)時(shí)間,介于0到60秒之間。當(dāng)會(huì)話失效時(shí),Consul會(huì)在鎖定延遲間隔內(nèi)阻止其重新獲取任何先前持有的鎖,這受到了Google Chubby的啟發(fā)。此延遲的目的是允許潛在的仍然活躍的領(lǐng)導(dǎo)者檢測(cè)到無(wú)效節(jié)點(diǎn),并停止處理可能導(dǎo)致?tīng)顟B(tài)不一致的請(qǐng)求。盡管這不是解決問(wèn)題的絕對(duì)方法,但它確實(shí)避免了將系統(tǒng)引入休眠狀態(tài),并且可以幫助緩解許多問(wèn)題。該值默認(rèn)值為15秒,客戶(hù)端可以設(shè)置為0來(lái)禁用此機(jī)制。
KV集合
KV集合和會(huì)話之間的集成是會(huì)話的主要使用場(chǎng)景,會(huì)話必須在使用前就創(chuàng)建,然后引用其ID。
KV API擴(kuò)展支持獲?。╝cquire)和釋放(release)操作。獲取操作的作用類(lèi)似于檢查并設(shè)置(Check-And-Set)操作,不同之處在于,只有在當(dāng)前鎖沒(méi)有持有人的情況下它才能成功(當(dāng)前的鎖持有人可以使用重新獲?。╮e-acquire)操作)。成功后,將進(jìn)行正常的密鑰更新,LockIndex也會(huì)累加,并且Session值也會(huì)更新以顯示該Session持有了鎖。
如果在獲取過(guò)程中給定會(huì)話已持有該鎖,則LockIndex不會(huì)遞增,但密鑰內(nèi)容會(huì)更新。這樣,當(dāng)前的鎖持有者就可以更新密鑰內(nèi)容,而不必解鎖并重新獲取它。
持有鎖之后,就可以使用提供相同會(huì)話的對(duì)應(yīng)釋放操作來(lái)釋放鎖。同樣,這也類(lèi)似于檢查并設(shè)置(Check-And-Set)操作,因?yàn)槿绻峁o(wú)效會(huì)話,則請(qǐng)求將失敗。一個(gè)需要注意的地方是,可以在不創(chuàng)建會(huì)話的情況下釋放該鎖,這是設(shè)計(jì)使然,因?yàn)樗试S運(yùn)維人員在必要時(shí)進(jìn)行干預(yù)并強(qiáng)制終止會(huì)話。如上所述,會(huì)話失效也將導(dǎo)致所有持有的鎖被釋放或刪除。釋放鎖后,LockIndex不會(huì)更改;但是,會(huì)話被清除后ModifyIndex遞增。
這些概念(大多數(shù)來(lái)自于Chubby)允許Key、LockIndex、Session組成的元組充當(dāng)序列器(sequencer),并用于驗(yàn)證請(qǐng)求是否屬于當(dāng)前的鎖持有者。 因?yàn)?code>LockIndex在每次獲取時(shí)都會(huì)遞增,所以即使同一會(huì)話重新獲取鎖,序列器(sequencer)也將能夠檢測(cè)到過(guò)期請(qǐng)求。 同樣,如果會(huì)話失效,則與LockIndex對(duì)應(yīng)的會(huì)話將變?yōu)榭瞻住?br>
需要明確的是,該鎖定系統(tǒng)僅是建議性的。沒(méi)有強(qiáng)制要求客戶(hù)端必須獲得鎖才能執(zhí)行任何操作。任何客戶(hù)端都可以在不擁有相應(yīng)鎖的情況下讀取,寫(xiě)入和刪除密鑰。Consul的目的不是防止客戶(hù)端的不當(dāng)行為。
反熵算法(Anti-Entropy)
Consul使用高級(jí)的方法維護(hù)服務(wù)和健康信息。本章節(jié)詳細(xì)描述了如何注冊(cè)服務(wù)和健康檢查,如何填充(populate)目錄以及健康狀態(tài)信息在更新時(shí)如何變化。
組件
我們首先需要了解服務(wù)和健康檢查的兩個(gè)組件:代理(Agent)和目錄(Catalog),下面對(duì)這些概念進(jìn)行了詳細(xì)描述,可以幫助我們更好地理解反熵算法。
代理
每個(gè)Consul代理各自維護(hù)自己的服務(wù)、健康檢查注冊(cè)信息和健康檢查信息,代理負(fù)責(zé)執(zhí)行自己的健康檢查并更新其本地狀態(tài)。
代理上下文中服務(wù)和健康檢查有很多可選的配置項(xiàng),這是因?yàn)榇碡?fù)責(zé)通過(guò)健康檢查功能,生成服務(wù)和運(yùn)行情況信息。
目錄
Consul中的服務(wù)發(fā)現(xiàn)底層由服務(wù)目錄支持,目錄是通過(guò)代理提交的信息匯總形成的。目錄抽象展現(xiàn)了當(dāng)前集群的高層視圖,包括哪些服務(wù)可用,哪些節(jié)點(diǎn)運(yùn)行這些服務(wù),健康檢查信息等等。用戶(hù)可以通過(guò)Consul提供的接口(包括DNS和HTTP)訪問(wèn)到這些信息。
與代理相比,目錄上下文中服務(wù)和健康檢查包含的字段優(yōu)先,這是因?yàn)槟夸泝H負(fù)責(zé)記錄和返回有關(guān)服務(wù),節(jié)點(diǎn)和健康狀態(tài)的信息。
目錄僅由服務(wù)器節(jié)點(diǎn)維護(hù),這是因?yàn)镃onsul底層使用了Raft協(xié)議,集群內(nèi)部目錄信息是統(tǒng)一的。
反熵算法
我們將系統(tǒng)會(huì)變混亂的趨勢(shì)稱(chēng)為熵(Entropy),Consul中的反熵(Anti-Entropy)機(jī)制就是用來(lái)應(yīng)對(duì)這種情況,即使在其組件發(fā)生故障時(shí)也能保持群集狀態(tài)有序。熵是物理學(xué)上的一個(gè)概念,代表雜亂無(wú)章,而反熵就是在雜亂無(wú)章中尋求一致。
如上所述,Consul在全局服務(wù)目錄和代理的本地狀態(tài)之間有明確的區(qū)分。反熵機(jī)制打通了這兩個(gè)世界的視角:反熵可以將本地代理狀態(tài)和目錄保持同步。例如,當(dāng)用戶(hù)在代理中新注冊(cè)了一個(gè)服務(wù)或健康檢查,代理會(huì)通知給目錄;同樣,當(dāng)從代理中刪除健康檢查時(shí),也會(huì)將其從目錄中刪除。
反熵也用于更新服務(wù)可用性(Availability)信息,當(dāng)代理運(yùn)行健康檢查時(shí),其狀態(tài)可能會(huì)更改,在這種情況下,新?tīng)顟B(tài)將同步到目錄。目錄使用此信息,可以根據(jù)其節(jié)點(diǎn)和服務(wù)的可用性智能地響應(yīng)查詢(xún)請(qǐng)求。
在同步期間,還將檢查目錄的正確性。如果目錄中存在代理不知道的任何服務(wù)或健康檢查,它們將被自動(dòng)刪除,以使目錄可以真實(shí)反映代理中運(yùn)行的服務(wù)集和健康檢查集合。Consul將代理的狀態(tài)視為權(quán)威(authoritative);如果代理視圖和目錄視圖之間有任何差異,將始終使用代理本地視圖。
定期同步
除了在代理發(fā)生更改時(shí)運(yùn)行之外,反熵也會(huì)作為常駐進(jìn)程長(zhǎng)期運(yùn)行,該進(jìn)程會(huì)周期性喚醒,將服務(wù)和健康檢查狀態(tài)同步給目錄,這樣可以確保目錄和代理之間的狀態(tài)是匹配的。即使在發(fā)生數(shù)據(jù)丟失的情況下,Consul也可以重新填充服務(wù)目錄。
為了避免同步過(guò)于頻繁,反熵周期性運(yùn)行時(shí)間會(huì)根據(jù)進(jìn)群大小變化,下表定義了群集大小和同步間隔時(shí)間之間的關(guān)系:
| 集合數(shù) | 定期同步周期 |
|---|---|
| 1-128 | 1分鐘 |
| 129-256 | 2分鐘 |
| 257-512 | 3分鐘 |
| 513-1024 | 4分鐘 |
| ... | ... |
上面的間隔時(shí)間是近似值,每個(gè)Consul代理會(huì)在周期窗口內(nèi)選擇一個(gè)隨機(jī)交錯(cuò)的開(kāi)始時(shí)間,避免集中在一起。
盡力同步
在許多情況下,反熵可能會(huì)失敗,原因有很多,包括代理或其操作環(huán)境配置錯(cuò)誤,I/O問(wèn)題(磁盤(pán)存滿,文件系統(tǒng)權(quán)限等),網(wǎng)絡(luò)問(wèn)題(代理無(wú)法與服務(wù)端通信)等。 因此,代理會(huì)盡可能保持同步。
如果在反熵運(yùn)行過(guò)程中遇到錯(cuò)誤,代理會(huì)記錄下來(lái)并繼續(xù)運(yùn)行。定期運(yùn)行反熵機(jī)制,可以從這些瞬態(tài)故障中恢復(fù)過(guò)來(lái)。
啟用標(biāo)簽覆蓋
服務(wù)注冊(cè)的信息同步可以部分修改,這樣允許外部代理更改服務(wù)的標(biāo)簽,這在某些情況下很有用,比如外部監(jiān)視服務(wù)作為標(biāo)簽信息的來(lái)源,Redis數(shù)據(jù)庫(kù)及其監(jiān)控服務(wù)Redis Sentinel之間就具有這種關(guān)系,Redis實(shí)例負(fù)責(zé)其大部分配置,但Sentinels用來(lái)確定Redis實(shí)例是主實(shí)例(primary)還是副實(shí)例(secondary)。 使用Consul服務(wù)配置項(xiàng)enable_tag_override,可以指示正在運(yùn)行Redis數(shù)據(jù)庫(kù)的Consul代理在反熵同步期間不更新標(biāo)簽。
安全模式
Consul同時(shí)依靠輕量級(jí)的Gossip機(jī)制和RPC系統(tǒng)來(lái)提供各種功能,這兩個(gè)系統(tǒng)因?yàn)樵O(shè)計(jì)原理不同,需要提供不同的安全機(jī)制。 但總的來(lái)說(shuō),Consul的安全機(jī)制有一個(gè)統(tǒng)一的目標(biāo):提供機(jī)密性(confidentiality)、完整性(integrity)和身份校驗(yàn)(authentication)。
Gossip協(xié)議底層由Serf提供支持,Serf使用對(duì)稱(chēng)密鑰或共享密鑰,有關(guān)Serf的相關(guān)細(xì)節(jié)和Consul中使用Serf的細(xì)節(jié),可以參考其他文檔。
RPC系統(tǒng)支持將端到端的TLS協(xié)議,并且可選用身份驗(yàn)證。TLS是一種廣泛運(yùn)用的非對(duì)稱(chēng)密碼系統(tǒng),并且是Web安全的基礎(chǔ)。
這些機(jī)制意味著Consul通信受到保護(hù),不會(huì)被竊聽(tīng),篡改和欺騙。 這樣就可以在不受信任的網(wǎng)絡(luò)(例如EC2和其他云托管平臺(tái))上運(yùn)行Consul。
安全配置
Consul防范模型(threat model)是建立在Consul以安全配置運(yùn)行的前提下,Consul默認(rèn)情況下沒(méi)有啟用安全配置,如果未啟用下文羅列的一些設(shè)置,則此防范模型的某些部分將失效。如以下章節(jié)所述,還必須對(duì)Consul防范模型之外的項(xiàng)目采取其他安全預(yù)防措施。
Consul運(yùn)行方式和其他二進(jìn)制文件一樣,它作為單個(gè)進(jìn)程運(yùn)行,并遵循與操作系統(tǒng)上任何其他應(yīng)用程序相同的安全性要求。Consul不會(huì)與主機(jī)系統(tǒng)進(jìn)行交互,不會(huì)以任何方式更改或操作安全配置。用戶(hù)應(yīng)根據(jù)自己的操作系統(tǒng)對(duì)程序進(jìn)行一些防護(hù)措施,以下是一些常用的建議:
- 添加適當(dāng)配置,以非root用戶(hù)身份運(yùn)行應(yīng)用程序(包括Consul);
- 使用內(nèi)核安全模塊(例如
SELinux)實(shí)施強(qiáng)制性訪問(wèn)控制; - 防止非特權(quán)用戶(hù)成為root;
ACL默認(rèn)啟用拒絕策略。必須將Consul配置為通過(guò)允許列表(allowlist)使用ACL,默認(rèn)為拒絕。這就強(qiáng)制所有請(qǐng)求具有明確的匿名訪問(wèn)權(quán)限或提供ACL令牌。
啟用加密:必須啟用和配置TCP和UDP加密,防止Consul代理之間進(jìn)行明文通信。至少應(yīng)啟用verify_outgoing來(lái)驗(yàn)證每個(gè)服務(wù)端有唯一的TLS認(rèn)證。還需要配置verify_server_hostname,以防止受感染的代理作為服務(wù)端重新啟動(dòng)并獲得對(duì)所有機(jī)密的訪問(wèn)權(quán)限。
verify_incoming通過(guò)相互身份驗(yàn)證提供了額外的代理驗(yàn)證,但對(duì)于實(shí)施防范模型而言并非絕對(duì)必要,因?yàn)檎?qǐng)求還必須包含有效的ACL令牌。它們之間的細(xì)微差別在于當(dāng)verify_incoming=false,將允許服務(wù)端仍然接受來(lái)自客戶(hù)端的未加密連接,僅此一項(xiàng)并不會(huì)違反防范模型,但是任何選擇不使用TLS的配置錯(cuò)誤的客戶(hù)端都會(huì)違反該模型。我們建議將該配置項(xiàng)設(shè)置為true;如果保留為false,則必須確保所有Consul客戶(hù)端均如上所述使用verify_outgoing = true,但所有外部API/UI訪問(wèn)就必須全部通過(guò)HTTPS,HTTP監(jiān)聽(tīng)將禁用。
已知的不安全配置
除了配置上述非默認(rèn)設(shè)置外,Consul還具有多個(gè)非默認(rèn)選項(xiàng),這些選項(xiàng)可能會(huì)帶來(lái)其他安全風(fēng)險(xiǎn)。
- 使用網(wǎng)絡(luò)公開(kāi)的API進(jìn)行腳本檢查。如果Consul代理(客戶(hù)端或服務(wù)端)向本地主機(jī)以外的網(wǎng)絡(luò)公開(kāi)其HTTP API,則
enable_script_checks必須設(shè)置為false,否則,即使配置了ACL,腳本檢查也會(huì)帶來(lái)遠(yuǎn)程執(zhí)行代碼的威脅。如果必須公開(kāi)HTTP API,則enable_local_script_checks提供了一種安全的替代方法,這個(gè)方法從1.3.0版本開(kāi)始可用,該功能還被反向移植到補(bǔ)丁版本0.9.4、1.1.1和1.2.4。 -
啟用遠(yuǎn)程執(zhí)行程序: Consul包含consul exec功能,允許跨集群執(zhí)行任意命令。從0.8.0版本開(kāi)始默認(rèn)禁用,我們建議禁用它。如果啟用,則必須格外小心,以確保正確的ACL限制訪問(wèn)。 -
驗(yàn)證服務(wù)器主機(jī)名單獨(dú)使用。從0.5.1版本到1.4.0版本,我們記錄了verify_server_hostname為true表示verify_outgoing,但是由于Bug原因,情況并非如此,因此僅設(shè)置verify_server_hostname會(huì)導(dǎo)致客戶(hù)端和服務(wù)端之間進(jìn)行純文本通信,這在1.4.1版本中已修復(fù)。
威脅模型
以下是Consul防范模型(threat model)的一部分:
-
Consul代理間通信:應(yīng)確保Consul代理間的通信不被竊聽(tīng),這需要在集群上啟用傳輸加密,并涵蓋TCP和UDP通信; -
Consul代理到CA間通信:Consul服務(wù)端與CA服務(wù)器之間的通信始終被加密; -
篡改傳輸?shù)臄?shù)據(jù):應(yīng)檢測(cè)到任何篡改,這樣Consul可以避免處理該請(qǐng)求; -
無(wú)需身份驗(yàn)證或授權(quán)即可訪問(wèn)數(shù)據(jù):所有請(qǐng)求都必須經(jīng)過(guò)身份驗(yàn)證和授權(quán),這要求群集上啟用ACL,使用默認(rèn)拒絕模式; -
由于惡意消息導(dǎo)致?tīng)顟B(tài)修改或損壞:格式錯(cuò)誤的消息將被直接丟棄,格式正確的消息需要身份驗(yàn)證和授權(quán); -
非服務(wù)端成員訪問(wèn)原始數(shù)據(jù):所有服務(wù)端必須加入集群(具有正確的身份驗(yàn)證和授權(quán))才能開(kāi)始參與Raft,Raft數(shù)據(jù)通過(guò)TLS傳輸; -
針對(duì)節(jié)點(diǎn)的服務(wù)拒絕:針對(duì)節(jié)點(diǎn)的DoS攻擊不應(yīng)影響軟件的安全性; -
基于連接的服務(wù)端通信:應(yīng)該保護(hù)兩個(gè)啟用連接的服務(wù)之間的通信(本機(jī)或通過(guò)代理)不被竊聽(tīng)并提供身份驗(yàn)證,這是通過(guò)雙向TLS實(shí)現(xiàn)的。
以下內(nèi)容不是Consul服務(wù)端代理防范模型(threat model)的一部分:
-
訪問(wèn)Consul數(shù)據(jù)目錄(讀寫(xiě)操作):所有Consul服務(wù)器,包括非領(lǐng)導(dǎo)者,都將完整的Consul狀態(tài)保存到此目錄中。數(shù)據(jù)包括所有KV數(shù)據(jù)集、服務(wù)注冊(cè)、ACL令牌、Connect CA配置等。對(duì)該目錄的任何讀寫(xiě)操作都使攻擊者可以訪問(wèn)和篡改該數(shù)據(jù); -
訪問(wèn)Consul配置目錄(讀寫(xiě)操作):Consul配置可以啟用或禁用ACL系統(tǒng),修改數(shù)據(jù)目錄路徑等。對(duì)該目錄的任何讀寫(xiě)操作都使攻擊者可以重新配置Consul的許多方面。通過(guò)禁用ACL系統(tǒng),攻擊者可以訪問(wèn)所有Consul數(shù)據(jù); -
訪問(wèn)Consul服務(wù)端機(jī)器內(nèi)存:如果攻擊者能夠訪問(wèn)正在運(yùn)行的Consul服務(wù)端機(jī)器的內(nèi)存狀態(tài),則幾乎所有Consul數(shù)據(jù)的機(jī)密性都可能受到損害。如果用戶(hù)使用的是外部Connect CA,則根私有密鑰(root private material)永遠(yuǎn)不會(huì)用于Consul流程,因此可以認(rèn)為是安全的。Service Connect TLS證書(shū)應(yīng)被視為已泄露,它們永遠(yuǎn)不會(huì)被服務(wù)端機(jī)器持久化,至少在簽名請(qǐng)求期間確實(shí)存在于內(nèi)存中。
以下內(nèi)容不是Consul客戶(hù)端代理防范模型(threat model)的一部分:
-
訪問(wèn)Consul數(shù)據(jù)目錄(讀寫(xiě)操作):Consul客戶(hù)端將使用數(shù)據(jù)目錄緩存本地狀態(tài)。這包括本地服務(wù)、關(guān)聯(lián)的ACL令牌、Connect TLS證書(shū)等。對(duì)該目錄的讀寫(xiě)操作使攻擊者可以訪問(wèn)此數(shù)據(jù),此數(shù)據(jù)通常是群集完整數(shù)據(jù)的較小子集; -
訪問(wèn)Consul配置目錄(讀寫(xiě)操作):Consul客戶(hù)端配置文件包含服務(wù)的地址和端口信息、代理的默認(rèn)ACL令牌等。訪問(wèn)Consul配置可以使攻擊者將服務(wù)端口更改為惡意端口,注冊(cè)新服務(wù)等。此外,某些服務(wù)定義附加了ACL令牌,可在群集范圍內(nèi)使用ACL令牌來(lái)模擬該服務(wù)。攻擊者無(wú)法更改群集范圍的配置,例如禁用ACL系統(tǒng); -
訪問(wèn)Consul客戶(hù)端機(jī)器內(nèi)存:其危險(xiǎn)半徑比服務(wù)端代理要小得多,但是仍然會(huì)損害數(shù)據(jù)子集的機(jī)密性。特別是,針對(duì)代理的API請(qǐng)求的任何數(shù)據(jù)(包括服務(wù),KV數(shù)據(jù)集和連接信息)可能會(huì)受到威脅。如果客戶(hù)端從未請(qǐng)求服務(wù)端上的特定數(shù)據(jù)集,則它絕不會(huì)進(jìn)入代理的內(nèi)存,因?yàn)閺?fù)制僅存在于服務(wù)端之間。攻擊者還可能提取此代理上用于服務(wù)注冊(cè)的ACL令牌,因?yàn)榱钆票仨毰c注冊(cè)的服務(wù)一起存儲(chǔ)在內(nèi)存中; -
通過(guò)網(wǎng)絡(luò)訪問(wèn)本地Connect代理或服務(wù):服務(wù)和可識(shí)別連接的代理之間的通信通常是未加密的,并且必須通過(guò)受信任的網(wǎng)絡(luò)進(jìn)行。這通常是回送設(shè)備(loopback device)。這要求信任同一臺(tái)計(jì)算機(jī)上的其他進(jìn)程,或者使用更復(fù)雜的隔離機(jī)制,例如網(wǎng)絡(luò)名稱(chēng)空間(network namespaces)。這還要求外部進(jìn)程不能與Connect服務(wù)或代理進(jìn)行通信,入站端口(inbound port)除外。因此,非本機(jī)Connect應(yīng)用程序應(yīng)僅綁定到非公共地址; -
實(shí)施不完善的Connect代理或服務(wù):Connect代理或本機(jī)集成服務(wù)必須正確提供有效的葉子證書(shū)(leaf certificate),驗(yàn)證入站TLS客戶(hù)端證書(shū)并調(diào)用Consul代理本地授權(quán)端點(diǎn)。如果其中任何一項(xiàng)未正確執(zhí)行,則代理或服務(wù)可能允許未經(jīng)身份驗(yàn)證或未經(jīng)授權(quán)的連接。
外部威脅概述
有四個(gè)影響Consul防范模型(threat model)的組件:服務(wù)器代理、客戶(hù)端代理、Connect CA和Consul API客戶(hù)端(包括Connect代理)。
服務(wù)端代理通過(guò)Raft參與領(lǐng)導(dǎo)者選舉和數(shù)據(jù)復(fù)制,與其他代理的所有通信都是加密的。數(shù)據(jù)以明文方式存儲(chǔ)在配置的數(shù)據(jù)目錄中。存儲(chǔ)的數(shù)據(jù)包括ACL令牌和TLS證書(shū)。如果內(nèi)置CA與Connect一起使用,則根證書(shū)私鑰(root private key)也存儲(chǔ)在磁盤(pán)上,外部CA程序不在此目錄中存儲(chǔ)數(shù)據(jù)。必須仔細(xì)保護(hù)此數(shù)據(jù)目錄,以防止攻擊者冒充服務(wù)端或特定的ACL用戶(hù)。我們計(jì)劃隨著時(shí)間的推移在數(shù)據(jù)目錄中引入進(jìn)一步的防范措施(包括至少部分?jǐn)?shù)據(jù)加密),但應(yīng)始終將數(shù)據(jù)目錄視為機(jī)密。
要使客戶(hù)端代理加入集群,它必須提供具有node:write功能的有效ACL令牌。加入集群的請(qǐng)求和客戶(hù)端與服務(wù)端代理之間所有其他API請(qǐng)求均通過(guò)TLS進(jìn)行通信??蛻?hù)端提供Consul API,并通過(guò)共享的TLS連接將所有請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)端。每個(gè)請(qǐng)求均包含用于身份驗(yàn)證和授權(quán)的ACL令牌。不提供ACL令牌的請(qǐng)求將繼承可配置代理的默認(rèn)ACL令牌。
Connect CA提供者負(fù)責(zé)存儲(chǔ)用于簽署和驗(yàn)證通過(guò)Connect建立的連接的根(或中間)證書(shū)的私鑰。Consul服務(wù)端代理通過(guò)加密方法與CA程序進(jìn)行通信。此方法取決于使用的CA程序,Consul提供一個(gè)內(nèi)置的CA,可以在服務(wù)端代理上本地執(zhí)行所有操作。Consul本身不存儲(chǔ)任何私鑰信息,內(nèi)置CA除外。
Consul API客戶(hù)端(代理本身,內(nèi)置UI,外部軟件)必須通過(guò)TLS與Consul代理進(jìn)行通信,并且必須為身份驗(yàn)證和授權(quán)請(qǐng)求提供ACL令牌。
Jepsen 測(cè)試
Jepsen是由Kyle Kingsbury編寫(xiě)的工具,旨在測(cè)試分布式系統(tǒng)的分區(qū)容錯(cuò)性(partition tolerance)。它創(chuàng)建網(wǎng)絡(luò)分區(qū),同時(shí)對(duì)系統(tǒng)進(jìn)行隨機(jī)操作,并分析結(jié)果查看系統(tǒng)是否違反了一致性原則。
作為Consul測(cè)試的一部分,我們運(yùn)行了Jepsen測(cè)試,以確定是否可以發(fā)現(xiàn)任何一致性問(wèn)題。 在我們的測(cè)試中,Consul從分區(qū)中正?;謴?fù),沒(méi)有引入任何一致性問(wèn)題。
運(yùn)行測(cè)試
目前,使用Jepsen進(jìn)行測(cè)試非常復(fù)雜,因?yàn)樗枰O(shè)置多個(gè)虛擬機(jī),SSH密鑰,DNS配置和有效的Clojure環(huán)境。 我們希望貢獻(xiàn)我們的Consul測(cè)試代碼,并為Jepsen測(cè)試提供一個(gè)運(yùn)行環(huán)境。
輸出
以下是從Jepsen捕獲的輸出。我們多次運(yùn)行Jepsen,每次都測(cè)試通過(guò),由于輸出內(nèi)容過(guò)于占用篇幅(總共3900+行),我們僅粘貼前100行數(shù)據(jù):
$ lein test :only jepsen.system.consul-test
lein test jepsen.system.consul-test
INFO jepsen.os.debian - :n5 setting up debian
INFO jepsen.os.debian - :n3 setting up debian
INFO jepsen.os.debian - :n4 setting up debian
INFO jepsen.os.debian - :n1 setting up debian
INFO jepsen.os.debian - :n2 setting up debian
INFO jepsen.os.debian - :n4 debian set up
INFO jepsen.os.debian - :n5 debian set up
INFO jepsen.os.debian - :n3 debian set up
INFO jepsen.os.debian - :n1 debian set up
INFO jepsen.os.debian - :n2 debian set up
INFO jepsen.system.consul - :n1 consul nuked
INFO jepsen.system.consul - :n4 consul nuked
INFO jepsen.system.consul - :n5 consul nuked
INFO jepsen.system.consul - :n3 consul nuked
INFO jepsen.system.consul - :n2 consul nuked
INFO jepsen.system.consul - Running nodes: {:n1 false, :n2 false, :n3 false, :n4 false, :n5 false}
INFO jepsen.system.consul - :n2 consul nuked
INFO jepsen.system.consul - :n3 consul nuked
INFO jepsen.system.consul - :n4 consul nuked
INFO jepsen.system.consul - :n5 consul nuked
INFO jepsen.system.consul - :n1 consul nuked
INFO jepsen.system.consul - :n1 starting consul
INFO jepsen.system.consul - :n2 starting consul
INFO jepsen.system.consul - :n4 starting consul
INFO jepsen.system.consul - :n5 starting consul
INFO jepsen.system.consul - :n3 starting consul
INFO jepsen.system.consul - :n3 consul ready
INFO jepsen.system.consul - :n2 consul ready
INFO jepsen.system.consul - Running nodes: {:n1 true, :n2 true, :n3 true, :n4 true, :n5 true}
INFO jepsen.system.consul - :n5 consul ready
INFO jepsen.system.consul - :n1 consul ready
INFO jepsen.system.consul - :n4 consul ready
INFO jepsen.core - Worker 0 starting
INFO jepsen.core - Worker 2 starting
INFO jepsen.core - Worker 1 starting
INFO jepsen.core - Worker 3 starting
INFO jepsen.core - Worker 4 starting
INFO jepsen.util - 2 :invoke :read nil
INFO jepsen.util - 3 :invoke :cas [4 4]
INFO jepsen.util - 0 :invoke :write 4
INFO jepsen.util - 1 :invoke :write 1
INFO jepsen.util - 4 :invoke :cas [4 0]
INFO jepsen.util - 2 :ok :read nil
INFO jepsen.util - 4 :fail :cas [4 0]
INFO jepsen.util - 1 :ok :write 1
INFO jepsen.util - 0 :ok :write 4
INFO jepsen.util - 3 :fail :cas [4 4]
INFO jepsen.util - 2 :invoke :cas [0 3]
INFO jepsen.util - 2 :fail :cas [0 3]
INFO jepsen.util - 4 :invoke :cas [4 4]
INFO jepsen.util - 1 :invoke :write 3
INFO jepsen.util - 0 :invoke :cas [3 1]
INFO jepsen.util - 3 :invoke :write 2
INFO jepsen.util - 4 :fail :cas [4 4]
INFO jepsen.util - 0 :fail :cas [3 1]
INFO jepsen.util - 1 :ok :write 3
INFO jepsen.util - 3 :ok :write 2
INFO jepsen.util - 2 :invoke :read nil
INFO jepsen.util - 2 :ok :read 2
INFO jepsen.util - 4 :invoke :read nil
INFO jepsen.util - 0 :invoke :write 4
INFO jepsen.util - 1 :invoke :write 0
INFO jepsen.util - 4 :ok :read 2
INFO jepsen.util - 3 :invoke :read nil
INFO jepsen.util - 0 :ok :write 4
INFO jepsen.util - 3 :ok :read 2
INFO jepsen.util - 1 :ok :write 0
INFO jepsen.util - 2 :invoke :write 3
INFO jepsen.util - 2 :ok :write 3
INFO jepsen.util - 4 :invoke :write 4
INFO jepsen.util - 4 :ok :write 4
INFO jepsen.util - 0 :invoke :write 1
INFO jepsen.util - 3 :invoke :read nil
INFO jepsen.util - 1 :invoke :cas [1 0]
INFO jepsen.util - 3 :ok :read 4
INFO jepsen.util - 0 :ok :write 1
INFO jepsen.util - 1 :fail :cas [1 0]
INFO jepsen.util - 2 :invoke :cas [0 2]
INFO jepsen.util - 2 :fail :cas [0 2]
INFO jepsen.util - 4 :invoke :cas [1 2]
INFO jepsen.util - 4 :fail :cas [1 2]
INFO jepsen.util - 3 :invoke :write 1
INFO jepsen.util - 0 :invoke :write 1
INFO jepsen.util - 1 :invoke :read nil
INFO jepsen.util - 0 :ok :write 1
INFO jepsen.util - 1 :ok :read 1
INFO jepsen.util - 3 :ok :write 1
INFO jepsen.util - 2 :invoke :write 4
INFO jepsen.util - 2 :ok :write 4
INFO jepsen.util - 4 :invoke :cas [2 4]
INFO jepsen.util - 4 :fail :cas [2 4]
INFO jepsen.util - 0 :invoke :read nil
INFO jepsen.util - 1 :invoke :write 3
INFO jepsen.util - 3 :invoke :read nil
INFO jepsen.util - 0 :ok :read 4
INFO jepsen.util - 3 :ok :read 4
INFO jepsen.util - 1 :ok :write 3
INFO jepsen.util - 2 :invoke :cas [4 2]
INFO jepsen.util - 2 :fail :cas [4 2]
INFO jepsen.util - 4 :invoke :read nil
INFO jepsen.util - 4 :ok :read 3
INFO jepsen.util - 0 :invoke :cas [2 4]
INFO jepsen.util - 3 :invoke :write 2
INFO jepsen.util - 1 :invoke :write 0
INFO jepsen.util - 0 :fail :cas [2 4]
INFO jepsen.util - 3 :ok :write 2
INFO jepsen.util - 1 :ok :write 0
INFO jepsen.util - 2 :invoke :cas [0 3]
INFO jepsen.util - 2 :fail :cas [0 3]
INFO jepsen.util - 4 :invoke :write 0
INFO jepsen.util - 4 :ok :write 0
INFO jepsen.util - 0 :invoke :write 1
INFO jepsen.util - 3 :invoke :cas [0 2]
INFO jepsen.util - 1 :invoke :cas [0 0]
INFO jepsen.util - 0 :ok :write 1
Consul 術(shù)語(yǔ)表
本章節(jié)收集了Consul和Consul Enterprise文檔中使用的一些技術(shù)術(shù)語(yǔ),以及整個(gè)Consul社區(qū)中經(jīng)常出現(xiàn)的一些術(shù)語(yǔ)。
代理
代理是Consul群集中,每個(gè)成員上運(yùn)行時(shí)間較長(zhǎng)的守護(hù)程序,它通過(guò)運(yùn)行consul agent啟動(dòng)。代理可以以客戶(hù)端或服務(wù)端模式運(yùn)行。由于所有節(jié)點(diǎn)都必須運(yùn)行代理,所以將節(jié)點(diǎn)稱(chēng)為客戶(hù)端或服務(wù)器更為簡(jiǎn)單,但是還存在代理的其他實(shí)例。所有代理都可以運(yùn)行DNS或HTTP接口,并負(fù)責(zé)運(yùn)行檢查,和保持服務(wù)同步。
客戶(hù)端
客戶(hù)端是將所有RPC請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)端的代理??蛻?hù)端相對(duì)是無(wú)狀態(tài)(stateless)的,客戶(hù)端執(zhí)行的唯一后臺(tái)活動(dòng)是參與LAN gossip pool,這個(gè)資源開(kāi)銷(xiāo)很小,并且僅消耗少量的網(wǎng)絡(luò)帶寬。
服務(wù)端
服務(wù)端在職責(zé)上有所增強(qiáng),它參與Raft仲裁、維護(hù)群集狀態(tài)、響應(yīng)RPC查詢(xún),與其他數(shù)據(jù)中心交換WAN gossip以及將查詢(xún)轉(zhuǎn)發(fā)給領(lǐng)導(dǎo)者或遠(yuǎn)程數(shù)據(jù)中心。
數(shù)據(jù)中心
我們將數(shù)據(jù)中心定義為私有的、低延遲和高帶寬的網(wǎng)絡(luò)環(huán)境。它不包括穿越公共互聯(lián)網(wǎng)(public internet)的通信,在我們的定義中,單個(gè)EC2區(qū)域內(nèi)的多個(gè)可用區(qū)視為單個(gè)數(shù)據(jù)中心的一部分。
Consensus
在文檔中,我們使用共識(shí)(Consensus)來(lái)表示對(duì)領(lǐng)導(dǎo)者的選舉達(dá)成共識(shí),以及對(duì)事務(wù)順序達(dá)成一致。由于這些事務(wù)適用于有限狀態(tài)機(jī)(finite-state machine),因此我們對(duì)一致性的定義意味著復(fù)制狀態(tài)機(jī)(replicated state machine)的一致。
Gossip
Consul建立在Serf之上,Serf提供了完整的Gossip協(xié)議,可用于多種目的。Serf提供成員資格,故障檢測(cè)和事件廣播機(jī)制。我們?cè)?code>Gossip章節(jié)中對(duì)這些用法有詳細(xì)說(shuō)明。簡(jiǎn)單理解,Gossip協(xié)議涉及隨機(jī)的節(jié)點(diǎn)間通信,主要是通過(guò)UDP協(xié)議。
LAN Gossip
局域網(wǎng) Gossip Pool:其中包含的節(jié)點(diǎn)都位于同一局域網(wǎng)或數(shù)據(jù)中心上。
WAN Gossip
廣域網(wǎng) Gossip Pool:其中僅包含服務(wù)端機(jī)器,這些服務(wù)器位于不同數(shù)據(jù)中心,通常通過(guò)Internet或廣域網(wǎng)進(jìn)行通信。
RPC
遠(yuǎn)程調(diào)用,這是一種請(qǐng)求/響應(yīng)機(jī)制,允許客戶(hù)端向服務(wù)器發(fā)出請(qǐng)求。