Cinder Volume 服务启动流程分析和周期性任务分析
1. 服务启动流程
1) systemctl status openstack-cinder-volume ----> /usr/bin/cinder-volume
2) cat /usr/bin/cinder-volume ----> from cinder.cmd.volume import main
3) 那么服务启动的起始位置就是 cinder.cmd.volume 的 main 函数
a) 通过 _launch_services_posix 不难看出每个 backend 都是一个 launcher
那么 launcher 又是什么?
launcher = service.get_launcher()
from cinder import service
def get_launcher() ----> def process_launcher():
return service.ProcessLauncher(CONF, restart_method='mutate')
from oslo_service import service
launcher.launch_service(server)
self._start_child(wrap)
pid = os.fork()
由此可以看出每个 backend 启动的 cinder-volume service 都是单独的子进程。
即每个 launcher 都是一个子进程。
def launch_service(self, service, workers=1):
另一方面可以看出开启多少个子进程,可以通过 workers 参数来控制。
b) _launch_service 是每个 backend 的启动主函数
service 的 host 的命令:host = "%s@%s" % (backend_host or CONF.host, backend)
server = service.Service.create
from cinder import service
c) def start(self)为服务启动入口,主要包含下面的内容(为啥是 start?待深入调查):
1) self.manager.init_host,这边的详解看 2
manager = CONF.get('%s_manager' % subtopic, None)
volume_manager,default='cinder.volume.manager.VolumeManager'
2) self.rpcserver.start()
3) if self.report_interval: 默认值 10。这边详解看 3
self.report_state
4) if self.periodic_interval: 默认值 60。这边详解看 4
self.periodic_tasks
2. init_host 过程分析
init_host 整个过程精简如下:
try:
driver.do_setup(ctxt)
driver.check_for_setup_error()
except Exception:
LOG.exception()
return
driver.set_initialized()
self.publish_service_capabilities(ctxt)
1) driver 的初始化在 VolumeManager 的 init 方法中。也就是说Cinder-volume 服务启动过程中,如果有任何一个卷驱动 init 失败,会导致服务启动失败(所有的卷驱动都启动失败)。
2) 把一些核心的和存储的初始化操作放到了 do_setup 中,当 do_setup 失败时,并不会导致服务启动失败,也不会影响 multi backend 的其他 backend。
3) 换一个角度就是说服务启动了,cinder service-list 显示正常了,并不表示这个 backend 服务可用,因为它的初始化未完成。那么这个时候就得看日志才能确定错误了。
4) set_initialized 将驱动的 initialized 属性设定为 true,标志驱动成功启动并初始化完成。
5) 这是一个完整的 cinder-volume 驱动初始化的过程,如果我们自己需要在代码中启动一个 volume driver,这就是一个很好的参考。K 版本的cinder-backup manager 就有类似的用法。
6) publish_service_capabilities,这边就是 cinder-volume 的周期性任务添加点。这边详解看 5
3. report_state
其实这个方法就是单纯的获取 service 列表,count +1 ,再更新到数据库。
分析代码找到之前遇到的一个 bug 的 fix:驱动初始化失败,但是 cinder service-list 却是 up。
https://bugs.launchpad.net/cinder/+bug/1446750
https://github.com/openstack/cinder/commit/2a84ae0fc015a43617521727ca31152066839d3c#diff-8f2683e56fe80449628d16fe5b4f9b37
这边补充下 Cinder-Scheduler 判断服务是 down 还是 up 过程如下:
volume_services = db.service_get_all_by_topic(context,
topic,
disabled=False)
def service_is_up(service):
"""Check whether a service is up based on last heartbeat."""
last_heartbeat = service['updated_at'] or service['created_at']
# Timestamps in DB are UTC.
elapsed = (timeutils.utcnow() - last_heartbeat).total_seconds()
return abs(elapsed) <= CONF.service_down_time
就是对比数据库的 service 更新时间和现在的时间差,再跟配置文件可容忍的时间差进行比对。
那么问题来了:service['updated_at'] 是什么时候怎么更新的呢?就是这儿做的更新。
4. periodic_tasks
self.manager.run_periodic_tasks
cinder.volume.manager.VolumeManager 没有实现该方法
父类manager.CleanableManager
manager.SchedulerDependentManager
from cinder import manager
CleanableManager 无该方法,父类是 object
SchedulerDependentManager 无该方法,父类是 ThreadPoolManager -> 无该方法,父类是 Manager -> 无该方法,父类是 base.Base, PeriodicTasks
from cinder.db import base base.Base 无该方法,父类是 object
PeriodicTasks 无该方法,父类是 periodic_task.PeriodicTasks
from oslo_service import periodic_task
找到该方法的最终实现
PeriodicTasks 是一个元类
self._periodic_tasks 一开始并没有任何值
所以这个函数实际上第一次并没有真正执行周期性任务
周期性任务的添加参 5
5. publish_service_capabilities
@periodic_task.periodic_task
def publish_service_capabilities(self, context):
"""Collect driver status and then publish."""
self._report_driver_status(context)
self._publish_service_capabilities(context)
通过这个装饰器只是将它加入周期性任务,周期性的周期性执行实际上是通过 1-3-c-4 来实现的
这边就涉及到一个元类的用法:
1) 通过装饰器 @periodic_task.periodic_task 将需要周期性执行的任务再次包装为一个 periodic_task 对象 class-A。
2) class-A 初始化的时候先找到了当前文件下的元类 class _PeriodicTasksMeta(type),通过它的 init 将 class-A 添加到周期性任务列表 cls._periodic_tasks.append((name, task))。
3) 1-3-c-4 通过 loopingcall 初始化了周期性任务,并定时执行,那么这个时候 2 添加了新的任务,在下一次执行的时候就会开始执行周期性任务。
_report_driver_status
volume_stats = self.driver.get_volume_stats(refresh=True)
就是调用 volume driver 获取存储的容量情报
_publish_service_capabilities
self.scheduler_rpcapi.update_service_capabilities
使用 RPC 将容量情报推动到 cinder-scheduler
最新的代码已经将周期性任务的装饰器移动了位置,参:
https://review.openstack.org/#/c/550531/
移动的原因是上面两个任务不同步,也就是 cinder-scheduler 获取都是上一次的容量情报,这边涉及到线程用法,详细看上述的代码说明