別讓bug跑了,通過問題理解ceph的克隆過程 薦
寫在前面
剛剛過去的9月,人工智慧、雲端計算和物聯網界熱鬧非凡,接連迎來了世界物聯網博覽會、世界人工智慧大會和阿里雲棲大會。2018世界物聯網博覽會就在家門口舉行,抓了空去現場看展覽,外行人看看熱鬧,有感於科技的日新月異給生活帶來的便利。
話題扯遠了,回到這篇文章,文章的標題包含“覆盤”,顧名思義,是對以前的發生的現象或問題進行回顧,學而不思則罔,目的第一是從問題中總結經驗,最大化發掘它的價值;第二是不斷鍛鍊自己分析問題的能力;最後是希望強化在某項知識上的運用能力,畢竟以今天分析昨天,有思維就ok了,但是以今天設想未來,需要長期深厚的積累才行。
1 背景
雲硬碟的快照、克隆,屬於塊儲存RBD基本功能,在很多版本的openstack雲平臺中我們都使用過,之前我並沒有太多關注這兩塊,保證功能能用、好用就過去了。
不過在最近的產品中,遇到幾個與克隆或快照相關聯的bug,涉及到rbd clone、rbd flatten等,正好是之前未遇到過,因此找了時間對產生的bug覆盤,也是希望借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之間的關係是什麼?
在解決bug之前,先來了解下 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=40MB/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的限制下,所以耗時較長。最終解決方法就是:根據快照的object_map,跳過不存在的物件,只發送有資料的物件即可。
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)來入手,自頂向下先設計一個思考流程,帶著目標按照這個從上到下的順序去理解,如下圖所示:
4.1 從快照克隆卷的流程
(1)openstack cinder
自頂向下,先從cinder層入手,通過程式碼可以看到從快照克隆出volume的思路,從本質上講,快照克隆出新的卷,也是volume create的性質,所以先來了解下volume create過程
cinder:/cinder/api/v2/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的過程比想象中還要複雜,才疏學淺,對整個過程的瞭解還需要更多的時間,只能先用根據自己的理解畫出一張流程圖表示一下: