openstack 扩展开发最佳实践之计算节点高可用

时间:2017-03-27 12:29 来源:网管之家整理 字体:[ ] 评论:

openstack 扩展开发最佳实践之计算节点高可用。注意是扩展开发,这个词是我杜撰的,大概意思是指基于openstack的rest api做的一些开发,用于辅助相关功能,而不是直接改动openstack内的代码,怎么修改添加openstack各个组件的代码不在此文章内容内。

首先,千万,千万,千万不要用Openstack提供的SDK,原因如下。

一,SDK的相关文档并不健全。

二,版本不够统一,即兼容的问题。

所以不要使用openstack的SDK而是自己查阅openstack的API文档,通过requests库发http请求要比SDK灵活并便捷得多的方式。但是难过的地方在于开头,这也是本章的主要内容。为了使内容更加贴近现实,我们要做的需求是Openstack计算节点的高可用,主题内容如下:

一:解决思路

二:查询相关API

三:组织代码

四:总结

(一)解决思路

其实openstack计算节点高可用的方案应该是不同的厂商有不同解决方案,这里不具体分析各个方案,二是针对笔者自己实现的一个方案的实现讲解。

一,怎样确定计算节点挂了

二,怎样将计算节点上的虚拟机迁移到现存可用的计算节点

三,迁移之后是否应该采取相关措施

笔者的对应的解决方案如下,

一:通过openstack自身机制监控,即计算节点不可用时,不会上报状态,控制节点会将该计算节点的状态state设置为down,为down的就判定该计算节点不可用。

二:通过evacuate API将不可用的计算节点上的虚拟机迁移到可用的计算节点,共享存储肯定是必须的。

三:通过将该不可用计算节点status设置为disable,以达到隔离效果,即该计算节点即使又好了,也不会将新的虚拟机调度到该计算节点,除非人为干预。

注:这里没有考虑网络环境。

(二)查询相关API

一:所有计算节点可用状态:/os-services

二:查询指定计算节点上的虚拟机:/servers/detail

三:evacuate实例:/servers//action

四:disable阶段节点:/os-services/disable

主要就是上面这些API了。

(三)组织代码

1.首先是认证获取token,获取各个endpoint。

2.统一封装HTTP请求过程。

3.然后为每个对应的API封装一个函数。

4.将整个需求的逻辑独立与封装的函数及类。

相对应的代码如下

1.这里新建一个baseInfo的类作为基类,用于获取token及相应的endpoint

classbaseInfo(object):
"""inittheinfoofallcompute,andgetthetokenforaccesstheapi"""

def__init__(self):
confFile=sys.argv[1]

headers={}
headers["Content-Type"]="application/json"

self.cf=ConfigParser()
self.cf.read(confFile)
self.conf=self.getConf()
self.headers=headers

self.catalog,self.token=self.getToken()
self.url=[urlforurlinself.catalogifurl["name"]=="nova"]
self.url=self.url[0]["endpoints"][0]["publicURL"]

defgetConf(self):
try:
conf={
"url":self.cf.get("ser","OS_AUTH_URL"),
"uname":self.cf.get("ser","OS_USERNAME"),
"passwd":self.cf.get("ser","OS_PASSWORD"),
"tname":self.cf.get("ser","OS_TENANT_NAME"),
"interval":self.cf.get("ser","INTERVAL")}

exceptExceptionase:
logging.critical("加载配置文件失败")
logging.critical(e)
sys.exit(1)

returnconf

defgetToken(self):
headers=self.headers
url=self.conf["url"]+"/tokens"
data='{"auth":{"tenantName":"%s","passwordCredentials":{"username":"%s","password":"%s"}}}'
data=data%(self.conf["tname"],self.conf["uname"],self.conf["passwd"])
try:
logging.debug("开始获取Token")
ret=requests.post(url,data=data,headers=headers)
logging.debug("requesturl:%s"%ret.url)
ret=ret.json()
exceptExceptionase:
msg="获取Token失败data:%sheaders:%s"%(data,headers)
logging.critical(msg)
logging.critical(e)

catalog=ret["access"]["serviceCatalog"]
token=ret["access"]["token"]["id"]

returncatalog,token

2.将每个交互API的HTTP请求独立出来,所谓DRY吧,不要重复你自己,这样的好处自然是不用重复写代码,再者就是在一个统一的地方对所有调用了的,统一包装或修改。

defgetResp(self,suffix,method,data=None,headers=None,params=None,isjson=True):
"""returntheresultofrequests"""
url=self.url+suffix
ifheaders==None:
headers=self.headers.copy()
headers["X-Auth-Token"]=self.token

req=getattr(requests,method)
try:
ret=req(url,data=data,headers=headers,params=params,verify=False)
logging.debug("requesturl:%s"%ret.url)
exceptExceptionase:
msg="%s访问%s失败data:%sheaders:%s"%(method,suffix,data,headers)
logging.critical(msg)
logging.critical(e)
sys.exit(1)

ifret.status_code==401:
logging.warning("Token过期,重新获取Token")
self.catalog,self.token=self.getToken()
headers["X-Auth-Token"]=self.token
logging.debug("requestheaders:%s"%ret.request.headers)
ret=req(url,data=data,headers=headers)

ifisjson:
ret=ret.json()

returnret

3.封装每个API作为函数,函数是第一公民嘛。

...
defchkNode(self):
"""getthecomputelistservicedown"""
suffix="/os-services"
ret=self.getResp(suffix,"get")
ret=ret["services"]

cmpAll=[host["host"]forhostinretifhost["binary"]=="nova-compute"]
cmpDown=[host["host"]forhostinretifhost["state"]!="up"andhost["binary"]=="nova-compute"]

returncmpDown,cmpAll

defchkSerFromNode(self):
"""gettheserverlistfromfailednode"""
suffix="/servers/detail"
params={"all_tenants":1}

ret=self.getResp(suffix,"get",params=params)

ret=ret["servers"]
serDown=[ser["id"]forserinretifser["OS-EXT-SRV-ATTR:host"]inself.cmpDown]
self.serDown.extend(serDown)

defevacuate(self,serID):
"""evacuatetheserver"""
suffix="/servers/%s/action"%serID
data='{"evacuate":{"onSharedStorage":"True"}}'
ret=self.getResp(suffix,"post",data=data,isjson=False)

returnret.ok
...

4.逻辑独立出来,不要做太多事情。

defmain():
iflen(sys.argv)>1andos.path.isfile(sys.argv[1]):
ch=check()
fen=fence()
recov=recover()
whileTrue:
interval=ch.conf["interval"]

try:
interval=int(interval)
exceptExceptionase:
msg="时间间隔设置有误interval:%s"%interval
logging.critical(msg)
logging.critical(e)
sys.exit(1)

cmd="pcsstatus|grep'CurrentDC'|grep`hostname`"
p=sp.Popen(cmd,shell=True,stdout=DEVNULL,stderr=sp.STDOUT)
vip=p.wait()
ifnotvip:
ch.check()
logging.info("失败的计算节点%s"%ch.cmpDown)
fen.fence(ch.cmpDown)
logging.info("失败的计算节点下面的server%s"%ch.serDown)
recov.recover(ch.serDown)

time.sleep(interval)
else:
print"配置文件不存在"

(四)总结

不要用SDK,rest API的文档足够健全。

至于完整项目可参考我的Github。

https://github.com/youerning/UserPyScript

顶一下(0) 踩一下(0)
Top_arrow