提示:本文基于openstack liberty版本源码进行梳理
前言
上一篇文章(【OpenStack Liberty】cinder service模块启动流程)中以cinder-sechduler启动为例走读了代码。也提到scheduler、volume、backup都属于Service,而cinder-api属于WSGI Service,其实主要启动流程基本一致,本文主要偏向讲WSGIService启动流程。
一、启动流程
1.main方法
代码文件位置:cinder/cmd/api.py
...
def main():
...
# 1. 获取一个process launcher
launcher = service.process_launcher()
# 2. 创建一个名为`osapi_volume`的`WSGIService`服务对象
server = service.WSGIService('osapi_volume')
# 3. 加载service
launcher.launch_service(server, workers=server.workers)
service.wait()
可以看到跟Service服务的启动方式大致相同,只不过这里使用的Launcher和cinder-scheduler不一样。
openstack中的Launcher分为两种:
- ServiceLauncher
- ProcessLaunche
ServiceLauncher用来启动单进程的服务;而ProcessLauncher用来启动有多个worker子进程的服务,如各类api服务(nova-api、cinder-api)等
2.ProcessLauncher
ProcessLauncher直接继承于Object,同样也有launch_service方法
lauch_service除了接受service以外,还需要接受一个workers参数,即子进程的个数,然后调用_start_child启动多个子进程
class ProcessLauncher(object):
def launch_service(self, service, workers=1):
_check_service_base(service)
wrap = ServiceWrapper(service, workers)
...
LOG.info('Starting %d workers', wrap.workers)
while self.running and len(wrap.children) < wrap.workers:
# 调用_start_child启动子进程
self._start_child(wrap)
_start_child
调用了一个os.fork()
,然后子进程开始运行,子进程调用_child_process
def _start_child(self, wrap):
...
pid = os.fork()
if pid == 0:
# 子进程调用_child_process
self.launcher = self._child_process(wrap.service)
while True:
self._child_process_handle_signal()
status, signo = self._child_wait_for_exit_or_signal(
self.launcher)
if not _is_sighup_and_daemon(signo):
self.launcher.wait()
break
self.launcher.restart()
os._exit(status)
LOG.debug('Started child %d', pid)
wrap.children.add(pid)
self.children[pid] = wrap
return pid
_child_process其实很简单:
- 创建一个Launcher
- 调用Laucher.launch_service方法
前面介绍过,其实ServiceLauncher继承自Launcher,也是调用的launcher_service方法,将服务启动,因此接下来的步骤可以参考前面,最终都将调用service.start方法启动服务
def _child_process(self, service):
...
launcher = Launcher(self.conf, restart_method=self.restart_method)
launcher.launch_service(service)
return launcher
3. WSGIService启动
通过刚才ProcessLauncher的分析,我们知道服务的启动最终调用了service的start方法,而这里的service就是我们最开始在api.py中创建的service,然后一层层传进后面的启动器中的,我们继续回到WSGIService类中的start(self)方法
def start(self):
if self.manager:
self.manager.init_host()
# wsgi.Server
self.server.start()
self.port = self.server.port
联系上下文知:这里的self.server
为wsgi.Server
实例。
代码位置cinder/wsgi/eventlet_server.py
def start(self):
# The server socket object will be closed after server exits,
# but the underlying file descriptor will remain open, and will
# give bad file descriptor error. So duplicating the socket object,
# to keep file descriptor usable.
dup_socket = self._socket.dup()
dup_socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
# NOTE(praneshp): Call set_tcp_keepalive in oslo to set
# tcp keepalive parameters. Sockets can hang around forever
# without keepalive
netutils.set_tcp_keepalive(dup_socket,
CONF.tcp_keepalive,
CONF.tcp_keepidle,
CONF.tcp_keepalive_count,
CONF.tcp_keepalive_interval)
# ssl相关暂时忽略
wsgi_kwargs = {
# 关注下这个参数
'func': eventlet.wsgi.server,
'sock': dup_socket,
'site': self.app,
'protocol': self._protocol,
'custom_pool': self._pool,
'log': self._logger,
'socket_timeout': self.client_socket_timeout,
'keepalive': CONF.wsgi_keep_alive
}
self._server = eventlet.spawn(**wsgi_kwargs)
注意 wsgi_kwargs 中的参数 func,它的值为 eventlet.wsgi.server,在 eventlet/wsgi.py 的定义如下:
def server(sock, site,
...):
...
try:
serv.log.info('({0}) wsgi starting up on {1}'.format(serv.pid, socket_repr(sock)))
while is_accepting:
try:
client_socket, client_addr = sock.accept()
client_socket.settimeout(serv.socket_timeout)
serv.log.debug('({0}) accepted {1!r}'.format(serv.pid, client_addr))
connections[client_addr] = connection = [client_addr, client_socket, STATE_IDLE]
(pool.spawn(serv.process_request, connection)
.link(_clean_connection, connection))
except ACCEPT_EXCEPTIONS as e:
if support.get_errno(e) not in ACCEPT_ERRNO:
raise
except (KeyboardInterrupt, SystemExit):
serv.log.info('wsgi exiting')
break
finally:
for cs in six.itervalues(connections):
prev_state = cs[2]
cs[2] = STATE_CLOSE
if prev_state == STATE_IDLE:
greenio.shutdown_safe(cs[1])
pool.waitall()
serv.log.info('({0}) wsgi exited, is_accepting={1}'.format(serv.pid, is_accepting))
...
这里可以看到sock.accept()
监听请求,每当接收到一个新请求,调用 pool.spawn() 启动一个协程处理该请求
自此cinder-api启动完毕。
4. 流程图
总结
以上就是cinde-api的启动流程,仅仅贴上主要代码及注释,中间各项细节加载可以根据这个方向去仔细看,本文不再贴出。
如有错误欢迎指教😘
后面有更好的想法再补充