写给 OpenStacker 的 ZStack 指南(一)

前言

相比 OpenStack 而言,ZStack 全异步、追求高稳定性和安全的代码是相对难读的,所以本文希望能通过一些简单的例子和与 OpenStack 的一些对比,将 ZStack 的特点、代码的原理尽量描述出来,降低 ZStack 的入门门槛。欢迎更多 OpenStacker 参与 ZStack 或从 ZStack 的代码中汲取经验。

从执行一个 API 说起

对一个业内人士,观察在页面上一个指令如何逐步被执行,无疑是最直观深入的了解方式。

第一步 前端

打开 Mevoco/ZStack 的界面,可以发现基本设计思路与其他 IaaS 或 OpenStack 是基本类似的,然而打开开发者面板就会发现大有不同。

OpenStack 的面板一般通过 HTTP 到 Web 后端,可能是一个像 Horizon 的中间件,也可能是直接把请求发到后面的具体服务的 API 服务,例如 nova-api 或 neutron-server。当然中间可能还会有负载均衡器或高可用之类的设施。

image_1b6dgt82i1rl0md5j8t8va1v4m9.png-305.3kB

图1 在OpenStack的Horizon面板上创建虚拟机出发的Post请求

而在 ZStack 的面板中开发者面板是很干净的,打开之后无论什么操作是不会触发 HTTP 请求的,数据和请求都在 WebSocket 传递,比如我们在面板上创建一个虚拟机,可以看到通过 WebSocket 发送一个 frame,一个 org.zstack.header.vm.APICreateVmInstanceMsg 的消息,后面跟着的是创建的参数。

image_1b6dhje891gd7rj61e551r9hv3vt.png-209.3kB
图2 ZStack面板上创建虚拟机发出的WebSocket帧

第二步 Web 后端

请求送到送到后端就需要我们登录到这个服务器上看了,我的实验环境是一个 All in One 的环境,登陆到服务器查询 5000 端口的监听情况,可以看到是一个 Python 程序在监听这个端口。

image_1b6dja8kukgq66ph0fs8m14hl1n.png-56.9kB
图3 通过netstat查询这个端口后找到了这个进程

那么问题是这个程序怎么找到呢,现在我们不想去查部署程序是怎么部署的,然而直接在自己的 python 里直接 import 却报错提示没有这个库。

由于 Python 程序往往有比较重的依赖,因此 Python 部署是个运维中老生常谈的问题,想必这里是用了类似虚拟环境(virtualenv)之类的技术,那么问题就是那个虚拟环境在哪里。

我们知道 zstack 还有一个程序叫 zstack-cli,是 ZStack 的命令行程序,不妨查一下这个。切到这个目录后,上面果然还有几个目录,推测分别是 5 个 zstack 组件所使用的虚拟环境。

image_1b6djocjqbm218po1lob3c38mo2h.png-63.5kB
图4 成功找到目录路径

顺利找到 zstack-dashboard 目录后可以发现里边实际上没有多少 Python 代码,先打开 web.py,发现里边是一个小 Flask SocketIO App。

Flask SocketIO 用于实现 Web Socket 服务端,用 @socketio.on() 表示路由,使用的是call,所以可以直接看处理callhandle_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 的控制命令可以看到消息在这里的流转情况,先整体看一下。

image_1b6du12kr1quv1vbtb1e237c253b.png-183.7kB
图6 RabbitMQ概况

有两个 Exchange,从命名来看,一个是用于广播消息(BROADCAST),一个用于相互发送消息(P2P),另一个 NO_ROUTE 是帮助调试用的,可以不多理会。不同于 OpenStack,这里一共只两个连接,通过查询端口和进程得知,分别是 zstack-dashboard 和 zstack 进程。

image_1b6dun16s14a4bs8tbr1vlnqn59.png-115.9kB

图7 继续查找进程

刚才我们提到来自界面的消息被用 zstack.message.api.portal 的 routing key 发到 p2p exchange,看下它的消费者,由于 RabbitMQ CLI 看不到消费者情况,我们需要登录到 RabbitMQ 的 Web 界面:

image_1b6e0r3l61piu1n3p17a97lt1o3e9.png-96.6kB
图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 的逻辑里如何实现高性能的工作流系统、自动安全的级联删除、为代码质量作出重要贡献的模拟器集成测试等内容。

One thought on “写给 OpenStacker 的 ZStack 指南(一)

Leave a Reply

Your email address will not be published. Required fields are marked *