寫在前面
剛剛過去的9月,人工智能、雲計算和物聯網界熱鬧非凡,接連迎來了世界物聯網博覽會、世界人工智能大會和阿裡雲栖大會。2018世界物聯網博覽會就在家門口舉行,抓了空去現場看展覽,外行人看看熱鬧,有感于科技的日新月異給生活帶來的便利。
話題扯遠了,回到這篇文章,文章的标題包含“複盤”,顧名思義,是對以前的發生的現象或問題進行回顧,學而不思則罔,目的第一是從問題中總結經驗,最大化發掘它的價值;第二是不斷鍛煉自己分析問題的能力;最後是希望強化在某項知識上的運用能力,畢竟以今天分析昨天,有思維就ok了,但是以今天設想未來,需要長期深厚的積累才行。
1 背景
雲硬碟的快照、克隆,屬于塊存儲RBD基本功能,之前我并沒有太多關注這兩塊,保證功能能用、好用就過去了。
不過在最近遇到幾個與克隆或快照相關聯的問題,涉及到rbd clone、rbd flatten等,正好是之前未遇到過,是以找了時間對産生的問題複盤,也是希望借bug觀察ceph對克隆和快照的處理流程。
整篇文章分以下幾小章節:
1 、背景
2 、bug回顧
3、 利用rbd_max_clone_depth觸發flatten
4 、cinder和ceph層面對clone、flatten的實作
2 bug回顧
2.1 bug1 通過快照建立雲硬碟,删除父快照失敗
這個問題複現步驟很簡單,如下流程圖所示:

遇到的問題就是在删除快照時發生失敗,禁止該删除操作。
由快照建立出的雲硬碟,一般情況與快照是父子關系,在ceph下通過rbd info或rbd children可以查詢到鍊的關系。 如下圖紅色框中所示,在parent一項中,可以看到volume-8e81a7b0-4fdc-49b0-a9ed-4124c8e61f7d是volume-117078c1-c724-44b5-a271-e0f708e9d6b3下的快照克隆得來:
圖1
既然克隆盤與快照存在父子關系,要删除父快照的首要條件是斬斷這種依賴關系,這個就需要通過rbd_flatten_volume_from_snapshot配置項來實作,見/etc/cinder/cinder.conf配置檔案,如下圖中:
在我們的存儲下,rbd_flatten_volume_from_snapshot=false,在false條件下volume和snapshot之間的關系是什麼?
在解決問題之前,先來了解下rbd_flatten_volume_from_snapshot在true、false下的不同作用,見下表:
一目了然,在false條件下,volume和snapshot存在依賴,要想解決該bug,隻要開啟為true即可。
不過先别急着改/etc/cinder/cinder.conf,在此之前我們通過flatten手動解除依賴關系,再重複bug的步驟看是否能夠成功,這個小實驗分5個步驟複現,如下圖3中:
(1)建立volume和快照 PASS
(2)執行克隆 PASS
(3)删除父快照 FAILED
錯誤提示需要對快照先去除保護,unprotect後再執行删除快照,錯誤提示:cannot unprotect: at least 2 child(ren) [1a5e266b8b4567,1aa62d6b8b4567] in pool
(4)執行flatten操作,解除依賴 PASS
(5)再次删除父快照 PASS
小實驗OK,證明了解除克隆盤與snapshot的依賴後再删除快照成功。如果在控制節點的cinder配置檔案中,開啟了rbd_flatten_volume_from_snapshot = true,則 由快照建立出的雲硬碟,會自動合并,清除依賴關系,這樣一來這個雲硬碟就變為扁平的沒有層級的volume。最後記得重新開機cinder服務,使其生效!
2.2 bug2 大容量的空盤建立快照,再通過該快照建立雲硬碟,耗時過長
這個bug提的比較優秀,功能本身屬于正常流程,但之前遺漏了大容量雲硬碟這個場景,比如1T、2T。
問題複現步驟同bug1,隻是少了删除快照的步驟,如下流程圖所示:
通常,我們在雲平台上對雲硬碟建立快照後,會同時建立快照卷,由于精簡配置的屬性,隻需配置設定相對少量的存儲空間即可,當再通過該快照clone出雲硬碟,快照處于隻讀保護, 在cow的機制下,克隆操作會很迅速。下面引用一張ceph官方的圖來解釋:
上圖4中,parent是指源雲硬碟的快照,而child是從快照克隆出來的雲硬碟。
這個bug 2與“2.1 bug 1”對比,相同點都是由rbd_flatten_volume_from_snapshot造成的bug,不同的地方在于true和false。
在bug 2 的雲環境下,rbd_flatten_volume_from_snapshot=true,在上文的bug1中曾說過解除volume和snapshot的依賴關系,取消這種依賴關系叫做flatten,這個flatten花費的時間和源雲硬碟(volume)的大小成正比。
回到bug本身,内部在做排查時,依照以下的順序:
(1)檢查volume qos
眼光先放在了volume qos上,在有資料條件下,qos速率大小(比如write=100MB/S)肯定是會影響到快照建立雲硬碟的速度的。轉念一想,雲硬碟是空盤,并不存在任何object,是以克隆速度應該是很快的。
(2)檢查父雲硬碟、快照、子雲硬碟的實際容量
我們環境中雲硬碟是空盤,不存在任何資料,同樣的由快照建立出的新盤也不會有任何資料。實際是否如此,通過下面的驗證步驟來證明一下:
建立雲硬碟和快照,并擷取真實存儲空間
圖5
圖5中,建立1G的volume,并建立該盤的快照後,rados查詢實際存在的object,找不到任何資料,這是正确的。
由快照建立雲硬碟,并擷取真實存儲空間
圖6
圖6中,快照建立出新的雲硬碟,叫volume-d7199f3d-ed96-446a-83c8-25083a752e23,可以看到在雲硬碟建立過程中,新的雲硬碟和快照時父子關系,建立成功後,新的雲硬碟和快照時父子關系被解除。
圖7所示是擷取新的雲硬碟的實際資料對象,發現已經存在256個object(父雲硬碟總容量為1GB,根據order 22 (4096 kB objects)來切分)
圖8中,随機抽查幾個object,發現其實這些object的容量都是0,并不存在真實的資料。一般而言,從快照建立雲硬碟,代碼實作很簡單,先克隆再flatten,Fill clone with parent data (make it independent),此時flatten會将所有塊從父節點複制到child,但父雲硬碟中沒有資料,flatten操作是不應該産生object的。
這個bug問題就在于flatten會對建立雲盤的每一個對象進行一個寫操作,進而建立無數個大小為0的對象,又在qos的限制下,是以耗時較長。
3 利用rbd_max_clone_depth觸發flatten
麥子邁在《解析Ceph: Librbd 的克隆問題》一文中提到 “Librbd 在卷的克隆時會形成子卷對父卷的依賴,在産生較長的克隆依賴鍊後會有嚴重的性能損耗”。這個理論其實和cow下多快照産生的性能衰減是一樣的,對ceph的雲硬碟做快照,每次做完快照後再對雲硬碟進行寫入時就會觸發COW操作, 即1次讀操作、2次寫操作,volume→volume的克隆本質上就是将 volume 的某一個 Snapshot 的狀态複制變成另一個volume。
為解決在産生較長的克隆依賴鍊後會有嚴重的性能損耗問題,在OpenStack Cinder 的/etc/cinder/cinder.conf中提供一個參數,可以解除父子依賴關系,在超過自定義設定的閥值後選擇強制 flatten。
在圖9中,通過 rbd_max_clone_depth來控制最大可克隆的層級。
rbd_max_clone_depth = 5 這個參數控制卷克隆的最大層數,超過的話則使用 fallten。設為 0 的話,則禁止克隆。
為了驗證這個過程,下面我們做個實驗,建立1個volume,命名為01,依次複制下,即由01複制成02,02複制為03,03複制為04,04複制為05,05複制為06,06複制為07,如下圖流程圖:
實驗預期結果,就是當從06複制到07時,滿足rbd_max_clone_depth > 5,此時觸發flatten操作。
圖10
圖11
圖10、圖11是 複制雲硬碟後的查詢到克隆盤資訊
圖12
圖12中, 上面的log記錄了複制07時,觸發了flatten操作,對上級雲硬碟06執行flatten操作,開始執行合并。
圖13
圖13所示是Flatten成功後,可以看到雲硬碟06 的parent一項消失,此時在頁面上可以删除雲硬碟06
4 cinder和ceph層面對clone、flatten的實作
現在市面上很多講ceph的書(大多數翻譯自ceph中國社群之手),在RBD塊存儲章節都會對快照、克隆等操作花很多篇幅去描述,基本都是在rbd層通過指令一步步分解rbd clone過程來講原理。
對于類似我這樣的剛接觸ceph不久的人來說,知識點分散在各處,看了前面忘了後面,很難在腦子裡建立完整的概念,當然主要原因還是自己太菜了,迷霧重重看不透!
言歸正傳,我隻是想大概的了解下對雲硬碟執行操作在底層是如何實作的,是以還是由上文中提到的小處(bug)來入手,自頂向下先設計一個思考流程,帶着目标按照這個從上到下的順序去了解,如下圖所示:
注:以下涉及的代碼均來自GitHub開源,如有雷同,純屬巧合!
4.1 從快照克隆卷的流程
(1)openstack cinder
自頂向下,先從cinder層入手,通過代碼可以看到從快照克隆出volume的思路,從本質上講,快照克隆出新的卷,也是volume create的性質,是以先來了解下volume create過程
cinder:/cinder/volumes.py
volumes.py中def create方法我省略了很多,主要就是通過req、body的參數來擷取建立volume所需要的參數,根據不同參數來發送具體的建立volume請求,因為我是從快照來建立,snapshot id自然必不可少,在 volumes.py最後實際調用new_volume = self.volume_api.create()去實作。
cinder:/cinder/volume/api.py
經過volume_api.create(),在/cinder/volume/api.py來處理前端發來的卷相關的所有請求,通過create_what{}表示volume的實作參數,然後分别就調用cinder.scheduler的scheduler_rpcapi,cinder.volume的volume_rpcapi建立建立volume的工作流:create_volume.get_flow
注:關于create volume flow的流程及具體實作,見/cinder/volume/rpcapi.py:def create_volume(),/cinder/volume/flows/api/create_volume.py,本篇省略過程
cinder:/cinder/volume/manager.py
對于api來講,隻是做到處理前端發來的卷相關的所有請求,具體實作交由manager下的去完成,rpcapi調用inder/volume/manager.py:def create_volume()去操作
執行中發現crate voluem 有snapshot id,然後調用/cinder/volume/flows/manager/create_volume.py下的私有方法_create_volume_from_snapshot()
最後根據配置檔案指定的RBD後端請求/cinder/volume/drivers/rbd.py的create_volume_from_snapshot()
cinder:/cinder/volume/drivers/rbd.py
衆所周知,一般cinder使用RBD驅動來對接底層的後端存儲(比如ceph、xsky),在openstack cinder層面最終交由create_volume_from_snapshot()實作,因為是通過快照來建立volume,還需要調用私有方法_clone(),滿足條件的話,還要調用_flatten()和_resize()。
(2)librbd
經曆多方接力才結束在cinder層面的流程,這還不算完,真正要實作create volume from snapshot的建立,核心在調用ceph執行。
ceph:/src/pybind/rbd/rbd.pyx
/ceph/blob/v10.2.3/src/librbd/librbd.cc
在librbd中對外提供api在class RBD中,從librbd.cc函數中看到有多個clone()、clone2()、clone3()函數,差別在于根據傳入的不同參數來調用對應的函數,但這些函數都不像是具體的功能實作,隻是一些相關參數傳值。
再看看/ceph/blob/v10.2.3/src/librbd/internal.cc函數,同librbd.cc一樣,對應的clone()也是3種,因為篇幅如下展示的是clone3()函數(實際命名并不如此,通過參數來區分得知是clone2):
将librbd.cc、internal.cc兩個函數聯系起來看,librbd.cc隻是定義了對外的各種函數接口,接口的具體實作,調用的還是internal.cc中定義的函數内容。
總結一下,根據自己的了解将整個流程繪成圖,如下圖所示中,需要一提的是,我沒有涉及到librados的實作過程,因為clone等volume的操作,librbd可以說就是rbd的完整實作,rados隻是作為後端的存儲
4.2 flatten的流程
在前文“ 利用rbd_max_clone_depth觸發flatten”小節中,我們描述了一個volume clone的過程,通過cinder.conf的一個參數,當滿足rbd_max_clone_depth最大層數後,觸發flatten操作,下面我們通過代碼去看一看具體實作的流程。
對于上層雲平台而言,從雲硬碟1克隆出雲硬碟2,或者從快照建立雲硬碟,一般是能夠觸發flatten操作的主要場景,其實兩者實作原理基本一緻。
是以,和之前的由snapshot來實作建立新的雲硬碟一樣,首要都是從create()開始,隻是參數不同,克隆盤在create過程先要擷取parent volume id
之後也是一樣經曆api→manager→driver的過程,這裡省掉重複的過程,直接看cinder調用rbd驅動對克隆雲硬碟的實作代碼,如下圖中/cinder/volume/drivers/rbd.py:
調用了私有方法_get_clone_depth()來判斷depth,調用_flatten()來實作flatten操作,當然flatten過程經曆一系列過程,在parent volume上建立snapshot,對snapshot加保護、再執行clone,然後flatten,這個過程一樣可以通過rbd 指令來完成。
建立RADOSClient,連接配接到ceph rados,這裡也是先調用clone()去執行,再觸發flatten()操作,和我預期不同,flatten的過程比想象中還要複雜,才疏學淺,對整個過程的了解還需要更多的時間,隻能先用根據自己的了解畫出一張流程圖表示一下: