寫在前面前端
剛剛過去的9月,人工智能、雲計算和物聯網界熱鬧非凡,接連迎來了世界物聯網博覽會、世界人工智能大會和阿里雲棲大會。2018世界物聯網博覽會就在家門口舉行,抓了空去現場看展覽,外行人看看熱鬧,有感於科技的突飛猛進給生活帶來的便利。後端
話題扯遠了,回到這篇文章,文章的標題包含「覆盤」,顧名思義,是對之前的發生的現象或問題進行回顧,學而不思則罔,目的第一是從問題中總結經驗,最大化發掘它的價值;第二是不斷鍛鍊本身分析問題的能力;最後是但願強化在某項知識上的運用能力,畢竟以今天分析昨天,有思惟就ok了,可是以今天設想將來,須要長期深厚的積累才行。api
1 背景ide
雲硬盤的快照、克隆,屬於塊存儲RBD基本功能,以前我並無太多關注這兩塊,保證功能能用、好用就過去了。函數
不過在最近遇到幾個與克隆或快照相關聯的問題,涉及到rbd clone、rbd flatten等,正好是以前未遇到過,所以找了時間對產生的問題覆盤,也是但願借bug觀察ceph對克隆和快照的處理流程。性能
整篇文章分如下幾小章節:阿里雲
1 、背景雲計算
2 、bug回顧人工智能
三、 利用rbd_max_clone_depth觸發flattenspa
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
圖十、圖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)openstack cinder
對於上層雲平臺而言,從雲硬盤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 命令來完成。
(2)librbd
建立RADOSClient,鏈接到ceph rados,這裏也是先調用clone()去執行,再觸發flatten()操做,和我預期不一樣,flatten的過程比想象中還要複雜,才疏學淺,對整個過程的瞭解還須要更多的時間,只能先用根據本身的理解畫出一張流程圖表示一下: