您當前的位置:首頁 > 詩詞

如何閱讀OpenStack原始碼

作者:由 int32bit 發表于 詩詞時間:2017-09-01

1 OpenStack基礎

1。1 OpenStack元件介紹

OpenStack是一個IaaS雲計算平臺開源實現,其對標產品為AWS。最開始OpenStack只有兩個元件,分別為提供計算服務的Nova以及提供物件儲存服務的Swift,其中Nova不僅提供計算服務,還包含了網路服務、塊儲存服務、映象服務以及裸機管理服務。之後隨著專案的不斷髮展,從Nova中根據功能拆分為多個獨立的專案,如nova-volume拆分為Cinder專案提供塊儲存服務,nova-image拆分為Glance專案,提供映象儲存服務,nova-network則是neutron的前身,裸機管理也從Nova中分離出來為Ironic專案。最開始容器服務也是由Nova提供支援的,作為Nova的driver之一來實現,而後遷移到Heat,到現在已經獨立為一個單獨的專案Magnum,後來Magnum的願景調整為主要提供容器編排服務,單純的容器服務則由Zun專案接管。最開始OpenStack並沒有認證功能,從E版開始才加入認證服務Keystone。

目前OpenStack基礎服務元件如下:

Keystone:認證服務。

Glance:映象服務。

Nova:計算服務。

Cinder:塊儲存服務。

Neutorn:網路服務。

Swift:物件儲存服務。

E版之後,在這些核心服務之上,又不斷湧現新的服務,如面板服務Horizon、編排服務Heat、資料庫服務Trove、檔案共享服務Manila、大資料服務Sahara、工作流服務Mistral以及前面提到的容器編排服務Magnum等,這些服務幾乎都依賴於以上的基礎服務。比如Sahara大資料服務會先呼叫Heat模板服務,Heat又會呼叫Nova建立虛擬機器,呼叫Glance獲取映象,呼叫Cinder建立資料卷,呼叫Neutron建立網路等。

目前最新發布的版本為第15個版本,代號為Pike,

Queens

版本已經進入快速開發階段。

OpenStack服務越來越多、越來越複雜,覆蓋的技術生態越來越龐大,宛如一個龐然大物,剛接觸如此龐大的分散式系統,都或多或少感覺有點如”盲人摸象”的感覺。不過不必先過於絕望,好在OpenStack專案具有非常良好的設計,雖然OpenStack專案眾多,元件繁雜,但幾乎所有的服務骨架脈絡基本是一樣的,熟悉了其中一個專案的架構,深入讀了其中一個專案原始碼,再去看其它專案可謂輕車熟路。

本文章會以Nova專案為例,一步一步剖析原始碼結構,希望讀者閱讀完之後再去看Cinder專案會是件非常輕鬆的事。

1。2 工欲善其事必先利其器

要閱讀原始碼首先需要安裝科學的程式碼閱讀工具,圖形介面使用pycharm沒有問題,不過通常在虛擬機器中是沒有圖形介面的,首選vim,需要簡單的配置使其支援程式碼跳轉和程式碼搜尋,可以參考GitHub - int32bit/dotfiles: A set of vim, zsh, git, and tmux configuration files。如圖:

如何閱讀OpenStack原始碼

如何閱讀OpenStack原始碼

OpenStack所有專案都是基於Python開發,並且都是標準的Python專案,透過setuptools工具管理專案,負責Python模組的安裝和分發。想知道一個專案有哪些服務組成,最直接有效的辦法就是找到入口函式(main函式)在哪裡,只要是標準的基於setuptools管理的專案的所有入口函式都會在專案根目錄的setup。cfg檔案中定義,console_scripts就是所有服務元件的入口,比如nova(Mitaka版本)的setup。cfg的console_scripts如下:

[entry_points]

。。。

console_scripts

=

nova-all = nova。cmd。all:main

nova-api = nova。cmd。api:main

nova-api-metadata = nova。cmd。api_metadata:main

