ceph管理平台Calamari的擴充開發
接近大半年沒有寫日誌了,也許是自己越來越懶惰吧。但有時候寫寫東西能夠讓自己沉澱,還是回來記錄一下吧。入職大半年了,熟悉了一些相關的工作,目前主要從事分布式系統的研究和開發,目前的開發主要是停留在管理層面的開發,還未到達修改代碼。這半年的時間熟悉了兩款非常不錯的分布式系統glusterfs和Ceph。兩款分布式儲存產品各有優勢,其中Glusterfs提供的檔案服務是Ceph系統無法提供的。而Ceph的塊裝置、Object Storage Service、檔案系統統一的架構也是GlusterFs無法滿足的。因此各有優勢。
從代碼層面來說,GlusterFs的代碼比較簡單,層次比較明顯,堆棧式的處理流程非常清晰。非常容易實現檔案系統的功能擴充(在用戶端和伺服器端添加處理模組即可),雖然伺服器端、用戶端代碼是一份代碼,但整體而言代碼比較清晰,代碼量較少。
而Ceph採用C++開發,而且系統本身存在多個進程,多個進程構成一個大的叢集,而叢集內部也存在小的叢集,相對Glusterfs而言,代碼要複雜的多,同時Ceph自身實現了自我調整和自我修複。支援軟體系統的定製,通過Crush演算法尋找到對象的儲存位置。
就目前的熱度而言Ceph比較火,但是檔案系統的提供,Glusterfs還是不錯的選擇。
最近在從事Ceph的相關管理平台開發工作,熟悉了官方提供的Calamari平台,該平台目前主要提供了Ceph分布式儲存系統的管理工作,整體上主要是提供了頁面管理Ceph的手段。從目前的實現角度來看,該平台還存在一定的局限性,不能完成強大的功能,或者說目前提供的版本只能提供一些基本的功能。但是Calamari的架構確實非常不錯的。Ceph屬於開源軟體,Calamari也是開源軟體,而且Calamari是由一系列的開源軟體組合而言,這些開源軟體都只完成了其特定的功能。雖然是拼湊,但整體而言,該管理平台的架構是值得借鑒的。
以下部分參考http://www.openstack.cn/?p=2708。
Calamari的架構圖
其中紅框部分為Calamari代碼實現的部分,非紅框部分為非Calamari實現的開源架構。
在Cephserver node安裝的組件有Diamond和Salt-minion。Diamond負責收集監控資料,它支援非常多的資料類型和metrics;每一個類型的資料都是中的一個collector,它除了收集Ceph本身的狀態資訊,它還可以收集關鍵的資源使用方式和效能資料,包括CPU,記憶體,網路,I / O負載和磁碟指標。Collector都是使用本地的命令列來收集資料,然後報告給Graphite。
Graphite不僅是一個企業級的監控工具, 還可以即時繪圖。carbon-cache是Python實現的高度可擴充的事件驅動的I/O架構的後端進程,它可以有效地跟大量的用戶端通訊並且以較低的開銷處理大量的業務量。
Whisper跟RRDtool類似,提供資料庫開發庫給應用程式來操縱和檢索儲存在特殊格式的檔案資料(時間資料點資料),Whisper最基本的操作是建立作出新的Whisper檔案,更新寫入新的資料點到一個檔案中,並擷取檢索的資料點
Graphite_web是使用者介面,用來產生圖片,使用者可以直接通過URL的方式訪問這些產生的圖片。
Calamari 使用了Saltstack讓Calamari Server和Ceph server node通訊。Saltstack是一個開源的自動化營運管理工具,與Chef和Puppet功能類似。Salt-master發送指令給指定的Salt-minion來完成對Cpeh Cluster的管理工作;Salt-minion 在Ceph server node安裝後都會從master同步並安裝一個ceph.py檔案,裡麵包含Ceph操作的API,它會調用librados或命令列來最終和Ceph Cluster通訊。
calamari_rest提供Calamari REST API,詳細的介面請大家參照官方文檔。Ceph的REST API是一種低層次的介面,其中每個URL直接映射到等效的CEPH CLI;Calamari REST API提供了一個更高層次的介面,API的使用者可以習慣的使用GET/POST/PATCH方法來操作對象,而無需知道底層的Ceph的命令;它們之間的主要區別在於,Ceph的REST API的使用者需要非常瞭解Ceph本身,而Calamari 的REST API更貼近對Ceph資源的描述,所以更加適合給上層的應用程式調用。
cthulhu可以理解是Calamari Server的Service層,對上為API提供介面,對下調用Salt-master。
calamari_clients是一套使用者介面,Calamari Server在安裝的過程中會首先建立opt/calamari/webapp目錄,並且把webapp/calamari下的manager.py(django 配置)檔案考進去, calamari_web的所有內容到要放到opt/calamari/webapp下面來提供UI的訪問頁面。
calamari-web包下面的檔案提供所有web相關的配置,calamari_rest和calamari_clients都要用到。
該架構使用了大量的開源軟體,但是從擴充的角度來說還是值得學習的,其中saltstack實現了管理節點和伺服器節點的通訊鏈路,而且支援多節點的管理,這樣不需要考慮管理節點和伺服器之間的通訊問題,在伺服器端只需要實現具體的商務邏輯,即具體管理工作的實現。同時Saltstack是採用Python開發的,這樣便於快速的開發系統,非常的方便管理員在現場進行調試,定位問題。ceph本身也提供了python的API介面,直接通過Ceph的API就能實現叢集的控制。SaltStack的使用使得叢集可以到達一定的規模。SaltStack的Master端實際上作為管理端的控制介面,而SaltStack作為伺服器的Agent端。在Calamari中通過Saltstack發送心跳報文,檢查伺服器的資訊、叢集的資訊,控制命令的分發。可以說理解了SaltStack的基本模式就能理解Calamari的開發和擴充。
該架構中另一組非常重要的開源軟體是diamond+graphite,其中diamond完成了伺服器端資訊的收集工作,而graphite實現了圖表資訊的提供。diamond目前提供了絕大多數開源系統的資訊收集,提供伺服器基本資料的收集(CPU、記憶體、磁碟等資訊),也是採用Python實現,非常容易擴充和調試。目前diamond中已經存在了Ceph的資訊收集。而graphite主要是為前台提供時序資料,這樣就簡化了重新編寫具體的商務邏輯。
學習和瞭解Calamari就必須瞭解一些基本的組件,掌握這些組件的作用和目的。下面從代碼的層面介紹如何擴充Calamari。
1 Calamari的擴充
在Calamari的基礎之上進行新的功能開發,主要分為如下的幾個模組,這部分包括Rest-API部分,Cthulhu、salt用戶端的擴充。關於擴充新功能的基本步驟如下:
>> 擴充URL模組,確定對應的響應介面參數、對應ViewSet中的響應介面。
>> 完成ViewSet中部分介面的實現,這部分主要涉及與cthulhu的互動,如何擷取資料資訊,有些情況下還需要擷取serializer中對象的序列化操作。
>> 完成後台rpc.py中對應類型的擴充,這部分主要是針對部分的post操作。
>> 完成cluster_monitor.py的擴充,對於提供操作的部分功能需要支援create、update、delete等操作,必須提供對應的RequestFactory。而在cluster_monitor.py中需要將對應的RequestFactory添加代碼中。
>> 完成對應RequestFactory類的編寫,這部分主要是完成命令操作的封裝。並構建對應的請求操作。
>> salt-minion的擴充,這部分主要是針對ceph.py檔案的擴充,當然也可以提供新的xxx.py檔案。
接下來以PG的控制和操作為例進行說明。
1.1URL模組擴充
目前Calmamari採用Rest-API形式,採用Django的Rest-Framework架構支援,這部分在rest-api代碼目錄中。Django採用Url和代碼邏輯分離的實現方式,因此URL可以單獨的擴充。
在rest-api/calamari-rest/urls/v2.py中添加如下的有關PG的URL:
url(r'^cluster/(?P[a-zA-Z0-9-]+)/pool/(?P\d+)/pg$', calamari_rest.views.v2.PgViewSet.as_view({'get': 'list'}), name='cluster-pool-pg-list'),
url(r'^cluster/(?P[a-zA-Z0-9-]+)/pool/(?P\d+)/pg/(?P[0-9a-fA-F]+\.[0-9a-fA-F]+)/command/(?P[a-zA-Z_]+)$',
calamari_rest.views.v2.PgViewSet.as_view({'post': 'apply'}),
name='cluster-pool-pg-control'),
以上定義了兩個URL,分別是:
api/v2/cluster/xxxx/pool/x/pg
api/v2/cluster/xxxx/pool/x/pg/xx/command/xxx
以上兩個URL分別指定了PgViewSet中的介面,url的get方法對應了list介面。post介面對應的apply介面。這兩個介面就是PgViewSet中必須實現的。
1.2ViewSet的擴充
在擴充URL之後,接下來就是進行對應響應介面的擴充,這部分的擴充主要是針對在URL中指定的介面類進行實現。在之前的PG指定了兩個不同的介面,分別是擷取和操作命令,對應的代碼路徑為/rest-api/calamari-rest/view/v2.py,具體的代碼如下:
class PgViewSet(RPCViewSet):
serializer_class= PgSerializer
deflist(self, request, fsid, pool_id):
poolName = self.client.get(fsid, POOL, int(pool_id))['pool_name']
pg_summary = self.client.get_sync_object(fsid, PgSummary.str)
pg_pools = pg_summary['pg_pools']['by_pool'][int(pool_id)]
forpg in pg_pools:
pg['pool'] = poolName
return Response(PgSerializer(pg_pools, many=True).data)
defapply(self, request, fsid, pool_id, pg_id, command):
return Response(self.client.apply(fsid, PG, pg_id, command), status=202)
從如上的實現可知,代碼實現了兩個介面,分別是list和apply介面,即對應與之前的get、post操作。以上兩個操作都會與後台cthulhu進行互動。分別是擷取參數和提交請求。返回內容也有一定的差異。
同時在list介面中進行了序列化設定,即PgSerializer,該實現在rest-api/calamari-rest/serializer/v2.py中。
1.2.1 序列化操作
通常在Rest-Api中會進行資料的序列化,這部分並不是一定要進行的,通常在需要更改的操作中是有必要的。如下是Pg的序列化操作:
class PgSerializer(serializers.Serializer):
classMeta:
fields = ('id', 'pool', 'state', 'up', 'acting', 'up_primary','acting_primary')
id =serializers.CharField(source='pgid')
pool =serializers.CharField(help_text='pool name')
state =serializers.CharField(source='state', help_text='pg state')
up =serializers.Field(help_text='pg Up set')
acting =serializers.Field(help_text='pg acting set')
up_primary = serializers.IntegerField(help_text='pg up primary')
acting_primary =serializers.IntegerField(help_text='pg acting primary')
這部分並不是必須的。有些模組可能不存在這部分的操作。在之前的三個步驟中基本上就實現了Rest-API部分的擴充,其中主要的ViewSet的擴充。有關ViewSet實際上實現了cthulhu與rest-api的互動方法。
在ViewSet的擴充中實際上採用了rpc與後台互動,因此在cthulhu的實現部分主要是處理對應的rpc請求。
1.3rpc擴充
rpc.py中實現了所有請求的操作,但是新擴充的操作也是需要支援擴充的,以pg為例繼續說明:
defapply(self, fs_id, object_type, object_id, command):
"""
Apply commands that do not modify an object in a cluster.
"""
cluster = self._fs_resolve(fs_id)
ifobject_type == OSD:
# Run a resolve to throw exception if it's unknown
self._osd_resolve(cluster, object_id)
return cluster.request_apply(OSD, object_id, command)
elifobject_type == PG:
return cluster.request_apply(PG,object_id, command)
else:
raise NotImplementedError(object_type)
而Pg的列表是通過PgSummary擷取。這部分在之前的實現中已存在,之前的代碼實現如下:
defget_sync_object(self, fs_id, object_type, path=None):
"""
Getone of the objects that ClusterMonitor keeps a copy of from the mon, such
asthe cluster maps.
:param fs_id: The fsid of a cluster
:param object_type: String, one of SYNC_OBJECT_TYPES
:param path: List, optional, a path within the object to return insteadof the whole thing
:return: the requested data, or None if it was not found (including ifany element of ``path``
was not found)
"""
ifpath:
obj =self._fs_resolve(fs_id).get_sync_object(SYNC_OBJECT_STR_TYPE[object_type])
try:
for part in path:
if isinstance(obj, dict):
obj = obj[part]
else:
obj = getattr(obj, part)
except (AttributeError, KeyError) as e:
log.exception("Exception %s traversing %s: obj=%s" % (e, path,obj))
raise NotFound(object_type, path)
return obj
else:
returnself._fs_resolve(fs_id).get_sync_object_data(SYNC_OBJECT_STR_TYPE[object_type])
1.4cluster_monitor.py擴充
有關請求的操作都會進行叢集的控制,這部分可以通過cluster_monitor進行實現,以pg為例進行說明。
def__init__(self, fsid, cluster_name, notifier, persister, servers, eventer,requests):
super(ClusterMonitor, self).__init__()
self.fsid = fsid
self.name = cluster_name
self.update_time = datetime.datetime.utcnow().replace(tzinfo=utc)
self._notifier = notifier
self._persister= persister
self._servers = servers
self._eventer = eventer
self._requests = requests
#Which mon we are currently using for running requests,
#identified by minion ID
self._favorite_mon = None
self._last_heartbeat = {}
self._complete = gevent.event.Event()
self.done = gevent.event.Event()
self._sync_objects = SyncObjects(self.name)
self._request_factories = {
CRUSH_MAP: CrushRequestFactory,
CRUSH_NODE: CrushNodeRequestFactory,
OSD: OsdRequestFactory,
POOL: PoolRequestFactory,
CACHETIER: CacheTierRequestFactory,
PG: PgRequestFactory,
ERASURE_PROFILE: ErasureProfileRequestFactory,
ASYNC_COMMAND: AsyncComRequestFactory
}
self._plugin_monitor = PluginMonitor(servers)
self._ready = gevent.event.Event()
這部分主要是將對應的請求與對應的請求工廠類進行綁定,這樣才能產生出合適的請求。
1.5工廠類編寫
該工廠類主要是針對不同的需求,實現具體的介面類,不同的對象有不同的請求類,以Pg為例說明:
from cthulhu.manager.request_factory importRequestFactory
from cthulhu.manager.user_request importRadosRequest
from calamari_common.types importPG_IMPLEMENTED_COMMANDS, PgSummary
class PgRequestFactory(RequestFactory):
def scrub(self,pg_id):
return RadosRequest(
"Initiating scrub on{cluster_name}-pg{id}".format(cluster_name=self._cluster_monitor.name,id=pg_id),
self._cluster_monitor.fsid,
self._cluster_monitor.name,
[('pg scrub', {'pgid': pg_id})])
defdeep_scrub(self, pg_id):
return RadosRequest(
"Initiating deep-scrub on{cluster_name}-osd.{id}".format(cluster_name=self._cluster_monitor.name,id=pg_id),
self._cluster_monitor.fsid,
self._cluster_monitor.name,
[('pg deep-scrub', {'pgid': pg_id})])
defrepair(self, pg_id):
return RadosRequest(
"Initiating repair on{cluster_name}-osd.{id}".format(cluster_name=self._cluster_monitor.name,id=pg_id),
self._cluster_monitor.fsid,
self._cluster_monitor.name,
[('pg repair', {'pgid': pg_id})])
defget_valid_commands(self, pg_id):
ret_val = {}
file('/tmp/pgsummary.txt', 'a+').write(PgSummary.str + '\n')
pg_summary = self._cluster_monitor.get_sync_object(PgSummary)
pg_pools = pg_summary['pg_pools']['by_pool']
pool_id = int(pg_id.split('.')[0])
pool= pg_pools[pool_id]
forpg in pool:
if pg['pgid'] == pg_id:
ret_val[pg_id] = {'valid_commands': PG_IMPLEMENTED_COMMANDS}
else:
ret_val[pg_id] = {'valid_commands': []}
return ret_val
該類中實現了三個不同的命令的實現,該命令主要是進行對應的封裝,這部分關鍵字需要根據ceph源碼中的參數進行選擇,因此在編碼時需要參照ceph源碼中對應命令的json參數名。
1.6salt-minion的擴充
這部分是salt的擴充模組,主要用於擷取對應的資料資訊,執行對應的操作命令等。在cthulhu中通過salt執行對應的操作命令。Ceph.py中有rados.commands等介面,該介面可用於執行ceph的命令。工廠類中封裝的命令最終都會通過該介面執行。
總結
整體而言,Calamari的代碼結構比較清晰,而且該開源架構也是值得學習的,在後續的分布式管理系統中也可參考saltstack+diamond+graphite的架構,前者實現控制邏輯,後面兩個實現資料擷取和資料的儲存顯示。
http://www.bkjia.com/PHPjc/1092984.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/1092984.htmlTechArticleceph管理平台Calamari的擴充開發 接近大半年沒有寫日誌了,也許是自己越來越懶惰吧。但有時候寫寫東西能夠讓自己沉澱,還是回來記錄一下...