openstack zun源码分析

容器服务启动过程
项目包括三个服务,分别是zun-api,zun-,zun-,均使用来管理启动停止,相关的服务文件如 zun-api. 在/etc// 或 /usr/lib//(nova 等在这里)中 。nova 等是自动创建的,而zun的是手动创建的,指定了创建位置 。
zun-api启动过程
该文件内的指定了启动脚本所在位置,如 /usr/bin/zun-api,该脚本是个代码,启动就是调用了zun.cmd.api.main进行启动
启动之后解析命令行参数,如果设置的有配置文件,则从配置文件中读取数据
重点在.(),构建该对象时,在方法中,app.()加载了api-paste.ini文件,该文件中构建的app只有一个,在zun.api.app.(),这里调用zun.api.app.构建了wsgi应用(即pecan对象,该对象中有的实现,对应方法是),然后又使用wsgi.构建了wsgi服务,传入参数有self.app,也就是该wsgi 和构建的wsgi app关联上了
而该wsgi服务的创建是由提供的 。
paste提供的是如何调用配置文件来启动wsgi应用,以及定义执行流程,还有一些路由的功能,通过部分实现,但是zun的配置文件中没有这块,路由不是通过paste实现的,而是pecan实现的
oslo. 提供了一个框架,用于使用其他应用程序建立的模式定义新的长期运行服务 。它还包括长时间运行的应用程序可能需要使用 SSL 或 WSGI、执行定期操作、与交互等的实用程序 。
zun-启动过程
zun-的启动,本质上是rpc 的启动
zun-安装之后,创建启动文件,放置在/etc///zun-.中,其中有/usr/local/bin/zun-这个命令,查看可以看到其中执行了·pute.main()·,该函数内进行·rpc ·的创建,即构建了pute..对象 。也就是zun-启动的时候,就启动了rpc
zun创建容器全流程分析
1、 start zun-api启动WSGI ,对接应用主要为zun/api//v1/.py(容器相关代码)
2、以创建容器流程做全过程分析
zun 的其他操作比如 start、stop、kill 容器等实现原理也类似
zun-api详细流程分析
容器服务入口为 zun-api,主要代码实现在 zun/api//v1/.py 以及 zun//api.py
创建容器代码分析
请求体样例
{"name": "aaa","image": "cirros","image_driver": "docker","run": true,"auto_heal": false,"mounts": [],"security_groups": ["default"],"interactive": true,"hints": {},"nets": []}
路由走向:
zun/api/root.py() –> zun/api//v1/.py() – > zun/api//v1/.py(.post(1.)–>
()方法是创建容器的方法,核心处理代码如下:
...# 检查 policy,验证用户是否具有创建 container 权限的 API 调用policy.enforce(context, "container:create", action="container:create")...# 检查安全组是否存在,根据传递的名称返回安全组的 ID 。security_group_id = self._check_security_group(context, {'name': sg})...# 检查 quota 配额 。self._check_container_quotas(context, container_dict)...# 检查网络配置,比如 port 是否存在、network id 是否合法,最后构建内部的 network 对象模型字典 。注意,这一步只检查并没有创建 port 。requested_networks = utils.build_requested_networks(context, nets)...# 根据传递的参数,构造 container 对象模型 。new_container = objects.Container(context, **container_dict)...# 检查 volume 配置,如果传递的是 volume id,则检查该 volume 是否存在,如果没有传递 volume id 只指定了 size,则调用 Cinder API 创建新的 volume 。self._build_requested_volumes(context, new_container, mounts)...# 调用zun/compute/api.py中对应的方法创建容器compute_api.container_create(context, new_container, **kwargs)
经过以上处理之后流程进入zun//api.py内的API.,在这里执行以下步骤:
# 使用 FilterScheduler 调度 container,返回宿主机的 host 对象 。这个和 nova-scheduler 非常类似,只是 Zun 集成到 zun-api 中了 。目前支持的 filters 如 CPUFilter、RamFilter、LabelFilter、ComputeFilter、RuntimeFilter 等 。host_state = self._schedule_container(context, new_container, extra_spec)...# image validation: 检查镜像是否存在,这里会远程调用 zun-compute 的 image_search 方法,其实就是调用 docker search 。这里主要为了实现快速校验,避免到了 compute 节点才发现 image 不合法 。if CONF.api.enable_image_validation:try:images = self.rpcapi.image_search(context, new_container.image,new_container.image_driver, True, new_container.registry,host_state['host'])...# record action: 和 Nova 的 record action 一样,记录 container 的操作日志 。self._record_action_start(context, new_container, container_actions.CREATE)...# 调用zun/compute/rpcapi.py的API.container_createself.rpcapi.container_create
注意:方法只支持从搜索,搜不到不要紧,后续会继续执行 。但是在zun-中会看到错误如下:ERROR pute. [None req--a6ef-4d51-b2ea- admin admin]whileimage: Imageis notin : : d: Imageis notin :
接下来进入 zun//.py(API.) 。这个API中的函数即为zun服务提供给RPC调用的接口,其他服务在调用前需要先这个模块 。
def container_create(self, context, host, container, limits,requested_networks, requested_volumes, run,pci_requests):self._cast(host, 'container_create', limits=limits,requested_networks=requested_networks,requested_volumes=requested_volumes,container=container,run=run,pci_requests=pci_requests)
self._cast: 通过rpc远程异步调用 zun- 的 ()方法,zun-api 任务结束 。
pute..API只是暴露给其他服务的RPC调用接口,服务的RPC 在接收到RPC请求后,真正完成任务的是pute.模块 。从pute..API到pute..的过程就是RPC调用的过程 。
进入 zun//.py(API.) --> zun//.py ,这个文件内构建的rpc的客户端class API和服务端class
从zun//rpc.py(获取rpc的,构建过程由模块提供),其中的参数由本文件内的init函数执行初始化,而init函数的执行通过mon..()调用,被mon..调用,又被zun.cmd.api.main调用,即在启动zun-api构建WSGI服务时,就已经进行了配置项的初始化 。因此在rpc.py文件内找不到init的调用之处 。
zun-详细流程分析
在zun//.py中走到self._cast(host, ''时,注意这里的,会到zun//.py中寻找对应的方法 。因该文件中只有类,后续会直接说该文件中的方法名,不再说类名 。
def container_create(self, context, limits, requested_networks,requested_volumes, container, run, pci_requests=None):@utils.synchronized(container.uuid)def do_container_create():# 等待 volume 创建完成,状态变为 avaiable 。# -->zun.container.docker.driver.DockerDriver.is_volume_available()# -->zun.volume.driver.Cinder.is_volume_avalable() self._wait_for_volumes_available(context, requested_volumes, container)# 这一步的目的是将cinder api创建的卷映射到宿主机上,并挂载到对应目录上self._attach_volumes(context, container, requested_volumes)# 如果使用本地盘,检查本地的 quota 配额 。self._check_support_disk_quota(context, container)# 创建容器created_container = self._do_container_create( context, container, requested_networks, requested_volumes, pci_requests, limits)if run:# 创建容器后如设置启动,调用 Docker客户端 启动容器self._do_container_start(context, created_container)utils.spawn_n(do_container_create)
重点过程分析 self.
首先说明一下,容器挂载卷的全流程如下所示:
以上1-2-3就在self.(, , ![在这里插入图片描述]() mes)这段逻辑中完成,因为可能是多卷挂载,因此还会进入 进行处理,这里是针对一个卷的处理过程,细节如下:
注:在上指定的挂载点()是容器内的路径,宿主机的挂载点在tree会自动创建(路径在zun.conf中的参数配置的目录下mnt内),本例中在`/var/lib/zun/mnt/zun..uuid目录下,这里的uuid对应的是zun库表的uuid字段,不是中的id,中的id对应的是zun库表的 。
简单说,1-2的过程与容器无关,因此使用的是id,与容器映射的目录肯定就是容器相关,使用的就是容器的 uuid 。
这两个字段都在zun的表中
以上的挂载实现的是卷挂载到容器宿主机的过程
self. --> _base,到这里开始执行具体操作,本质上就是组织创建容器需要的一系列参数,最后向提供的接口发起创建容器的请求,完成容器的创建 。简要内容如下:
# 调用 Docker 拉取或者加载镜像 。self.driver.pull_image,pull or load image# 具体执行流程:从zun/compute/manager.py(_do_container_create_base)-->zun/container/docker/driver.py(DockerDriver,配置文件配置了container_driver指向这里) -->zun/image/docker/driver.py(DockerDriver.pull_image)# 镜像拉取完成,开始创建容器,调用 Docker 创建容器 。create container: 从zun/compute/manager.py(_do_container_create_base)-->zun/container/docker/driver.py(DockerDriver.create)其中涉及到创建 docker network、创建 neutron port,最后再创建容器,向docker api发起请求创建
调用拉取镜像、创建容器、启动容器的代码位于zun///.py,该模块基本就是对社区SDK for的封装
创建容器过程中的网络相关流程
1、zun.api..v1... 创建容器接口
2、mon.utils.orks 通过调用客户端构建请求的网络 。请求参数中如果有网络相关的参数(其实就是网络的uuid),就使用 。如果没有调用可用,如果还没有就抛出异常 。返回可用的 数据供后续使用
3、pute...() --> () --> _base(拉取镜像开始调用驱动构建容器)
4、zun.....(),这个方法在调用sdk的()方法之前,还做了很多工作,其中包括网络相关配置,具体如下:
其实 kuryr 只干了一件事,那就是把 Zun 申请的 port(中的port) 绑定到容器中 。即 –kuryr–,kuryr是桥梁
查询容器 查询容器列表
访问url:
zun-api:zun/api//v1/.py
路由走向:
api/root.py()
–> api//v1/.py()
– > api//v1/.py(.)
– >)
做了一些验证,还有请求参数验证是否符合要求,默认情况下是{},最终进入..list(在模型中定义方法),指向到zun.db.api 的数据库操作中,在这里首先获取DB api对象,配置从配置项中获取,同时后端映射采用的zun.db..api,最终是从数据库获取数据返回给
注:此过程不经过zun-
查询单个容器
访问url:
zun-api: zun/api//v1/.py
路由走向:
api/root.py()
–>api//v1/.py()
–>api//v1/.py(.)
核心逻辑:
# 获取该容器的基本信息,读取zun数据库得到container = utils.get_container(container_ident)utils.get_container--> zun.common.utils.get_container-->zun.api.utils.get_resource-->zun.objects.container.get_by_uuid or get_by_name如果有container.host,最终会调用rpc,运行到zun.compute.manager.Manager.container_show--> zun.container.docker.driver.DockerDriver.show -->实际执行docker inspect命令得到数据 -->数据填充到container中(self._populate_container)返回
删除容器
删除一个容器
:
body [“-3e7b-428d-94f1-”]

openstack zun源码分析

文章插图
zun-api: zun/api//v1/.py
路由走向:
api/root.py()
–> api//v1/.py()
–> api//v1/.py(.)
如果.host存在,即主机节点可以连上,调用pute.api.,通过rpc异步调用pute... -->。否则调用zun.... 。仅仅干了一件事,就是从数据库中删除这个容器 。
...# 调用 zun/container/driver/docker.py 中的delete方法,清理容器的网络,然后向docker服务发起实质性删除容器的请求self.driver.delete(context, container, force)...# 卸载卷,详见下方分析self._detach_volumes(context, container, reraise=reraise)...# 摧毁容器在数据库中的内容container.destroy(context)
卸载卷( )
卸载卷( ):pute...
zun源码中调用的只有在删除和 时才会调用 。
核心处理逻辑如下:
zun....
def detach(self, context, volmap):self._unmount_device(volmap)# 这一步执行是linux命令 umount,卸载卷cinder = cinder_workflow.CinderWorkflow(context)cinder.detach_volume(context, volmap)
1、begin_detaching: --> zun.volume.cinder_api.py(begin_detaching 136行)--> cinderclient.v3.volumes.py(VolumeManager) --> cinderclient.v2.volumes.py(VolumeManager.begin_detaching,os-begin_detaching 具体发起post请求,url是 /volumes/volume-id/action)这一步目的是将volume的状态改为detaching即分离中Update volume status to detaching 。2、如果volume mapping表中还有该volume的信息,要执行断开连接的操作,通过os_brick包完成该操作3、执行分离卷zun.volume.cinder_api.py(detach) --> cinderclient.v2.volumes.py(VolumeManager.detach)执行 os-detach,cinderclient发送请求 。/volumes/volume-id/action这一步目的从服务器分离卷 。卷状态必须为in-use. 。4、如果要删除volume,还要执行删除操作并等待删除完成,delete_volumecompute.manager._detach_volumes --> container.docker.driver.delete_volume --> volume.driver.delete --> volume.cinder_workerflow.delete_volume --> cinder_api.delete_volume --> cinderclient.v2.volumes.delete --> cinderclient.base._delete至此发起删除请求/volumes/volume-id?cascade=True给cinder服务
删除卷的前提条件:
must be , in-use, error, , , , and must not be , , -,to a group, haveor befromafter.
从上可以看出,与毫无关系,整个过程就是zun源码执行,而后通过对卷操作(类似卸载u盘的操作,先执行卸载,后拔出,与u盘同理,如果卷被使用着就卸载不掉)
从原生使用的地方,即删除时调用,在之前,先进行了的删除操作 。但是只要卷挂载的目录没有被正在使用,就能够执行,可以手动执行/dev/sdc 进行测试 。
通过实现远程容器访问
虚拟机可以通过 VNC 远程登录,物理服务器可以通过 SOL(IPMIOver LAN)实现远程访问,容器则可以通过接口实现远程交互访问 。
原生支持连接,参考 to avia a,地址为//{id}//ws,不过只能在计算节点访问,那如何通过 API 访问呢,和 Nova、 实现完全一样,也是通过 proxy 代理转发实现的,负责的转发的进程为 zun- 。
当调用 zun- 的 () (代码在zun//.py中)方法时,zun- 会把的以及保存到数据库中. 。zun- 则可读取的作为目标端进行转发 。
相应代码在 zun\\.py中的t()
的连接是通过包-处理的 。
整个流程:浏览器(ws://192.168.221.129:6784/?token=-f887-4ac8-ba11-&uuid=-6bf6-42ab-9811-),访问该地址,到达zun-服务,该服务为每一个新的客户端连接创建 对象,最后通过进行代理转发到对象中设置的目标地址,即数据库中的字段对应的地址
更新容器
目前只支持更新cpu、name、(看页面似乎disk也行,但是代码的patch方法中没有disk)
路由走向:
api/root.py()
–>api//v1/.py()
– >api//v1/.py(.patch)
源码中可看到,如果更新了cpu或者内存,会先检查配额,而后向·zun-·发起请求,进行相应的更新
对存在的容器进行重命名方法是(POST)api//v1/.py(.) ,在zun-api完成操作
原生的按钮不知为何无法点击?存疑
容器配额quota 创建容器时的配额检查
创建容器时,会设置cpu、、如果用户没有输入,默认值在 zun/conf/.py中 。即容器驱动中设置容器的一些默认参数 。disk并不在其中
另外,创建容器时会检查quota,具体代码逻辑在 zun/api//v1/.py中的tas方法,检查项如下所示:
deltas = {'containers': 0 if update_container else 1,'cpu': container_delta_dict.get('cpu', 0),'memory': container_delta_dict.get('memory', 0),'disk': container_delta_dict.get('disk', 0)}# container_delta_dict 是创建容器时提交的容器参数
项目配额
zun-ui没有提供容器配额相关页面,因此后续操作通过命令行进行
初始配额设置位置zun/conf/quota.py
查询默认配额
查询默认配额GET://v1//{}/
这个接口获取的是配置文件(zun/conf/quota.py)中设置的数据
获取项目配额
命令:zun quota-get
接口:GET //v1//{}
路由落脚点在:zun/api//v1/.py(.get)
代码执行过程:
.get
–>.,通过对象
–zun//quota.py(,.())。创建时
–>(继承自)
–>(方法)
–> zun/conf/quota.py(该代码内全是设置的默认值,上边执行的命令在数据库没有数据时,就从此处获取默认配置)
具体执行到conf/quota.py中获取参数的代码在zun//quota.py(.
for resource in resources.values():quotas[resource.name] = default_quotas.get(resource.name, resource.default)
这里的.就是获取默认值,是对象,继承自,有方法,但是该方法有@装饰器,因此可以用属性的调用方法调用
zun.conf也能配置这个参数
quota-defaultsPrint a default quotas for a projectquota-deleteDelete quotas for a projectquota-getPrint a quotas for a project with usages (optional)quota-updatePrint an updated quotas for a projectquota-class-getPrint a quotas for a quota classquota-class-updatePrint an updated quotas for a quota class
更多信息参考 -.html
增加和修改项目配额
增加和修改项目配额接口PUT://v1//{}
请求体:
{"disk": 200,"cpu": 30,"containers": 80,"memory": 102400}
这里的一个参数对应表中的一个字段,即字段内记录的是以上四个选项 。另外这里使用发起请求时,不是json格式,-type需要设置成/x-www-form-
代码执行完之后数据会写入表中,表中原本不需要有该条记录 。每次执行put请求,都不是更新,而是新增记录,取数据取的最新的记录进行返回 。数据库的字段发现未生效 。
删除配额
接口:://v1//{}
返回null,会将数据库中这个相关的所有记录全部删除
镜像的增删改查
官方没有image api只有 api,以下内容从源码中分析得到 。如有错漏,请告知
zun-ui提供的镜像功能(非镜像仓库)
选择拉取镜像
提供的功能点:
1、pull image,从配置的镜像仓库拉取指定镜像(镜像名)到指定主机上
openstack zun源码分析

文章插图
2、删除镜像(注意不要手动在服务器删除zun拉取的镜像,否则zun-ui会删不掉该镜像在数据库的数据,执行不到这一步还看不到错误输出 。实际应该在zun///.py -->第一行就有问题了,因为查不到这个镜像了)
3、过滤搜索镜像,即页面上的搜索框(注意这里走的接口仍然是获取所有镜像,前端完成的过滤,与通常理解的搜索逻辑不同)
4、镜像列表
本质上这里执行的是 pull ,从镜像仓库拉取镜像到本地
拉取完成后,在对应的host上通过 命令可以看到拉取成功的镜像 。
API(无官方文档):
通过命令行创建的镜像zun-ui看不到,或通过zun-ui pull的镜像,命令行也看不到,但是数据库是有数据的,造成该现象的原因可能是命令和页面看的不是一个项目所导致
容器
通过原生zun-ui,是没有提供类似功能的入口的,但是源码中提供有接口完成此功能 。
zun-api中接口
def commit(self, container_ident, **kwargs):"""Create a new image from a container's changes.:param container_ident: UUID or Name of a container."""container = utils.get_container(container_ident)check_policy_on_container(container.as_dict(), "container:commit")utils.validate_container_state(container, 'commit')LOG.debug('Calling compute.container_commit %s ', container.uuid)context = pecan.request.contextcompute_api = pecan.request.compute_apipecan.response.status = 202return compute_api.container_commit(context, container,kwargs.get('repository', None),kwargs.get('tag', None))
核心只在最后调用完成此功能
zun-中
def container_commit(self, context, container, repository, tag=None):# NOTE(miaohb): Glance is the only driver that support image# uploading in the current version, so we have hard-coded here.# https://bugs.launchpad.net/zun/+bug/1697342# 从上提到的glance是支持镜像上传的唯一驱动 。docker是不支持的 。创建glance镜像,此处并未上传镜像,尚未使用docker commit从容器创建一个新的镜像snapshot_image = self.driver.create_image(context, repository,glance.GlanceDriver())...self._do_container_commit(context, snapshot_image, container,repository, tag)
核心内容如下:
...# ensure the container is paused before doing commit# commit之前要确保容器状态是暂停container = self.driver.pause(context, container)...# 执行docker commit操作# 流程经过 zun/container/docker/driver.py(commit)--> docker/api/container.py(commit) 实质上向docker服务提供的api发起commit操作container_image_id = self.driver.commit(context, container, repository, tag)# 获取image,核心执行的是docker/api/image.py(get_image)# Get a tarball of an image. Similar to the ``docker save`` command. 可以看到执行的是docker save操作,返回镜像数据container_image = self.driver.get_image(repository + ':' + tag)...#如果之前操作了暂停容器,此时要将容器恢复container = self.driver.unpause(context, container)...# 将镜像上传到glance,当前版本不支持上传到dockerhub等仓库self._do_container_image_upload(context, snapshot_image,container_image_id,container_image, tag)
api见a new image from a
an image 接口
浏览器通过非方式访问容器的可行性研究exec
exec命令的流程(该命令在 api中相当于执行俩api,分别是 an exec和 start an exec )
该命令的实质是让容器能像宿主机中一样执行命令 。只是可以通过指定i和t参数,来得到一个tty,方便操作 。最终实质是要执行的命令 。
因此要执行的命令是不可缺少的 。这也决定了通过该命令开一个终端是不现实的,因为没有命令不会给你显示伪终端 。而要执行命令,如bash,sh等,又不确定每个容器内都存在,就导致不能使用该方案 。
在对应接口中,api文档中给了四个参数分别是 ,,run,
run在代码中默认值是true,即exec 之后还要执行exec run,实际就是 api中的start,(代码中默认值false)代表了 api中的两个参数和Tty(zun.....中进行了赋值,二者值与保持一致)
执行流程:
zun.api..v1...
–>pute...
–>zun.....
–>.api...,在这里拼装了请求体,拼装了url,最后发起post请求,self._url和self.在–>.api..中实现,post请求调用的是发起的
如果run是true就要start
–>zun.....
–>.api... 这一步对接的就是 api的start exec接口(这里将、tty、参数全部写死为false了)
但是start传递的参数比 api要多,而且 api没有返回值,这里却有返回值可以接收
start之后,还要执行获取容器的简单信息,对应的也是 api中的 an exec
ssh方式
在使用平台时,发现该平台提供给用户的就是容器,可以在浏览器访问,执行exit也不会导致容器停止 。经过调研发现该平台是通过ssh方式访问容器的 。
但是ssh方式也不可行,ssh连接需要服务器内安装有sshd服务并开启 。每个容器是否存在该服务是不确定的,因此该方案也不行
关于ssh方式实现访问可参考xterm.js
研究该问题的初衷是因为浏览器上进入容器后,在容器内执行exit命令,会使容器直接退出,状态变为,这是因为本质上是通过的方式进入容器的,而该方式在容器内执行exit,就是会导致容器停止 。希望通过其他方案不让容器退出,但是目前看没有可行性
paste配置文件详解
# 这是一个composite段,表示这将会根据一些条件将web请求调度到不同的应用[composite:main]use = egg:Paste#urlmap# 表示我们将使用Paste egg包中urlmap来实现composite/ = home/blog = blog# 根据web请求的path的前缀进行一个到应用的映射(map)/wiki = wiki/cms = config:cms.ini# 映射到了另外一个配置文件,Paste Deploy再根据这个文件进行载入# app是一个callable object,接受的参数(environ,start_response)app需要完成的任务是响应envrion中的请求,准备好响应头和消息体,然后交给start_response处理,并返回响应消息体[app:home]use = egg:Paste#static# Paste包中的一个简单程序,它只处理静态文件# 它需要一个配置文件document_root,后面的值可以是一个变量,形式为%(var)s相应的值应该在[DEFAULT]字段指明以便Paste读取 。document_root = %(here)s/htdocs[filter-app:blog]# filter是一个callable object,其唯一参数是(app),这是WSGI的application对象,filter需要完成的工作是将application包装成另一个application(“过滤”),并返回这个包装后的application 。use = egg:Authentication#authnext = blogapp# 在正式调用blogapp之前,我会调用egg:Authentication#auth进行一个用户的验证,随后才会调用blogapp进行处理roles = adminhtpasswd = /home/me/users.htpasswd[app:blogapp]# 定义了blogapp,并指明了需要的database参数 。use = egg:BlogAppdatabase = sqlite:/home/me/blog.db[app:wiki]#call(表示使用call方法):模块的完成路径名字:应用变量的完整名字use = call:mywiki.main:application# 使用call的话,相应的函数,类,实例中必须实现call()方法 。database = sqlite:/home/me/wiki.db
# pipeline就是简化了filter-app,不然你想,如果我有十个filter,那不是要写十# 个filter-app(有next),通过pipeline,我就可以把这些filter都连起来写在一行,很方便 。但要注意的是这些filter需要有一个app作为结尾 。[pipeline:main]pipeline = cors request_id osprofiler authtoken api_v1# 定义WSGI应用,main表示只有一个应用,有多个应用的话main改为应用名字#定义application需要运行的Python code# 这种方式必须明确指定使用的protocol(此例中是paste.app_factory),value值表# 示需要import的内容 。此例中是import zun.api.app,然后检测app_factory执行 。[app:api_v1]paste.app_factory = zun.api.app:app_factory# 就是一个过滤[filter:authtoken]acl_public_routes = /, /v1paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory[filter:osprofiler]paste.filter_factory = zun.common.profiler:WsgiMiddleware.factory[filter:request_id]paste.filter_factory = oslo_middleware:RequestId.factory[filter:cors]paste.filter_factory =oslo_middleware.cors:filter_factoryoslo_config_project = zun
uwsgi部署实现
参考官方文档
创建文件 /etc/zun/zun-uwsgi.ini ,放在其他位置也是可以的
[uwsgi]http = 0.0.0.0:9517wsgi-file = /zun/api/app.wsgiplugins = python# This is running standalonemaster = true# Set die-on-term & exit-on-reload so that uwsgi shuts downexit-on-reload = truedie-on-term = true# uwsgi recommends this to prevent thundering herd on accept.thunder-lock = true# Override the default size for headers from the 4k default. (mainly for keystone token)buffer-size = 65535enable-threads = true# Set the number of threads usually with the returns of command nprocthreads = 8# Make sure the client doesn't try to re-use the connection.add-header = Connection: close# Set uid and gip to a appropriate user on your server. In many# installations ``zun`` will be correct.uid = zungid = zun
启动:uwsgi ./zun-uwsgi.ini
后台启动:uwsgi -d ./zun-uwsgi.ini
依旧是使用paste启动wsgi 应用,对比二者启动方式发现,这里仅是用uwsgi代替了 zun.cmd.api.main中的wsgi 功能,不再走zun提供的wsgi服务,而是走wsgi-file指定的文件,如zun/api/app.wsgi,其余不变,仅此而已
如果要默认使用uwsgi启动zun-api,还得修改/etc///zun-api.
测试接口说明
当无法通过完成请求时,需要使用或其他工具直接访问zun-api提供的接口
获取服务接口
通过list 命令可以看到各个组件的接口,如下所示:
如zun-api对应::9517/v1 ,然后再到各组件的api文档中查看各个请求的接口url,拼接后就是完整的请求路径 。
使用组成的url直接通过浏览器访问是会报401错的 。因为没有携带认证信息
通过以下命令获取token
/etc// 这个文件由自己决定放在哪里,内容大概如下:
export OS_PROJECT_DOMAIN_NAME=Default export OS_USER_DOMAIN_NAME=Default export OS_PROJECT_NAME=admin export OS_USERNAME=admin export OS_PASSWORD=hy@123 export OS_AUTH_URL=http://controller:5000/v3 export OS_IDENTITY_API_VERSION=3 export OS_IMAGE_API_VERSION=2
通过 token issue 可以拿到当前用户的token,下图中的id就是
然后请求上边的url时将token加入中 。参数名是 X-Auth-Token 值就是token 。这样就可以测试所有api了 。
发现的一些问题 1、服务器重启
创建有容器的服务器如果发生重启,此时挂载的硬盘会全部丢失 。发生这种问题是致命的 。容器挂载硬盘这块的实现关系如下:
卷 --> map到宿主机/dev/sdb --> mount /var/lib/zun/mnt/zun..uuid 目录 --> /var/lib/zun/mnt/zun..uuid:/data
服务器重启后卷还在,容器也在,但是中间两步丢失了,因此在服务器重启后再次重启容器时需要完成中间两步即可 。这部分处理逻辑是源码中的,因此可以复用该代码进行适当调整实现服务器重启后挂载卷功能,映射关系保持不变 。
2、容器启动失败
此处的启动失败特指之前创建好的可以正常运行的容器,因为某些原因状态变为,当再次启动该容器时由于外在原因(如服务未启动、kuryr-未启动)导致容器启动失败,源码中的处理逻辑是直接走的逻辑,在该部分代码中会执行,这里会将卷从容器卸载,并且如果卷设置的是卸载后自动删除,还会将卷直接删掉 。这种bug是不可承受的 。
当前思考的处理方式是在容器启动失败时,判断容器状态,如果状态是,并且当前正在执行的状态是,则只卸载卷,而不删除卷,再新建容器将这个卷挂到新容器上实现数据的保留 。
参考链接:
容器服务 Zun 初探与原理分析
api
zun
the api via wsgi
Pecan web 框架简介
pecan框架的使用
【openstack zun源码分析】about