【OpenStack源碼分析之三】Nova-Compute啓動流程分析

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

發佈了31 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章