您的位置:首页 > 运维架构

【OpenStack源码分析之四】WSGI与Nova API服务启动

2017-07-18 22:49 1566 查看

前言

前文已经介绍了RPC Server的启动,而Nova API启动的是WSGI服务,所以先介绍一下WSGI。

WSGI

Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。

WSGI区分为两个部份:一为“服务器”或“网关”,另一为“应用程序”或“应用框架”。在处理一个WSGI请求时,服务器会为应用程序提供环境资讯及一个回呼函数(Callback Function)。当应用程序完成处理请求后,透过前述的回呼函数,将结果回传给服务器。所谓的 WSGI 中间件同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用:从WSGI服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:

重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。

允许在一个进程中同时运行多个应用程序或应用框架。

负载均衡和远程处理,通过在网络上转发请求和响应消息。

进行内容后处理,例如应用XSLT样式表。

以前,如何选择合适的Web应用程序框架成为困扰Python初学者的一个问题,这是因为,一般而言,Web应用框架的选择将限制可用的Web服务器的选择,反之亦然。那时的Python应用程序通常是为CGI,FastCGI,mod_python中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计的。WSGI没有官方的实现, 因为WSGI更像一个协议。只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行, 反之亦然。WSGI就是Python的CGI包装,相对于Fastcgi是PHP的CGI包装。

WSGI将 web 组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。

WSGI Server/gateway

wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。文字很难解释清楚wsgi server到底是什么东西,以及做些什么事情,最直观的方式还是看wsgi server的实现代码。以python自带的wsgiref为例,wsgiref是按照wsgi规范实现的一个简单wsgi server。它的代码也不复杂。



服务器创建socket,监听端口,等待客户端连接。

当有请求来时,服务器解析客户端信息放到环境变量environ中,并调用绑定的handler来处理请求。

handler解析这个http请求,将请求信息例如method,path等放到environ中。

wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中。

wsgi handler 调用注册的wsgi app,并将environ和回调函数传给wsgi app

wsgi app 将reponse header/status/body 回传给wsgi handler

最终handler还是通过socket将response信息塞回给客户端。

WSGI Application

wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。此外这个wsgi app会return 一个iterator对象 ,这个iterator就是response body。这么空讲感觉很虚,对着下面这个简单的例子看就明白很多了。

WSGI MiddleWare

有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。

其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职

CGI

关于CGI的解释,知乎上有位hellocode兄弟在

https://www.zhihu.com/question/19998865/answer/29395327 讲的比较清楚,我摘抄如下:

CGI是比较原始的开发动态网站的方式。你可以想象一下,一个网站的动态内容肯定是程序生成的,光是静态的html页面无法达到这个效果。那么,这个程序就需要接受客户端的请求,然后进行相应处理,再返回给客户端,客户端和服务端的通信当然是通过HTTP协议。

然后我们会发现,这个程序在处理客户端请求的时候,大部分时候会进行很多重复的工作,比如说HTTP请求的解析。也就是说,你的程序需要解析HTTP请求,我的程序也需要解析。

于是为了DRY原则,Web服务器诞生了。(以下所说的都是CGI的工作模式)于是Web服务器可以解析这个HTTP请求,然后把这个请求的各种参数写进进程的环境变量,比如REQUEST_METHOD,PATH_INFO之类的。之后呢,服务器会调用相应的程序来处理这个请求,这个程序也就是我们所要写的CGI程序了。它会负责生成动态内容,然后返回给服务器,再由服务器转交给客户端。服务器和CGI程序之间通信,一般是通过进程的环境变量和管道。

这样做虽然很清晰,但缺点就是每次有请求,服务器都会fork and exec,每次都会有一个新的进程产生,开销还是比较大的。原因在与CGI程序是一个独立的程序,它是可以独立运行的(在提供HTTP请求的情况下),它可以用几乎所有语言来写,包括perl,c,lua,python等等。所以对于一个程序,服务器只能以fork and exec的方式来调用它了。

Nova API 服务启动流程

OpenStack api-paste.ini 详解

这里先摘抄 Nova模块的api-paste.ini文件如下:

############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta

[pipeline:meta]
pipeline = cors metaapp