nova-api-os-compute = nova。cmd。api_os_compute:main

nova-cells = nova。cmd。cells:main

nova-cert = nova。cmd。cert:main

nova-compute = nova。cmd。compute:main

nova-conductor = nova。cmd。conductor:main

nova-console = nova。cmd。console:main

nova-consoleauth = nova。cmd。consoleauth:main

nova-dhcpbridge = nova。cmd。dhcpbridge:main

nova-idmapshift = nova。cmd。idmapshift:main

nova-manage = nova。cmd。manage:main

nova-network = nova。cmd。network:main

nova-novncproxy = nova。cmd。novncproxy:main

nova-rootwrap = oslo_rootwrap。cmd:main

nova-rootwrap-daemon = oslo_rootwrap。cmd:daemon

nova-scheduler = nova。cmd。scheduler:main

nova-serialproxy = nova。cmd。serialproxy:main

nova-spicehtml5proxy = nova。cmd。spicehtml5proxy:main

nova-xvpvncproxy = nova。cmd。xvpvncproxy:main

。。。

由此可知nova專案安裝後會包含21個可執行程式,其中nova-compute服務的入口函式為nova/cmd/compute。py模組的main函式:

def

main

():

config

parse_args

sys

argv

logging

setup

CONF

‘nova’

utils

monkey_patch

()

objects

register_all

()

gmr

TextGuruMeditation

setup_autorun

version

if

not

CONF

conductor

use_local

block_db_access

()

objects_base

NovaObject

indirection_api

=

\

conductor_rpcapi

ConductorAPI

()

else

LOG

warning

_LW

‘Conductor local mode is deprecated and will ’

‘be removed in a subsequent release’

))

server

=

service

Service

create

binary

=

‘nova-compute’

topic

=

CONF

compute_topic

db_allowed

=

CONF

conductor

use_local

service

serve

server

service

wait

()

其它服務依次類推。

OpenStack使用Python語言開發,而Python是動態型別語言,引數型別不容易從程式碼中看出,因此部署一個allinone的OpenStack開發測試環境非常有必要,建議使用RDO部署:Packstack quickstart,當然樂於折騰使用DevStack也是沒有問題的。

要想深入研究原始碼,最有效的方式就是一步一步跟蹤程式碼執行,因此會使用debug工具是關鍵技能之一。Python的debug工具有很多,為了簡便起見,pdb工具就夠了,你也可以嘗試ipdb、ptpdb之類的除錯工具。使用方法也非常簡單,只要在你想設定斷點的地方,嵌入以下程式碼:

import

pdb

pdb

set_trace

()

然後在命令列(不能透過systemd啟動)直接執行服務即可。

假如想跟蹤nova建立虛擬機器的過程,首先nova/api/openstack/compute/servers。py模組的create方法打上斷點,如下:

def

create

self

req

body

):

“”“Creates a new server for a given user。”“”

import

pdb

pdb

set_trace

()

# 設定斷點

context

=

req

environ

‘nova。context’

server_dict

=

body

‘server’

password

=

self

_get_server_admin_password

server_dict

name

=

common

normalize_name

server_dict

‘name’

])

if

api_version_request

is_supported

req

min_version

=

‘2。19’

):

if

‘description’

in

server_dict

# This is allowed to be None

description

=

server_dict

‘description’

else

# No default description

description

=

None

else

description

=

name

。。。

然後注意需要透過命令列直接執行,而不能透過systemd啟動:

su -c

‘nova-api’

nova

此時呼叫建立虛擬機器API,nova-api程序就會立即彈出pdb shell,此時你可以透過s或者n命令一步一步執行程式碼。

1。3 OpenStack專案通用骨骼脈絡

閱讀原始碼的首要問題就是就要對程式碼的結構瞭然於胸,

需要強調的是,OpenStack專案的目錄結構並不是根據元件嚴格劃分,而是根據功能劃分

