讨论的是openstack中卷的host属性。
印象中,社区H版本对于volume的host值表示的就是对应cinder-volume服务的host配置项,默认为GuestOS的hostname。比如单板A上的cinder-volume创建了卷V,那么V的host就是A。同时,把该host值作为rpc转发的topic,即cinder-scheduler组件已不同的host为单位进行区分,调度确定到具体哪个host后,再下发消息。这种方式跟nova模块很类似,nova-scheduler也是以底下nova-compute所在的host为区分。但cinder另外还支持host字段携带后端存储名称,即“[email protected]”这种形式,见如下代码:
if CONF.enabled_backends: for backend in CONF.enabled_backends: host = "%[email protected]%s" % (CONF.host, backend) server = service.Service.create(host=host, service_name=backend) launcher.launch_server(server)
即表示一个host可以对接多个不同存储后端,host名分别为“[email protected]”,[email protected]等,然后分别启动一个独立的cinder-volume以对应。 我觉得nova也是可以这么玩耍的,一个host上理论上也可以存在多个hypervisor,也可以同时运行多个nova-compute,这也是数据表compute_nodes中node字段的含义吧。
这里有个很严重的问题,因为rpc的消息转发依赖卷的host字段,如果单板A上已经创建了很多卷,现在很不幸的事情发生了,单板A宕机且长时间不能恢复!这样一来,整个数据中心岂不是傻眼了,所有想操作原本由A创建的卷都宣告失败,挂不了卷,删不了卷等等。为了避免这么可怕的事情发生,可以如此修改,让多个cinder-volume服务对接同一个后端存储,同时统一host字段。这么一来,即使其中有个别单板发生宕机,其他正常单板也可以处理cinder-api发送过来的rpc消息。相当于把cinder-scheduler到cinder-volume之间的通信模型转变成为类似cinder-api到cinder-scheduler之间的通信模型,所谓负载均衡。
当然这么改依靠的是cinder-volume的状态无关性,也会带来一些不兼容的其他小问题,但都应该不算问题了,全加起来也比上述的问题来的轻。不知道社区如何看待此问题。
再来看Juno,同样是cinder-volume的启动脚本:
if CONF.enabled_backends: for backend in CONF.enabled_backends: CONF.register_opts([host_opt], group=backend) backend_host = getattr(CONF, backend).host host = "%[email protected]%s" % (backend_host or CONF.host, backend) server = service.Service.create(host=host, service_name=backend) launcher.launch_service(server)
有点变化的是,J版本支持配置项中对具体backend域设置host值,而非统一值。这就比H版本增加了一点灵活性。
比如我可以这样玩,在后端是LVM的单板上,cinder.conf这么配:
enabled_backends=lvm [lvm] host=lirenke
然后当cinder-volume起来后,如此结果:
这符合预期,然后我尝试创建一个卷,成功后show下,按照H版本,这个host也应是[email protected],不过:
奇葩的事情出现了,为啥在后面又加了“#LVM_iSCSI”,什么玩意儿。rpc使用topic又是什么字段了呢?不知道,于是乎只能看下J版本源码了。
原来,J版本引入了pool的概念,即存储池子。一个cinder-volume管辖的领域内可以有多个pool,即一个backend和pool是一对多的关系,资源上报需要按照pool为单位上报,而非原来的host。再进一步讲,cinder-scheduler中保存的host_state对象单位变成了pool。
完整的host名称(即volume对象的host属性)为: [email protected]#pool
查看cinder-scheduler下发给cinder-volume的rpc topic,使用了backend级别,即取到‘#’号之前的字符串,如“[email protected]”。至于pool名称的选取,在LVM的代码中,使用volume_backend_name的配置项,默认值即为LVM_iSCSI。如果有driver连默认值都没有,那么就会使用__pool”来做名称。
在调度模块通过方法:
hosts = self.host_manager.get_all_host_states(elevated)
来获取所有hosts时,其实返回的对象不是先前的HostState对象了,而是PoolState对象,PoolState对象的host属性做过特殊处理,把实际host的值和pool的名称做了结合,所以刷新到数据库的volume对象host属性值,就变成了host#pool_name。这就是为什么我创建的卷host属性会是“[email protected]#LVM_iSCSI”。
注意的是,在数据库查询API方法如volume_get_all_by_host,传入的是初始的hostname,实现的时候已经做了模糊处理,即传入lirenke,也会把[email protected]**,[email protected]**#**给查出来,不影响正常逻辑。
host,backend,pool三个概念容易让人困惑,灵活性的确是增加了。但是不是这样做就解决了上述严重的HA问题呢?似乎可以解决,原理一样,可以让两个cinder-volume取相同的host,相同的backend,不同的pool,但这么做其实跟后端存储driver的实现强相关了,框架这么实现给了driver一定灵活性,没有能通吃所有后端的配置。
先讨论到这,其隐藏的灵活性有待进一步体会和实践,总之,host字段是变复杂了。