[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory

#############
# OpenStack #
#############

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
# v21 is an exactly feature match for v2, except it has more stringent
# input validation on the wsgi surface (prevents fuzzing early on the
# API). It also provides new features via API microversions which are
# opt into for clients. Unaware clients will receive the same frozen
# v2 API feature set, but with some relaxed validation
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21

[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21

[composite:openstack_compute_api_v21_legacy_v2_compatible]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler noauth2 legacy_v2_compatible osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21

[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory

[filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory

[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory

[filter:noauth2]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory

[filter:osprofiler]
paste.filter_factory = nova.profiler:WsgiMiddleware.factory

[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory

[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory

[filter:legacy_v2_compatible]
paste.filter_factory = nova.api.openstack:LegacyV2CompatibleWrapper.factory

[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

[pipeline:oscomputeversions]
pipeline = cors faultwrap http_proxy_to_wsgi oscomputeversionapp

[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory

##########
# Shared #
##########

[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = nova

[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory


这里面涉及到几个术语,分别解释一下:

composite section:Request 进来后第一个通过的 Section,表示需要将一个 HTTP URL Request 调度到一个或者多种 Application 上。

use:是一个关键字,指定处理请求的代码,这里表示我们使用 Paste egg包中 urlmap 来实现 composite 请求分发方式。 ==> 决定请求的分发方式为 urlmap(urlmap 算是一个通用的请求分发程序)。

pipeline section : 指定的 section 有如下要求:

1.最后一个名字对应的 section 一定要是一个 app 类型。

2.非最后一个名字对应的 section 一定要是一个 filter 类型。

filter section: 是一个实现了过滤器功能的中间件(将 Application 进行进一步的封装),用于过滤 Request 和 Response。

App: 一个 app 就是一个实现主要功能的具体的 application 。所以 app 必须是 Callable Object 类型,接受的参数(environ,start_response),这是WSGI Server交给Application的符合WSGI规范的参数。

EXAMPLE: Keystone Request URL 为 http://homename:35357/v3/auth/tokens

Step1. (hostname:35357): 这一部分由 Web Server 来获取并处理的(EG.虚拟机功能)。

Step2. (/v3/auth/tokens): 根据 paste.ini 中的配置来对剩下的 URL(/v3/auth/tokens)部分进行处理。首先请求的 Port =35357 决定了会经过 [composite:admin] section 。

Step3. (/v3): composite section 会根据 /v3 这个 URL 前缀来决定将 Request 路由到哪一个 pipeline secion,这里就把请求转发给 [pipeline:api_v3] 处理,转发之前,会把 /v3 这个部分的 URL 去掉。

Step4. (/auth/tokens) : [pipeline:api_v3] 收到请求,URL_Path是 (/auth/tokens),然后开始调用各个 filter(中间件) 来处理请求。最后会把请求交给 [app:service_v3] 进行处理。

Step5. (/auth/tokens): [app:service_v3] 收到请求,URL_Path是 (/auth/tokens),最后交由的 WSGI Application:keystone.service:v3_app_factory 去处理。

服务如何部署?

目前Python有两种方式来开发和部署一个Web应用:用WSGI和不用WSGI。OpenStack的API服务都是使用WSGI的方式来部署的。在生产环境中部署WSGI,一般会考虑使用Web服务器 + 应用服务器 + 应用(框架)的方案。OpenStack官方推荐的是使用Apache + mod_wsgi的方案,不过这个要换成其他方案也很容易,你也可以选nginx + uWSGI。对于开发调试的目的,有些项目也会提供使用eventlet的单进程部署方案,比如Keystone项目的keystone-all命令。采用eventlet这种异步架构来进行应用开发也是一个比较大的话题,本文不覆盖这方面的内容。

当然,也可以不用WSGI。在Python中,如果不使用WSGI的化,一般开发者会选择一些专门的服务器和框架,比如Tornado,或者最新最潮的aiohttp。不过在OpenStack的项目中我还没见过不使用WSGI的。

参考文献:

https://www.biaodianfu.com/cgi-fastcgi-wsgi.html

http://blog.csdn.net/Jmilk/article/details/52081748

https://segmentfault.com/a/1190000003718598
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  openstack api python 源码