,以Nova為例,compute目錄並不是一定在nova-compute節點上執行,而主要是和compute相關(虛擬機器操作相關)的功能實現,同樣的,scheduler目錄程式碼並不全在scheduler服務節點執行,但主要是和排程相關的程式碼。不過目錄結構並不是完全沒有規律,它遵循一定的套路。

通常一個服務的目錄都會包含

api.py、rpcapi.py、manager.py

,這三個是最最重要的模組。

api.py

: 通常是供其它元件呼叫的封裝庫。換句話說,該模組通常並不會由本模組呼叫,而是類似提供其它服務SDK。比如compute目錄的api。py,通常會由nova-api服務的controller呼叫。

rpcapi.py

:這個是RPC請求的封裝,或者說是RPC封裝的client端,該模組封裝了所有RPC請求呼叫。

manager.py

: 這個才是真正服務的功能實現,也是RPC的服務端,即處理RPC請求的入口,實現的方法和rpcapi實現的方法一一對應。

比如對一個虛擬機器執行關機操作的流程為:

API節點

nova-api接收使用者請求 -> nova-api呼叫compute/api。py

-> compute/api呼叫compute/rpcapi。py -> rpcapi。py向目標計算節點發起stop_instance()RPC請求

計算節點

收到MQ RPC訊息 -> 解析stop_instance()請求 -> 呼叫compute/manager。py的callback方法stop_instance() -> 呼叫libvirt關機虛擬機器

前面提到OpenStack專案的目錄結構是按照功能劃分的,而不是服務元件,因此並不是所有的目錄都能有對應的元件。仍以Nova為例:

cmd:這是服務的啟動指令碼,即所有服務的main函式。看服務怎麼初始化,就從這裡開始。

db: 封裝資料庫訪問API,目前支援的driver為sqlalchemy,還包括migrate repository。

conf:Nova的配置項宣告都在這裡,想看Nova配置的作用和預設值可以從這個目錄入手。

locale: 本地化處理。

image: 封裝image API,其實就是呼叫python-glanceclient。

network: 封裝網路服務介面,根據配置不同,可能呼叫nova-network或者neutron。

volume: 封裝資料卷訪問介面,通常是Cinder的client封裝,呼叫python-cinderclient。

virt: 這是所有支援的hypervisor驅動,主流的如libvirt、xen等。

objects: 物件模型,封裝了所有實體物件的CURD操作,相對直接呼叫db的model更安全,並且支援版本控制。

policies: policy校驗實現。

tests: 單元測試和功能測試程式碼。

以上同樣適用於其它服務,比如Cinder等。

另外需要了解的是,所有的API入口都是從xxx-api開始的,RESTFul API是OpenStack服務的唯一入口,也就是說,閱讀原始碼就從api開始。而api元件也是根據實體劃分的,不同的實體對應不同的controller,比如servers、flavors、keypairs等,controller的index方法對應list操作、show方法對應get操作、create建立、delete刪除、update更新等。

根據程序閱讀原始碼並不是什麼好的實踐,因為光理解服務如何初始化、如何通訊、如何傳送心跳等就不容易,各種高階封裝太複雜了。我認為比較好的閱讀原始碼方式是追蹤一個任務的執行過程,比如看啟動虛擬機器的整個流程。因此接下來本文將以建立一臺虛擬機器為例,一步步分析其過程。

2 建立虛擬機器過程分析

這裡以建立虛擬機器過程為例,根據前面的總體套路,一步步跟蹤其執行過程。需要注意的是,Nova支援同時建立多臺虛擬機器,因此在排程時需要選擇多個宿主機。

S1 nova-api

入口為

nova/api/openstack/compute/servers.py

的create方法,該方法檢查了一堆引數以及policy後,呼叫compute_api的create方法。

def

create

self

req

body

):

“”“Creates a new server for a given user。”“”

context

=

req

environ

‘nova。context’

server_dict

=

body

‘server’

password

=

self

_get_server_admin_password

server_dict

name

=

common

normalize_name

server_dict

‘name’

])

。。。

flavor_id

=

