前言
相比 OpenStack 而言,ZStack 全异步、追求高稳定性和安全的代码是相对难读的,所以本文希望能通过一些简单的例子和与 OpenStack 的一些对比,将 ZStack 的特点、代码的原理尽量描述出来,降低 ZStack 的入门门槛。欢迎更多 OpenStacker 参与 ZStack 或从 ZStack 的代码中汲取经验。
从执行一个 API 说起
对一个业内人士,观察在页面上一个指令如何逐步被执行,无疑是最直观深入的了解方式。
第一步 前端
打开 Mevoco/ZStack 的界面,可以发现基本设计思路与其他 IaaS 或 OpenStack 是基本类似的,然而打开开发者面板就会发现大有不同。
OpenStack 的面板一般通过 HTTP 到 Web 后端,可能是一个像 Horizon 的中间件,也可能是直接把请求发到后面的具体服务的 API 服务,例如 nova-api 或 neutron-server。当然中间可能还会有负载均衡器或高可用之类的设施。
而在 ZStack 的面板中开发者面板是很干净的,打开之后无论什么操作是不会触发 HTTP 请求的,数据和请求都在 WebSocket 传递,比如我们在面板上创建一个虚拟机,可以看到通过 WebSocket 发送一个 frame,一个 org.zstack.header.vm.APICreateVmInstanceMsg
的消息,后面跟着的是创建的参数。

图2 ZStack面板上创建虚拟机发出的WebSocket帧
第二步 Web 后端
请求送到送到后端就需要我们登录到这个服务器上看了,我的实验环境是一个 All in One 的环境,登陆到服务器查询 5000 端口的监听情况,可以看到是一个 Python 程序在监听这个端口。

图3 通过netstat查询这个端口后找到了这个进程
那么问题是这个程序怎么找到呢,现在我们不想去查部署程序是怎么部署的,然而直接在自己的 python 里直接 import 却报错提示没有这个库。
由于 Python 程序往往有比较重的依赖,因此 Python 部署是个运维中老生常谈的问题,想必这里是用了类似虚拟环境(virtualenv)之类的技术,那么问题就是那个虚拟环境在哪里。
我们知道 zstack 还有一个程序叫 zstack-cli,是 ZStack 的命令行程序,不妨查一下这个。切到这个目录后,上面果然还有几个目录,推测分别是 5 个 zstack 组件所使用的虚拟环境。

图4 成功找到目录路径
顺利找到 zstack-dashboard 目录后可以发现里边实际上没有多少 Python 代码,先打开 web.py,发现里边是一个小 Flask SocketIO App。
Flask SocketIO 用于实现 Web Socket 服务端,用 @socketio.on()
表示路由,使用的是call
,所以可以直接看处理call
的 handle_call
,跳转到server.api_call
,将消息有 json 格式转换出来,然后连带一个用来发送回复的函数一起发给由CloudBus
类所生成对象的send
方法。
进入 CloudBus.send
,通过一些检查和填充之后,设置回复的队列为zstack.ui.message.$uuid
(这个 UUID 以及队列都是服务初始化时产生的)等等送给 Connection.send
通过 kombu
对 P2P Exchange 以 zstack.message.api.portal
为 routing key(这里被封装为 Service ID)将消息发了出去。
第三步 消息总线
消息总线是一个分布式系统的核心组件之一,在 ZStack 中更将这个用的技近乎艺,在后面的文章希望能更多向大家介绍,此外也可以参考张鑫撰写的ZStack 开发者指南的消息部分。
进入一个装好 ZStack 的系统,用 rabbitmq 的控制命令可以看到消息在这里的流转情况,先整体看一下。

图6 RabbitMQ概况
有两个 Exchange,从命名来看,一个是用于广播消息(BROADCAST),一个用于相互发送消息(P2P),另一个 NO_ROUTE 是帮助调试用的,可以不多理会。不同于 OpenStack,这里一共只两个连接,通过查询端口和进程得知,分别是 zstack-dashboard 和 zstack 进程。
刚才我们提到来自界面的消息被用 zstack.message.api.portal
的 routing key 发到 p2p exchange,看下它的消费者,由于 RabbitMQ CLI 看不到消费者情况,我们需要登录到 RabbitMQ 的 Web 界面:

图8 RabbitMQ Web端
在上面我们看过 57556 是 zstack 的连接,也就是说这个消息终于被 zstack 收到了。
第四步 CloudBus
在 zstack 的设计中,Cloudbus 是非常重要的一环,通过 Cloudbus 封装的 rabbitmq,在前边提供 API 消息的接收、分发,在中间负责进程内微服务的信息流转,在后边提供了用户请求的返回,可以是说 Cloudbus 是 ZStack 架构中的神经网络——感知、处理、返回。
ZStack 的管理节点不同于一个一般意义上的管理节点——一台服务器或一个虚拟机,ZStack 的进程内为服务架构将所有服务封装在了一个进程内,我们称这个进程为管理节点 Management Node。而 Management Node 启动的第一步会 bootstrap 拉起 Cloudbus,进入到了 Cloudbus 的启动函数。
读者如果感兴趣,可以通过阅读 org.zstack.core.cloudbus.CloudBusImpl2
的代码获得对消息中间件细节认识。简单地讲,如同 ZStack 架构文档 ZStack’s Scalability Secrets Part 1: Asynchronous Architecture 所描述,服务在 ZStack 中是一等公民,这些服务都实现了 org.zstack.header.Service
这个接口,当 Cloudbus 启动时,服务将通过 org.zstack.core.cloudbus.CloudBusImpl2#registerService
为服务注册 Endpoint,这个 Endpoint 最终完成消息队列的声明、绑定等工作。当获取消息时 Endpoint 还会 handle 消息,通过服务的接口方法 org.zstack.header.Service#handleMessage
将消息发到服务本身,从而实现信息在 ZStack 的高效流转,对服务更详细的描述可以阅读 ZStack 架构文章 The In-Process Microservices Architecture
。
后面,我们将开始离开 ZStack 的 Core 部分,进入更丰富多彩的 ZStack 业务服务的实现,我们会以一个例子来介绍在 ZStack 的逻辑里如何实现高性能的工作流系统、自动安全的级联删除、为代码质量作出重要贡献的模拟器集成测试等内容。
催更来了