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