self

_flavor_id_from_req_data

body

try

inst_type

=

flavors

get_flavor_by_flavor_id

flavor_id

ctxt

=

context

read_deleted

=

“no”

instances

resv_id

=

self

compute_api

create

context

inst_type

image_uuid

display_name

=

name

display_description

=

description

availability_zone

=

availability_zone

forced_host

=

host

forced_node

=

node

metadata

=

server_dict

get

‘metadata’

{}),

admin_password

=

password

requested_networks

=

requested_networks

check_server_group_quota

=

True

**

create_kwargs

except

exception

QuotaError

exception

PortLimitExceeded

as

error

raise

exc

HTTPForbidden

explanation

=

error

format_message

())

。。。

這裡的compute_api即前面說的

nova/compute/api.py

模組,找到該模組的create方法,該方法會建立資料庫記錄、檢查引數等,然後呼叫compute_task_api的build_instances方法:

self

compute_task_api

schedule_and_build_instances

context

build_requests

=

build_requests

request_spec

=

request_specs

image

=

boot_meta

admin_password

=

admin_password

injected_files

=

injected_files

requested_networks

=

requested_networks

block_device_mapping

=

block_device_mapping

compute_task_api即conductor的api。py。conductor的api並沒有執行什麼操作,直接呼叫了conductor_compute_rpcapi的build_instances方法:

def schedule_and_build_instances(self, context, build_requests,

request_spec, image,

admin_password, injected_files,

requested_networks, block_device_mapping):

self。conductor_compute_rpcapi。schedule_and_build_instances(

context, build_requests, request_spec, image,

admin_password, injected_files, requested_networks,

block_device_mapping)

該方法就是conductor RPC API,即

nova/conductor/rpcapi.py

模組,該方法除了一堆的版本檢查,剩下的就是對RPC呼叫的封裝,程式碼只有兩行:

cctxt

=

self

client

prepare

version

=

version

cctxt

cast

context

‘build_instances’

**

kw

其中cast表示非同步呼叫,build_instances是遠端呼叫的方法,kw是傳遞的引數。引數是字典型別,沒有複雜物件結構,因此不需要特別的序列化操作。

截至到現在,雖然目錄由api->compute->conductor,但仍在nova-api程序中執行,直到cast方法執行,該方法由於是非同步呼叫,因此nova-api任務完成,此時會響應使用者請求,虛擬機器狀態為building。

S2 nova-conductor

由於是向nova-conductor發起的RPC呼叫,而前面說了接收端肯定是manager。py,因此程序跳到nova-conductor服務,入口為nova/conductor/manager。py的build_instances方法,該方法首先呼叫了_schedule_instances方法,該方法呼叫了scheduler_client的select_destinations方法:

def

_schedule_instances

self

context

request_spec

filter_properties

):

scheduler_utils

setup_instance_group

context

request_spec

filter_properties

# TODO(sbauza): Hydrate here the object until we modify the

# scheduler。utils methods to directly use the RequestSpec object

spec_obj

=

objects

RequestSpec

from_primitives

context

request_spec

filter_properties

hosts

=

self

scheduler_client

select_destinations

context

spec_obj

return

hosts

scheduler_client和compute_api以及compute_task_api都是一樣對服務的client SDK呼叫,不過scheduler沒有api。py,而是有個單獨的client目錄,實現在client目錄的__init__。py,這裡僅僅是呼叫query。py下的SchedulerQueryClient的select_destinations實現,然後又很直接地呼叫了scheduler_rpcapi的select_destinations方法,終於又到了RPC呼叫環節。

def _schedule_instances(self, context, request_spec, filter_properties):

scheduler_utils。setup_instance_group(context, request_spec,

filter_properties)

# TODO(sbauza): Hydrate here the object until we modify the

# scheduler。utils methods to directly use the RequestSpec object

spec_obj = objects。RequestSpec。from_primitives(

context, request_spec, filter_properties)

hosts = self。scheduler_client。select_destinations(context, spec_obj)

