從 2015 年到現(xiàn)在,ZStack 有一條宗旨一直沒有變過,就是向客戶交付穩(wěn)定、可靠、高性能的云平臺(tái),這條宗旨在前幾年讓我們一直聚焦云平臺(tái)本身,包括虛擬化、云網(wǎng)絡(luò)、云編排、存儲(chǔ)管理等等這些功能。
在這里面最讓我們頭痛的,即使不是第一也能進(jìn)前三的存在,就是存儲(chǔ)管理。
考慮到存儲(chǔ)對業(yè)務(wù)的無比的重要性,以及我們作為一家創(chuàng)業(yè)公司的支持能力,我們一開始一直是基于一些開源的存儲(chǔ)方案對客戶提供服務(wù):
1. XFS,作為 RHEL 默認(rèn)的本地文件系統(tǒng),我們原本一直對 XFS 是比較信任的,但實(shí)際上 XFS 在使用過程中問題多多,我們幫客戶繞過了很多坑,也在考慮別的替代方案;
2. NFS,NFS 是一個(gè)對云平臺(tái)很簡單的方案,因?yàn)樗帘瘟撕芏啻鎯?chǔ)的復(fù)雜性,用文件系統(tǒng)的方式提供了共享存儲(chǔ),使得我們可以用類似本地文件系統(tǒng)的管理方式管理共享存儲(chǔ),既簡單又支持熱遷移等高級功能,看似完美,但實(shí)際上 NFS 幾乎是我們最不推薦的生產(chǎn)用存儲(chǔ)方案之一,細(xì)節(jié)將在后面討論;
3. OCFS2,當(dāng)用戶只有 SAN 存儲(chǔ),也無法提供 NFS 接口時(shí),我們的選擇并不多,此時(shí) Oracle 的 OCFS2 成為一個(gè)值得青睞的方案,其優(yōu)點(diǎn)是在小規(guī)模使用時(shí)基本上很穩(wěn)定,部署后也可以使用文件系統(tǒng)的方式使用,但在性能、大規(guī)模的擴(kuò)展性和部分功能(例如文件鎖)上支持也并不完美;
4.Ceph,基于 Ceph 可以提供很棒的存儲(chǔ)方案,但 Ceph 相對復(fù)雜的部署運(yùn)維對部分客戶還是比較難接受,特別是在私有云中,很多客戶習(xí)慣了 SAN 存儲(chǔ)帶來的性能和安全感,對他們來說也沒有超大容量的需求或者隨時(shí)需要靈活擴(kuò)容,反而大廠商帶來的安全感,或者能夠?qū)⒅坝迷赩Mware 上的 SAN 存儲(chǔ)繼續(xù)用起來才是最重要的。
綜合考慮前面的各種存儲(chǔ),NFS、OCFS2 的不完美促使我們提供一個(gè)能夠管理共享存儲(chǔ)的存儲(chǔ)方案,這個(gè)方案要能達(dá)到下面的要求:
1. 部署速度要足夠快 ,ZStack 的部署速度一向是業(yè)界前列,我們的標(biāo)準(zhǔn)一直是對于 Linux 有基本理解的人能夠在 30 分鐘內(nèi)完成部署,這個(gè)時(shí)間是包括部署主存儲(chǔ)、鏡像倉庫的時(shí)間的。
2. 能夠擴(kuò)展到足夠大的規(guī)模 ,根據(jù) SAN 存儲(chǔ)的性能,單個(gè)集群應(yīng)該可以接管幾十到上百的服務(wù)器(因?yàn)橐话銇碚f單個(gè) SAN 存儲(chǔ)能支撐的服務(wù)器數(shù)量有限)。
3. 性能能夠完整發(fā)揮 SAN 存儲(chǔ)的性能 ,IO 模式能夠發(fā)揮 SAN 存儲(chǔ)的cache 性能,對于 OCFS2 我們可以通過調(diào)整block size 來優(yōu)化 OCFS2 性能,但如果在分層SAN 存儲(chǔ)上測試就會(huì)發(fā)現(xiàn)由于大 block size 帶來的IO pattern 變化,如果測試 4k 小文件隨機(jī)寫,性能并不穩(wěn)定,無法像直接在物理機(jī)上對 LUN 測試前期全部寫到高速盤上,帶來了測試數(shù)據(jù)的不理想。
4. 高穩(wěn)定性 ,與互聯(lián)網(wǎng)、公有云業(yè)務(wù)不同,私有云均部署在客戶機(jī)房,甚至是一些隔離、保密機(jī)房,這意味著我們無法像互聯(lián)網(wǎng)環(huán)境一樣執(zhí)行“反復(fù)試錯(cuò)”的策略,我們無法控制用戶的升級節(jié)奏,無法時(shí)刻監(jiān)控運(yùn)維存儲(chǔ)狀態(tài),也無法再客戶環(huán)境進(jìn)行灰度測試、鏡像驗(yàn)證。
最終,在2018 年我們決定自己開發(fā)一個(gè)面向共享塊存儲(chǔ)的存儲(chǔ)方法,命名很直接就叫 SharedBlock。整個(gè)方案是這樣的:
1. 基于塊設(shè)備,直接基于塊設(shè)備向虛擬機(jī)提供虛擬云盤,通過避免文件系統(tǒng)開銷可以明顯提升性能和穩(wěn)定性;
2. 在塊設(shè)備上基于 Paxos 實(shí)現(xiàn)分布式鎖來管理塊設(shè)備的分配和節(jié)點(diǎn)的加入、心跳、IO 狀態(tài)檢查;
3. 通過 Qemu 的接口實(shí)現(xiàn)對用戶磁盤讀寫狀況進(jìn)行監(jiān)控;
SharedBlock在推出后,應(yīng)用在了很多的生產(chǎn)客戶上,特別是可以利舊 SAN 存儲(chǔ)特點(diǎn)讓 SharedBlock 快速部署在大量以往使用虛擬化的客戶上。
后來隨著 5G 和物聯(lián)網(wǎng)、云端互聯(lián)的發(fā)展,讓市場迫切需要一個(gè)價(jià)格不高、可以簡便部署、軟硬一體的超融合產(chǎn)品,因此我們就在考慮一個(gè)兩節(jié)點(diǎn)一體機(jī)的產(chǎn)品,通過和硬件廠商合作設(shè)計(jì),可以實(shí)現(xiàn) 2U 的一體機(jī)包含足夠用戶使用的硬盤、獨(dú)立的模塊和雙電冗余,我們希望能通過這個(gè)產(chǎn)品將客戶的原本單節(jié)點(diǎn)運(yùn)行的應(yīng)用平滑升級到兩節(jié)點(diǎn)備份,讓客戶的運(yùn)行在軌道站點(diǎn)、制造業(yè)工廠這些“端”應(yīng)用既享受到云的便利,又不需要復(fù)雜的運(yùn)維和部署。這就是我們的Mini Storage。
在開發(fā)這些存儲(chǔ)產(chǎn)品的過程中,我們踩了無數(shù)的坑,也收獲了很多經(jīng)驗(yàn)。
下面先說說將存儲(chǔ)做正確有多難,在今年說這個(gè)話題有一個(gè)熱點(diǎn)事件是避不開的,就是今年的FOSDEM 19' 上 PostgreSQL 的開發(fā)者在會(huì)上介紹了 PostgreSQL 開發(fā)者發(fā)現(xiàn)自己使用 fsync() 調(diào)用存在一個(gè)十年的 bug——
1. PG使用 writeback 機(jī)制,特別是在過去使用機(jī)械硬盤的時(shí)代,這樣可以大大提高速度,但這就需要定時(shí) fsync 來確保把數(shù)據(jù)刷到磁盤;
2. PG使用了一個(gè)單獨(dú)線程來執(zhí)行 fsync(),期望當(dāng)寫入錯(cuò)誤時(shí)能夠返回錯(cuò)誤;
3.但其實(shí)操作系統(tǒng)可能自己會(huì)將臟頁同步到磁盤,或者可能別的程序調(diào)用 fsync();
4. 無論上面的哪種情況,PG 自己的同步線程在 fsync 時(shí)都無法收到錯(cuò)誤信息;
這樣 PG 可能誤以為數(shù)據(jù)已經(jīng)同步而移動(dòng)了 journal 的指針,實(shí)際上數(shù)據(jù)并沒有同步到磁盤,如果磁盤持續(xù)沒有修復(fù)且突然丟失內(nèi)存數(shù)據(jù)就會(huì)存在數(shù)據(jù)丟失的情況。
在這場 session 上 PG 的開發(fā)者吐槽了 kernel 開發(fā)以及存儲(chǔ)開發(fā)里的很多問題,很多時(shí)候 PG 只是想更好地實(shí)現(xiàn)數(shù)據(jù)庫,但卻發(fā)現(xiàn)經(jīng)常要為 SAN/NFS 這些存儲(chǔ)操心,還要為內(nèi)核的未文檔的行為買單。
這里說到 NFS,不得不多提兩句,在 Google 上搜索 "nfs bug" 可以看到五百萬個(gè)結(jié)果,其中不乏 Gitlab 之類的知名廠商踩坑,也不乏 Redhat 之類的操作系統(tǒng)嘗試提供遇到 NFS 問題的建議:
從我們一個(gè)云廠商的角度看來,虛擬機(jī)存儲(chǔ)使用 NFS 遇到的問題包括但不限于這幾個(gè):
1. 部分客戶的存儲(chǔ)不支持 NFS 4.0 帶來一系列性能問題和并發(fā)問題,而且 4.0 之前不支持 locking;
2. nfs服務(wù)本身會(huì)帶來安全漏洞;
3. 對于在 server 上做一些操作(例如 unshare)帶來的神秘行為;
4. 使用 async 掛載可能會(huì)帶來一些不一致問題,在虛擬化這種 IO 棧嵌套多層的環(huán)境可能會(huì)放大這一問題,而使用 sync 掛載會(huì)有明顯的性能損失;
5. NFS本身的 bug;
最終我們的建議就是生產(chǎn)環(huán)境、較大的集群的情況下,最起碼,少用 NFS 4.0 以前的版本……
另一個(gè)出名的文章是發(fā)表在 14 年 OSDI 的這篇 AllFile Systems Are Not Created Equal,作者測試了數(shù)個(gè)文件系統(tǒng)和文件應(yīng)用,在大量系統(tǒng)中找到了不乏丟數(shù)據(jù)的 Bug, 在此之后諸如 FSE'16 的 Crash consistency validation made easy 又找到了gmake、atom 等軟件的各種丟數(shù)據(jù)或?qū)е陆Y(jié)果不正確的問題:
上面我們舉了很多軟件、文件系統(tǒng)的例子,這些都是一些單點(diǎn)問題或者局部問題,如果放在云平臺(tái)的存儲(chǔ)系統(tǒng)上的話,復(fù)雜度就會(huì)更高:
1. 首先,私有云面臨的是一個(gè)離散碎片的環(huán)境,我們都知道 Android 開發(fā)者往往有比 iOS 開發(fā)者有更高的適配成本,這個(gè)和私有云是類似的,因?yàn)榭蛻粲校?/p>
1)不同廠商的設(shè)備;
2)不同的多路徑軟件;
3)不同的服務(wù)器硬件、HBA 卡;
雖然 SCSI 指令是通用的,但實(shí)際上對 IO 出錯(cuò)、路徑切換、緩存使用這些問題上,不同的存儲(chǔ)+多路徑+HBA 可以組成不同的行為,是最容易出現(xiàn)難以調(diào)試的問題地方,例如有的存儲(chǔ)配合特定 HBA 就會(huì)產(chǎn)生下面的 IO 曲線:
2. 由于我們是產(chǎn)品化的私有云,產(chǎn)品化就意味著整套系統(tǒng)不可能是托管運(yùn)維,也不會(huì)提供駐場運(yùn)維,這樣就會(huì)明顯受客戶參差不齊的運(yùn)維環(huán)境和運(yùn)維水平限制:
1)升級條件不同,有的用戶希望一旦部署完就再也不要升級不要?jiǎng)恿?,這就要求我們發(fā)布的版本一定要是穩(wěn)定可靠的,因?yàn)榘l(fā)出去可能就沒有升級的機(jī)會(huì)了,這點(diǎn)和互聯(lián)網(wǎng)場景有明顯的區(qū)別;
2)聯(lián)網(wǎng)條件不同,一般來說,來自生產(chǎn)環(huán)境的數(shù)據(jù)和日志是至關(guān)重要的,但對產(chǎn)品化的廠商來說,這些數(shù)據(jù)卻是彌足珍貴,因?yàn)橛械目蛻魴C(jī)房不僅不允許連接外網(wǎng),甚至我們的客戶工程師進(jìn)機(jī)房的時(shí)候手機(jī)也不允許攜帶;
3)運(yùn)維水平不同,對于一個(gè)平臺(tái)系統(tǒng),如果運(yùn)維水平不同,那么能發(fā)揮的作用也是不同的,比如同樣是硬件故障,對于運(yùn)維水平高的客戶團(tuán)隊(duì)可能很快能夠確認(rèn)問題并找硬件廠商解決,而有的客戶就需要我們先幫忙定位分析問題甚至幫助和硬件廠商交涉,就需要消耗我們很多精力;
3. 漫長的存儲(chǔ)路徑,對于平臺(tái)來說,我們不僅要操心 IO 路徑——Device Mapper、多路徑、SCSI、HBA 這些,還要操心虛擬化的部分——virtio 驅(qū)動(dòng)、virtio-scsi、qcow2…… 還要操心存儲(chǔ)的控制平面——快照、熱遷移、存儲(chǔ)遷移、備份…… 很多存儲(chǔ)的正確性驗(yàn)證只涉及選舉、IO 這部分,而對存儲(chǔ)管理并沒有做足夠的關(guān)注,而根據(jù)我們的經(jīng)驗(yàn),控制平面一旦有 Bug,破壞力可能比數(shù)據(jù)面更大。
說了這么多難處,我們來說說怎么解決。提到存儲(chǔ)的正確性,接觸過分布式系統(tǒng)的同學(xué)可能會(huì)說TLA+,我們先對不熟悉 TLA+ 的同學(xué)簡單介紹下 TLA+。
2002Lamport 寫了一本書《SpecifyingSystems》基本上算是 TLA+ 比較正式的第一本書,了解的朋友可能知道在此之前 Lamport 在分布式系統(tǒng)和計(jì)算結(jié)科學(xué)就很出名了——LaTex、Lamport clock、PAXOS 等等,TLA+ 剛開始的時(shí)候沒有特別受重視,他的出名是來自 AWS 15 年發(fā)表在 ACM 會(huì)刊的《How Amazon Web Services Uses FormalMethods》。
從本質(zhì)上講,形式化驗(yàn)證并不是新東西,大概在上世紀(jì)就有了相關(guān)的概念,TLA+ 的優(yōu)勢在于它特別適合驗(yàn)證分布式系統(tǒng)的算法設(shè)計(jì)。因?yàn)閷τ谝粋€(gè)可驗(yàn)證的算法來說,核心是將系統(tǒng)時(shí)刻的狀態(tài)確定化,并確定狀態(tài)變化的條件和結(jié)果,這樣 TLA+ 可以通過窮舉+剪枝檢查當(dāng)有并發(fā)操作時(shí)會(huì)不會(huì)有違反要求(TLA+ 稱之為 invariant)的地方——例如賬戶余額小于 0,系統(tǒng)中存在了多個(gè) leader 等等。
看最近的幾場 TLA Community Meeting,可以看到 Elasticserach、MongoDB 都有應(yīng)用。
那么既然這個(gè)東西這么好,為什么在國內(nèi)開發(fā)界似乎并沒有特別流行呢?我們在內(nèi)部也嘗試應(yīng)用了一段時(shí)間,在 Mini Storage 上做了一些驗(yàn)證,感覺如果 TLA+ 想應(yīng)用更廣泛的話,可能還是有幾個(gè)問題需要優(yōu)化:
1. 狀態(tài)爆炸,因?yàn)?TLA+ 的驗(yàn)證方式?jīng)Q定了狀態(tài)數(shù)量要經(jīng)過精心的抽象和仔細(xì)的檢查,如果一味地增加狀態(tài)就可能遇到狀態(tài)爆炸的問題;
2. TLA+Spec 是無法直接轉(zhuǎn)換成代碼的,反過來,代碼也無法直接轉(zhuǎn)換成 Spec。那么換句話說,無論是從代碼到 Spec 還是從 Spec 到代碼都有出錯(cuò)的可能,輕則有 Bug,重則可能導(dǎo)致你信心滿滿的算法其實(shí)與你的實(shí)現(xiàn)根本不同;
3. 外部依賴的正確性,這一點(diǎn)可能有點(diǎn)要求過高,但卻也是可靠系統(tǒng)的重要部分,因?yàn)橛脩羰遣还墚a(chǎn)品里是否用到了開源組件,不論是 qemu 的問題還是 Linux 內(nèi)核的問題,客戶只會(huì)認(rèn)為是你的問題,而我們不太可能分析驗(yàn)證每個(gè)依賴;
當(dāng)然了,涉及到算法的正確性證明,形式化證明依然是不可替代的,但不得不說目前階段在云平臺(tái)存儲(chǔ)上應(yīng)用,還沒做到全部覆蓋,當(dāng)然了我們也看到 TLA+ 也在不斷進(jìn)步——
1. 可視化;
2. 增強(qiáng)可讀性;
3. Spec的可執(zhí)行;
這里特別是第三點(diǎn),如果我們的 Spec 能夠被轉(zhuǎn)換成代碼,那么我們就可以將核心代碼的算法部分抽象出來,做成一個(gè)單獨(dú)的庫,直接使用被 Spec 證明過的代碼。
分布式系統(tǒng)的測試和驗(yàn)證,這幾年還有一個(gè)很熱門的詞匯,就是混沌工程。
混沌工程對大多數(shù)人來說并不是一個(gè)新鮮詞匯,可以說它是在單機(jī)應(yīng)用轉(zhuǎn)向集群應(yīng)用,面向系統(tǒng)編程轉(zhuǎn)向到面向服務(wù)編程的必然結(jié)果,我們已經(jīng)看到很多互聯(lián)網(wǎng)應(yīng)用聲稱在混沌工程的幫助下提高了系統(tǒng)的穩(wěn)定性如何如何,那么對于基礎(chǔ)架構(gòu)軟件呢?
在一定程度上可以說 ZStack 很早就開始在用混沌工程的思想測試系統(tǒng)的穩(wěn)定性,首先我們有三個(gè)關(guān)鍵性的外部整體測試:
1. MTBF,這個(gè)概念一般見于硬件設(shè)備,指的是系統(tǒng)的正常運(yùn)行的時(shí)間,對我們來說會(huì)在系統(tǒng)上根據(jù)用戶場景反復(fù)操作存儲(chǔ)(創(chuàng)建、刪除虛擬機(jī),創(chuàng)建、刪除快照,寫入、刪除數(shù)據(jù)等)在此之上引入故障檢查正確性;
2. DPMO,這個(gè)是一個(gè)測試界很老的概念,偏向于單個(gè)操作的反復(fù)操作,例如重啟 1000 次物理機(jī),添加刪除 10000 次鏡像等等,在這之上再考慮同時(shí)引入故障來考察功能的正確性;
3. Woodpecker,這是 ZStack 從最開始就實(shí)現(xiàn)的測試框架,代碼和原理都是開源的,它會(huì)智能的組合ZStack 的上千個(gè) API自動(dòng)找到可以持續(xù)下去的一條路徑,根據(jù)資源當(dāng)前的狀態(tài)判斷資源可以執(zhí)行的 API,這樣一天下來可以組合執(zhí)行數(shù)萬次乃至上百萬次,與此同時(shí)再考慮引入錯(cuò)誤;
上面這些方法,在大量調(diào)用 API、測試 IO 之外,很重要的一點(diǎn)就是注入錯(cuò)誤,例如強(qiáng)制關(guān)閉虛擬機(jī)、物理機(jī),通過可編程 PDU 模擬斷電等等,但是這些方法有一些缺陷:
1. 復(fù)雜場景的模擬能力有限,例如有些客戶存儲(chǔ)并不是一直 IO 很慢,而是呈現(xiàn)波峰波谷的波浪型,這種情況和 IO 始終有明顯 delay 是有比較大的區(qū)別的;
2. 不夠靈活,例如有的客戶存儲(chǔ)隨機(jī) IO 很差但順序 IO 性能卻還可以,也不是簡單的降低 IO 性能就可以模擬的;
總之大部分混沌工程所提供的手段(隨機(jī)關(guān)閉節(jié)點(diǎn)、隨機(jī)殺進(jìn)程、通過 tc 增加延時(shí)和 iproute2、iptables改變網(wǎng)絡(luò)等等)并不能滿足 ZStack 的完全模擬用戶場景的需求。
在這種情況下,我們將擴(kuò)展手段放在了幾個(gè)方向上:
libfiu,libfiu 可以通過 LD_PRELOAD 來控制應(yīng)用調(diào)用 POSIX API 的結(jié)果,可以讓應(yīng)用申請內(nèi)存失敗、打開文件失敗,或者執(zhí)行 open 失敗;
使用 fiurun + fiuctl 可以對某個(gè)應(yīng)用在需要的時(shí)刻控制系統(tǒng)調(diào)用;
fiu對注入 libaio 沒有直接提供支持,但好在 fio 擴(kuò)展和編譯都極為簡單,因此我們可以輕松的根據(jù)自己的需求增加 module;
2. systemtap,systemtap 是系統(tǒng)界的經(jīng)典利器了,可以對內(nèi)核函數(shù)的返回值根據(jù)需求進(jìn)行修改,對內(nèi)核理解很清晰的話,systemtap 會(huì)很好用,如果是對存儲(chǔ)進(jìn)行錯(cuò)誤注入,可以重點(diǎn)搜 scsi 相關(guān)的函數(shù),以及參考這里:Kernel Fault injection framework using SystemTap;
3. device-mapper,device-mapper 提供了 dm-flakey、dm-dust、dm-delay,當(dāng)然你也可以寫自己的 target,然后可以搭配 lio 等工具就可以模擬一個(gè) faulty 的共享存儲(chǔ),得益于 device-mapper 的動(dòng)態(tài)加載,我們可以動(dòng)態(tài)的修改 target 和參數(shù),從而更真實(shí)的模擬用戶場景下的狀態(tài);
4. nbd,nbd 的 plugin 機(jī)制非常便捷,我們可以利用這一點(diǎn)來修改每個(gè) IO 的行為,從而實(shí)現(xiàn)出一些特殊的 IO pattern,舉例來說,我們就用 nbd 模擬過用戶的順序?qū)懞芸斓S機(jī)寫異常慢的存儲(chǔ)設(shè)備;
5. 此外,還有 scsi_debug 等 debug 工具,但這些比較面向特定問題,就不細(xì)說了;
上面兩張圖對這些錯(cuò)誤注入手段做了一些總結(jié),從系統(tǒng)角度來看,如果我們在設(shè)計(jì)階段能夠驗(yàn)證算法的正確性,在開發(fā)時(shí)注意開發(fā)可測試的代碼,通過海量測試和錯(cuò)誤注入將路徑完整覆蓋,對遇到的各種 IO 異常通過測試 case 固化下來,我們的存儲(chǔ)系統(tǒng)一定會(huì)是越來越穩(wěn)定,持續(xù)的走在“正確”的道路上的。
申請創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!