Nova架構概覽
Nova是OpenStack社區最核心的項目,也是社區誕生之時就一直存在的項目,它主要提供計算資源的服務,這個計算資源包含了虛機以及配套的存儲,網絡等資源。我比較喜歡把OpenStack和Linux做類比,我們知道進程(Task)是處於執行期的程序以及相關資源的總稱,如果把虛機類比爲進程,Nova就類似於Linux中的進程管理和調度模塊。所以Nova會和很多其他的組件交互,不僅包括OpenStack自身的Neutron,Glance, Cinder等組件,還有不同的Hypervisor 包括KVM, Xen等。
Nova的組件構成
Nova組件有以下六部分組成:
1) API服務器 API Server(Nova-api)
2) 計算工作者Compute Workers(Nova-compute)
3) 網絡控制器Network Controller(Nova-network)
4) 卷工作者Volume Worker(Nova-volume)
5) 調度器Schedule(Nova-schedule)
6) 消息隊列Message Queue(rabbitmq server)
上圖是Nova的軟件架構圖,Nova中的各個組件(除了消息隊列組件以外)都是有Python代碼編寫的守護進程,由上圖可以看出每個進程之間通過隊列(Queue)和數據庫(Nova database)來交換信息。
下面對Nova的組件進行介紹。
1) API服務器 API Server(Nova-api)
Nova-API對外提供一個與雲基礎設施交互的接口,也是外部可用於管理基礎設施的唯一組件。它負責發起相應的類似運行新虛擬機實例這樣的資源調度活動。
在實現層面上,nova-api是python實現的WSGI應用。(WSGI即Web服務器網關接口是Python應用程序或框架和Web服務器之間的一種接口,已經被廣泛接受,它已基本達成可移植性方面的目標)
2) 計算工作者Compute Workers(Nova-compute)
Nova-compute處理管理實例生命週期,負責對虛擬機實例進行創建、終止、遷移、Resize的操作。
工作原理:隊列中接收請求→執行→更新數據庫狀態
3) 網絡控制器Network Controller(Nova-network)
Nova-network負責處理主機的網絡配置,其中包括:IP地址分配,配置vlan,實現安全組,配置計算節點網絡等任務。
工作原理:隊列中接收網絡任務→控制虛擬機的網絡(創建橋接網絡、改變iptables規則)
4) 卷工作者Volume Worker(Nova-volume)
Nova-volume提供卷管理,爲虛擬機實例提供額外的volume訪問
用來管理基於邏輯卷管理的實例卷。一個實例的重要數據總是要寫在捲上,這樣確保能在以後訪問。
5) 調度器Schedule(Nova-schedule)
Nova-Scheduler負責爲虛擬機實例指定運行的物理服務器,主要負責調度資源,有多種調度方法供選擇
通過適當的調度算法從可用資源池獲得一個計算服務。
6) 消息隊列Message Queue(rabbitmq server)
Openstack節點之間通過消息隊列使用AMQP(高級消息隊列協議)完成通信(異步通信)。
Rabbitmq是對這個協議的一個實現,默認使用kombu消息框架,該部分本文不進行詳細展開,將在另外一篇文章中進行講述。
Nova Compute Service啓動流程
Nova的服務類型分爲兩種,WsgiService和RpcService,每一種服務類型都會根據nova.conf的配置啓動一個或多個進程。這其中WsgiService主要是用於組件之間的Restful接口交互,而組件內部的不同模塊採取RpcService交互模式。
Nova Compute 啓動流程分析
這裏先以Nova Compute進程的啓動過程爲例,在/nava/bin目錄下爲所有的啓動腳本入口,對源代碼進行走讀分析。
OpenStack軟件包管理
軟件包管理是每個OpenStack項目的基礎,其目的是用來將項目代碼打包成源碼包或者二進制包進行分發。一個項目的代碼可能會被打包放到PyPI上,這樣你可以通過 pip 命令安裝這個包;也可能會被打包放到項目的軟件倉庫裏,這樣你可以通過 apt-get install 或者 yum install 來安裝這個軟件包。
OpenStack也是使用setuptools工具來進行打包,不過爲了滿足OpenStack項目的需求,引入了一個輔助工具 pbr (Python Build Reasonableness)來配合setuptools完成打包工作。pbr是一個setuptools的擴展工具,被開發出來的主要目的是爲了方便使用setuptools,其項目文檔地址也在OpenStack官網內: http://docs.openstack.org/developer/pbr/ 。
先說一下pbr如何使用:
import setuptools
setuptools.setup(setup_requires=['pbr'], pbr=True)
按照上面的方式就可以配置setuptools工具使用pbr來協助完成打包工作。這裏的 setup_requires 參數意思是setup函數在執行之前需要依賴的包的列表。這裏的依賴的包的功能可以理解爲生成setup的實際參數。你可以看到,當使用pbr的時候,setup函數只有兩個參數,然而實際上 setuptools.setup 函數實際上是 disutils.core.setup 函數,會接收任何參數,這些參數可以通過在調用時指定,也可以通過所依賴的擴展來生成(比如pbr)
setup.cfg
由於OpenStack項目都使用了setuptools和pbr來執行打包工作,因此項目的元數據都放在 setup.cfg 文件中。我們以 Compute項目的setup.cfg文件爲例來說明這個文件裏一般會包含什麼內容:
[metadata]
name = nova
summary = Cloud computing fabric controller
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://docs.openstack.org/developer/nova/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[global]# 全局段
setup-hooks =
pbr.hooks.setup_hook
[files] # 文件段
packages =
nova
[entry_points] # 指定入口點
oslo.config.opts =
nova.conf = nova.conf.opts:list_opts
oslo.config.opts.defaults =
nova.conf = nova.common.config:set_middleware_defaults
oslo.policy.enforcer =
nova = nova.policy:get_enforcer
oslo.policy.policies =
# The sample policies will be ordered by entry point and then by list
# returned from that entry point. If more control is desired split out each
# list_rules method into a separate entry point rather than using the
# aggregate method.
nova = nova.policies:list_rules
nova.compute.monitors.cpu =
virt_driver = nova.compute.monitors.cpu.virt_driver:Monitor
nova.image.download.modules =
file = nova.image.download.file
console_scripts = # 指定要生成的可執行文件
nova-compute = nova.cmd.compute:main
wsgi_scripts =
nova-placement-api = nova.api.openstack.placement.wsgi:init_application
[build_sphinx] # 文檔build相關信息
all_files = 1
build-dir = doc/build
source-dir = doc/source
[build_apiguide] # 文檔build相關信息
all_files = 1
build-dir = api-guide/build
source-dir = api-guide/source
[egg_info] # 指定egg信息
tag_build =
tag_date = 0
tag_svn_revision = 0
[compile_catalog]
directory = nova/locale
domain = nova nova-log-critical nova-log-error nova-log-info nova-log-warning
[update_catalog]
domain = nova
output_dir = nova/locale
input_file = nova/locale/nova.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = nova/locale/nova.pot
[wheel]
universal = 1
[extras]
osprofiler =
osprofiler>=1.4.0 # Apache-2.0
[pbr]
warnerrors = true
(上面有些未註釋的部分我目前還不太清楚,後續補充,可以先參考 PEP301 )
這裏說說一下 classifier 這個參數。這個參數是用來指定一個軟件包的分類、許可證、允許運行的操作系統、允許運行的Python的版本的信息。
entry_points是一個字典,從entry point組名映射到一個表示entry point的字符串或字符串列表。Entry points是用來支持動態發現服務和插件的,也用來支持自動生成腳本。
requirements.txt
這個文件指定了一個項目依賴的包有哪些,並且支出了依賴的包的版本需求
軟件包歸檔格式
Python的軟件包一開始是沒有官方的標準分發格式的。比如Java有jar包或者war包作爲分發格式,Python則什麼都沒有。後來不同的工具都開始引入一些比較通用的歸檔格式。比如,setuptools引入了Egg格式。但是,這些都不是官方支持的,存在元數據和包結構彼此不兼容的問題。因此,爲了解決這個問題, PEP 427 定義了新的分發包標準,名爲 Wheel 。目前pip和setuptools工具都支持Wheel格式。這裏我們簡單總結一下常用的分發格式:
- tar.gz 格式:這個就是標準壓縮格式,裏面包含了項目元數據和代碼,可以使用 python setup.py sdist 命令生成。
- .egg 格式:這個本質上也是一個壓縮文件,只是擴展名換了,裏面也包含了項目元數據以及源代碼。這個格式由setuptools項目引入。可以通過命令 python setup.py bdist_egg 命令生成。
- .whl 格式:這個是Wheel包,也是一個壓縮文件,只是擴展名換了,裏面也包含了項目元數據和代碼,還支持免安裝直接運行。whl分發包內的元數據和egg包是有些不同的。這個格式是由PEP 427引入的。可以通過命令 python setup.py bdist_wheel 生成。
Nova Compute啓動入口
瞭解了OPS的打包規則可以知道Nova Compute的程序入口就是在nova/cmd/compute.py,先列舉源碼:
"""Starter script for Nova Compute."""
CONF = nova.conf.CONF
LOG = logging.getLogger('nova.compute')
def main():
config.parse_args(sys.argv)
logging.setup(CONF, 'nova')
priv_context.init(root_helper=shlex.split(utils.get_root_helper()))
utils.monkey_patch()
objects.register_all()
# Ensure os-vif objects are registered and plugins loaded
os_vif.initialize()
gmr.TextGuruMeditation.setup_autorun(version)
cmd_common.block_db_access('nova-compute')
objects_base.NovaObject.indirection_api = conductor_rpcapi.ConductorAPI()
server = service.Service.create(binary='nova-compute',
topic=CONF.compute_topic)
service.serve(server)
service.wait()
在這裏首先會調用config.parse_args(sys.argv)函數來做一些初始化的工作,包括RpcServer的傳輸層Driver的指定等工作。
接下來調用Create()函數創建RPC Service,並且設置Topic爲CONF.compute_topic,在【OpenStack源碼分析之二】RabbitMQ分析中有詳細講述RPC的使用。Create()函數調用的實例化對象會設置一個ComputeManager來負責處理所有的Rpc請求,具體接口請閱讀源碼Nova/compute/manager/ComputeManager類。
後面就是Serve函數,它會分配一個協程(關於協程的介紹請見對Python協程的理解)來調用Service的Start()函數,接下來我們分析一下RpcService的Start()函數,源碼如下:
def start(self):
verstr = version.version_string_with_package()
LOG.info(_LI('Starting %(topic)s node (version %(version)s)'),
{'topic': self.topic, 'version': verstr})
self.basic_config_check()
self.manager.init_host()
self.model_disconnected = False
ctxt = context.get_admin_context()
self.service_ref = objects.Service.get_by_host_and_binary(
ctxt, self.host, self.binary)
if self.service_ref:
_update_service_ref(self.service_ref)
else:
try:
self.service_ref = _create_service_ref(self, ctxt)
except (exception.ServiceTopicExists,
exception.ServiceBinaryExists):
# NOTE(danms): If we race to create a record with a sibling
# worker, don't fail here.
self.service_ref = objects.Service.get_by_host_and_binary(
ctxt, self.host, self.binary)
self.manager.pre_start_hook()
if self.backdoor_port is not None:
self.manager.backdoor_port = self.backdoor_port
LOG.debug("Creating RPC server for service %s", self.topic)
target = messaging.Target(topic=self.topic, server=self.host)
endpoints = [
self.manager,
baserpc.BaseRPCAPI(self.manager.service_name, self.backdoor_port)
]
endpoints.extend(self.manager.additional_endpoints)
serializer = objects_base.NovaObjectSerializer()
self.rpcserver = rpc.get_server(target, endpoints, serializer)
self.rpcserver.start()
self.manager.post_start_hook()
LOG.debug("Join ServiceGroup membership for this service %s",
self.topic)
# Add service to the ServiceGroup membership group.
self.servicegroup_api.join(self.host, self.topic, self)
if self.periodic_enable:
if self.periodic_fuzzy_delay:
initial_delay = random.randint(0, self.periodic_fuzzy_delay)
else:
initial_delay = None
self.tg.add_dynamic_timer(self.periodic_tasks,
initial_delay=initial_delay,
periodic_interval_max=
self.periodic_interval_max)
這段代碼涉及到了Oslo_messaging庫,oslo.messaging的產生就不多說了,因爲RPC的調用在各個項目中都有,以前各個項目分別維護一坨類似的代碼,爲了簡化工作、方便打包等,社區就把RPC相關的功能作爲OpenStack的一個依賴庫。另一方面,也爲後續支持非AMQP協議的消息中間件(ZeroMQ)的引入打下基礎。
其實oslo.messaging庫就是把rabbitmq的python庫做了封裝,考慮到了編程友好、性能、可靠性、異常的捕獲等諸多因素。讓各個項目的開發者聚焦於業務代碼的編寫,而不用考慮消息如何發送和接收。這對於各個項目開發者來說當然是好事,但對於一套OpenStack系統的運維人員來說,封裝就意味着很多細節被隱藏,爲了能夠解決消息轉發過程中出現的問題,需要再花費時間和精力去理解oslo.messaging的業務邏輯,對於本來就錯綜複雜的OpenStack核心業務來說,無疑是雪上加霜。
這裏有幾個概念:
- target:作爲消息發送者,需要在target中指定消息要發送到的exchange, binding-key, consumer等信息(這些概念可能與target對象屬性不一樣)
- serializer:負責消息的序列化處理。就是負責把Nova中的對象轉換成可以在網絡中傳送的格式。
- TRANSPORT:處理消息發送的抽象層。根據rpc_backend的配置確定真正處理消息發送的driver。一般我們會用到這個:rabbit = oslo_messaging._drivers.impl_rabbit:RabbitDriver。對於RabbitDriver,其相關配置項都在/oslo_messaging/_drivers/impl_rabbit.py中,它內部會維護一個connection pool,管理Connection對象。
- Endpoint:Transport Driver接收到消息之後會進行分發處理,這裏會有個Dispatcher分發給相應的Endpoint處理,Endpoint就是設置成前文提到的ComputeManager。
總結
- Nova和外部模塊的交互通過Restful接口調用,內部接口間採用異步RPC調用,而且正因爲是異步,所以Eventlet庫可以配套使用;
- 在RPC模式下,多個Nova-compute節點通過組合鍵(topic=self.topic, server=self.host)形成Routing Key來進行Binding
- Transport指定了底層的傳輸層機制,當前支持RabbitMQ和ZeroMQ
參考文獻:
https://docs.openstack.org/ocata/config-reference/compute.html
http://www.infoq.com/cn/articles/OpenStack-demo-packagemanagement
http://www.openstack.cn/?p=3514