return hosts

毫無疑問,RPC封裝同樣是在scheduler的rpcapi中實現。該方法RPC呼叫程式碼如下:

return

cctxt

call

ctxt

‘select_destinations’

**

msg_args

注意這裡呼叫的call方法,即同步RPC呼叫,此時nova-conductor並不會退出,而是堵塞等待直到nova-scheduler返回。因此當前狀態為nova-conductor為blocked狀態,等待nova-scheduler返回,nova-scheduler接管任務。

S3 nova-scheduler

同理找到scheduler的manager。py模組的select_destinations方法,該方法會呼叫driver方法,這裡的driver其實就是排程演算法實現,通常用的比較多的就是Filter Scheduler演算法,對應filter_scheduler。py模組,該模組首先透過host_manager拿到所有的計算節點資訊,然後透過filters過濾掉不滿足條件的計算節點,剩下的節點透過weigh方法計算權值,最後選擇權值高的作為候選計算節點返回。最後nova-scheduler返回排程結果的hosts集合,任務結束,返回到nova-conductor服務。

S4 nova-condutor

回到scheduler/manager。py的build_instances方法,nova-conductor等待nova-scheduler返回後,拿到排程的計算節點列表。因為可能同時啟動多個虛擬機器,因此迴圈呼叫了compute_rpcapi的build_and_run_instance方法。

for

instance

host

in

six

moves

zip

instances

hosts

):

instance

availability_zone

=

availability_zones

get_host_availability_zone

context

host

‘host’

]))

try

# NOTE(danms): This saves the az change above, refreshes our

# instance, and tells us if it has been deleted underneath us

instance

save

()

except

exception

InstanceNotFound

exception

InstanceInfoCacheNotFound

):

LOG

debug

‘Instance deleted during build’

instance

=

instance

continue

。。。

self

compute_rpcapi

build_and_run_instance

context

instance

=

instance

host

=

host

‘host’

],

image

=

image

request_spec

=

request_spec

filter_properties

=

local_filter_props

admin_password

=

admin_password

injected_files

=

injected_files

requested_networks

=

requested_networks

security_groups

=

security_groups

block_device_mapping

=

bdms

node

=

host

‘nodename’

],

limits

=

host

‘limits’

])

看到xxxrpc立即想到對應的程式碼位置,位於compute/rpcapi模組,該方法向nova-compute發起RPC請求:

cctxt

cast

ctxt

‘build_and_run_instance’

。。。

由於是cast呼叫,因此發起的是非同步RPC,因此nova-conductor任務結束,緊接著終於輪到nova-compute登場了。

S5 nova-compute

到了nova-compute服務,入口為compute/manager。py,找到build_and_run_instance方法,該方法呼叫了driver的spawn方法,這裡的driver就是各種hypervisor的實現,所有實現的driver都在virt目錄下,入口為driver。py,比如libvirt driver實現對應為virt/libvirt/driver。py,找到spawn方法,該方法拉取映象建立根磁碟、生成xml檔案、define domain,啟動domain等。最後虛擬機器完成建立。nova-compute服務結束。

3 一張圖總結

以上是建立虛擬機器的各個服務的互動過程以及呼叫關係,略去了很多細節。需要注意的是,所有的資料庫操作,比如instance。save()以及update()操作,如果配置use_local為false,則會向nova-conductor發起RPC呼叫,由nova-conductor代理完成資料庫更新,而不是直接由nova-compute更新資料庫,這裡的RPC呼叫過程在以上的分析中省略了。

整個流程用一張圖表示為:

如何閱讀OpenStack原始碼

如何閱讀OpenStack原始碼

如果你對OpenStack的其它服務以及操作流程感興趣,可以參考我的openstack-workflow專案, 這個專案是我本人在學習過程中記錄,繪製成序列圖,上圖就是其中一個例項。專案地址為:

https://

github。com/int32bit/ope

nstack-workflow

標簽: NOVA  compute  API  呼叫  py