openstack之虛擬機創建流程分析


       這篇博文靜靜的呆在草稿箱大半年了。假設不是由於某些原因被問到,以及由於忽略它而導致的損失,否則我也不知道什么時候會將它完畢。感謝這段時間經歷的挫折,讓我知道不足。希望你能給我更大的決心!

       本文試圖具體地描寫敘述openstack創建虛擬機的完整過程。從用戶發起請求到虛擬機成功執行,包含client請求的發出、keystone身份驗證、nova-api接收請求、nova-scheduler調度、nova-computer創建、nova-network分配網絡。對於每個模塊在創建虛擬機的過程中所負責的功能和執行的操作,進行較為具體描寫敘述和討論。為了方便描寫敘述,本文如果全部的服務ip地址為localhost。port使用服務的默認設置port。openstack為F版。下圖為創建虛擬機的一個大概流程圖,粗糙地表示下。接下來將對每個模塊進行具體介紹。如有錯誤,歡迎拍磚!



client

      創建虛擬機的第一步是須要client調用nova-api,發送創建虛擬機請求。眼下openstack提供兩種client:

1 命令行指令nova,通過指定命令行參數。就能夠請求nova-api創建虛擬機,一個最簡單的創建虛擬機指令例如以下:

nova boot vm_name --flavor flavor_id --image image_uuid

2 網頁交互頁面horizon,這是通過web操作頁面來調用nova-api創建虛擬機,比較簡單易用。選定相關參數后。點擊create就能夠了。

這兩種client除了UI不一樣以外,功能方面基本都是一樣。

就創建虛擬機來講。它們須要完畢:

  • 用戶身份驗證。client構造一個body格式例如以下:
    {"auth": {                                                       
                       "passwordCredentials": {"username": self.user,               
                                               "password": self.password}}
                       "tenantName": "admin" }
    向keystone發送HTTP請求(keystone的服務地址:nova命令一般通過環境變量OS_AUTH_URL設置,horizon則通過openstack_dashboard.local.local_settings.OPENSTACK_KEYSTONE_URL設置),url為http://localhost:5000/v2.0/tokens 。假設驗證通過,keystone則返回token_id和serviceCatalog。身份驗證通過后。client才可對nova-api發送創建請求。

    由於client只配置了keystone的服務地址和port,對於nova-api的服務地址它是不知道的。所以須要先通過keystone驗證,然后由keystone將nova-api的服務地址放在serviceCatalog中返回給它。事實上serviceCatalog中不只包括nova-api的服務地址,還有glance-api、cinder-api等服務的地址,這些地址在以后的鏡像下載、塊存儲請求時會用到。token_id被放在請求nova-api的headers中。用作nova-api驗證。



  • 對傳入flavor和image參數進行驗證,以確定資源是否有效(對於一些非必須參數,此處省略討論)。

    對於nova boot,flavor_id和image_uuid須要通過命令行參數指定,novaclient.v1_1.shell._boot()分別向nova-api的"http://localhost:8774/v2/project_id/flavors/flavor_id"和"http://localhost:8774/v2/project_id/images/image_id"發送HTTP請求,驗證資源是否存在。對於horizon,它首先運行horizon.api.nova.flavor_list()和horizon.api.glance.image_list_detailed(),獲取全部可用的flavor ids和image ids,創建虛擬機時僅僅能從這些可用的id中選擇。

    注意這里nova boot和horizon對於image的驗證有些不同,nova boot是請求nova-api,nova-api再請求glance-api以驗證image uuid,而horizon是直接請求glance-api獲取全部的image ids。

  • 最后設置好請求參數后(除name、flavor和image外,其他可使用默認值),通過novaclient.v1_1.ServerManager.create()發送創建虛擬機請求,該函數主要是將請求參數構造成規定格式的body。然后向nova-api發送HTTP請求。

    關於body的模樣,可看看novaclient.v1_1.BootingManagerWithFind._boot() 。以上面的nova boot命令為例,body內容例如以下:

    {'server': {'flavorRef': '1',
                'hypervisor_type': 'QEMU',
                'imageRef': 'd3670457-16f5-4c70-913f-6fc7b76706e4',
                'max_count': 1,
                'min_count': 1,
                'name': 'test-ic'}}
    
    這里的flavorRef相應的是數據庫instance_types的flavorid字段(非id字段),上面命令行傳入的flavor_id也是指數據庫的flavorid字段。向nova-api的http://localhost:8774/v2/project_id/flavors/flavorid請求時,它通過nova.api.openstack.compute.views.flavors.ViewBuilder,將數據庫的id字段作為instance_type_id。將flavorid作為id進行返回的。

       能夠看出,不管nova boot還是horizon,最后都是通過novaclient向nova-api發送請求的。

novaclient是針對nova-api做了一層封裝,如獲取驗證token-id,以特定的格式構造HTTP請求headers和body,解析請求的結果等。事實上能夠不用nova boot命令行和horizon,甚至novaclient都不須要,我們全然能夠設定好HTTP請求的headers和body,直接請求nova-api。只是這三個工具將這些繁瑣的工作替我們做了。我們僅僅須要填寫參數就能夠了。最后注意下。nova命令事實上僅僅是novaclient的一個entry point:novaclient.shell.main()。而nova boot實際上調用的是novaclient.v1_1.shell.do_boot()。


keystone

       由上可知,在client發起創建虛擬機請求時,keystone須要對client的username和password進行驗證。keystone-all與nova-api一樣,api的公布沒有採用不論什么框架,而是使用router、paste類庫。從頭寫的,實現風格上又與nova-api有點差異。

keystone-all服務會監聽兩個port:localhost:5000,即publicURL。一般用戶使用password能夠訪問;localhost:35357。即adminURL,僅僅能使用admin賬戶和password訪問。

在創建虛擬機流程中,調用的keystone的api有兩個(事實上,每次請求基本都會調用這兩個api):

1 http://localhost:5000/v2.0/tokens,請求該api來獲取token_id和serviceCatalog,由client調用。keystone會將該api的請求交給keystone.TokenController.authenticate()處理。該函數主要完畢:

  •  對client傳過來的name、password、tenant_id進行驗證。keystone的數據庫user表中保存了user相關信息,包含password加密后的hash值。password的加密使用了sha512算法。由passlib庫提供。密碼驗證例如以下:passlib.hash.sha512_crypt.verify(password, hashed)。
  • 依據CONF.signing.token_format配置項,為client的每一次請求生成一個token_id。眼下支持的選擇有兩個UUID、PKI。默覺得UUID。所以在默認情況下,一個token_id就是一個隨機產生的uuid。token_id有一個有效時間。從token_id的生成的時刻開始算起。有效時間可通過CONF.token.expiration配置項設置,默認值為86400s。
  • 構建serviceCatalog。這里面就是一堆endpoints,包含nova-api、glance-api、cinder-api、keystone-api等。依據配置項CONF.catalog.driver。能夠從數據庫的endpoint表中獲取,也能夠從模板文件里獲取。

2 http://localhost:35357/v2.0/tokens/token_id,對請求的token_id進行驗證。由nova-api調用。nova-api接收到請求后。首先使用請求攜帶的token_id來訪問該api,以驗證請求是否通過驗證。

當然nova-api須要在body里加上admin的賬戶和password信息,這些信息須要在api-paste.ini中配置。還有keystone的服務地址,由於在驗證沒通過之前。不能使用client傳過來的endpoints。glance-api、cinder-api等在接收到client請求后。都會調用該api對client的token_id進行驗證。

該api的請求交給keystone.TokenController.validate_token()處理,事實上就是使用請求的token_id進行一次數據庫查詢。


nova-api

        nova-api是一個統稱,它是一類服務的集合。如openstack之nova-api服務流程分析所說,在默認配置下,它包括ec2(與EC2兼容的API)。osapi_compute(openstack compute自己風格的API),osapi_volume(openstack volume服務API)。metadata(metadata 服務API)等api服務。每一個服務提供不同的api,只是盡管ec2和osapi_compute的api不同。但功能是同樣的。這里,創建虛擬機請求的api是:

http://localhost:8774/v2/project_id/servers。由osapi_compute服務公布。該api是RESTFUL的,以POST方法請求該api。經過幾層middleware處理后,終於交給nova.api.openstack.compute.servers.Controller.create()處理。

它們主要完畢下面功能:

  • 驗證client的token_id。通過keystone.middleware.auth_token.AuthProtocol.__call__()進行驗證,它是一個middleware,通過api-paste.ini配置。如上面keystone所講,它是通過請求http://localhost:35357/v2.0/tokens/token_id實現的。
  • 檢查創建虛擬機參數是否有效與合法,由nova.api.openstack.compute.servers.Controller.create()實現。

    如。檢查虛擬機name是否符合命名規范。flavor_id是否在數據庫中存在,image_uuid是否是正確的uuid格式等。

  • 檢查instance、vcpu、ram的數量是否超過限制,並預留所需資源,由nova.quota.QUOTAS管理。

    每一個project擁有的資源都是有限的,如創建虛擬機的個數,vcpu個數,內存大小。volume個數等。

    默認情況下。全部project的擁有的資源數量相等,由quota_instances、quota_cores、quota_ram、quota_volumes等配置項指定。使用admin賬戶。以PUT方法請求http://localhost:8774/v2/admin_project/os-quota-sets/project_id。可為特定的project設置配額。

  • 檢查metadata長度是否超過限制。inject file的個數是否超過限制,由nova.quota.QUOTAS管理檢測。普通情況下。這些參數都為空。都能通過。
  • 檢查指定的network和fixed ip是否有效。如network是否存在,fixed ip是否屬於該network,fixed ip有沒有被分配等。

    普通情況下,該參數也為空,由network自己主動分配。

  • 檢查image、disk、ramdisk是否存在且可用,這個是向glance-api(http://localhost:9292/v1/images/image_id)請求。獲取返回數據進行推斷的。

    並推斷flavor是否滿足該image的最小配置需求,如內存,虛擬磁盤是否滿足image的最小值。

  • 當全部資源充足。而且全部傳參都有效時。更新數據庫。新建一條instance記錄,vm_states設為BUILDING。task_state設為SCHEDULING。假設沒有給虛擬機指定security group。那么將默認使用default。instance與security group的關聯很隱蔽,在db.instance_create()中,注意觀察。

  • 提交預留資源,完畢資源分配。

    通過rpc call,將全部參數傳給nova-scheduler的nova.scheduler.manager.SchedulerManager.run_instance()。由它運行虛擬機調度。

        在token_id驗證通過的情況下。nova-api的主要任務是資源配額和參數檢查,並創建數據庫。

假設你使用dashboard,此時你在頁面上將會看到虛擬機處於scheduling狀態。只是該狀態持續時間非常短,差點兒察覺不到,除非nova-scheduler出問題了。


nova-scheduler

       與nova-api提供外部服務不同。nova各組件之間相互調用,使用的是以rabbitmq作為消息隊列的RPC調用。這里忽略RPC的實現細節。僅僅需知道rcp call調用哪個的節點的哪個服務就能夠了。nova-scheduler的run_instance()從nova-api接收到的參數中僅僅使用到了request_spec和filter_properties,其余參數將直接傳遞給nova-compute去創建虛擬機。request_spec包括虛擬機的type、number、uuids等信息,在調度中須要用這些信息作為參考。

filter_properties包括指定調度規則信息,如force_hosts指定調度到特定的節點,ignore_hosts不調度到某些節點等。nova-scheduler在接收到nova-api的請求后。由nova.scheduler.filter_scheduler.FilterScheduler.scheduler_run_instances(),它主要完畢下面任務:

  • 獲取現存全部計算節點及其信息。調用nova.scheduler.host_manager.HostManager.get_all_host_states()獲取。這些信息包含compute_nodes表中的信息,及由每一個nova-compute通過定時任務_publish_service_capabilitites上傳的capabilities信息。包含cpu、vcpu、內存、磁盤等大小和使用情況。

  • 使用指定的filter對上面返回的節點進行過濾,調用nova.scheduler.filters.HostFilterHander.get_filtered_hosts()實現。這些filter可通過配置項scheduler_default_filters指定,它能夠包含nova.scheduler.filters中不論什么已經實現的filter。如RamFilter,它用來過濾內存不足以創建虛擬機的host。ComputeFilter用來過濾一些service無效的host。也能夠在這里加入自己定義的filter。然后加入到scheduler_default_filters配置項中即可了。只是這里須要注意一點,通過force_hosts和forces_nodes指定的hosts。將不經過filter過濾。直接返回。

  • 使用weighers對經過過濾的host進行排序,調用nova.scheduler.weights.HostWeightHander.get_weighed_objects()實現,在F版中僅僅實現了一個RamWeigher,僅僅以內存大小對hosts做了一個排序。

    后面的版本號可能認為使用Weigher。又使用WeigherHander。僅僅是對內存大小做排序。有點小題大做了,在2012.2.4中用一個函數nova.scheduler.least_cost.weighted_sum()就實現排序。但在2013.2中又回到原來的結構了。

  • 從排名前n個hosts中。隨機選取一個用於創建虛擬機。n可通過配置項scheduler_host_subset_size設置,默認值為1。即選取最優節點進行創建。並更新scheduler保存host的資源信息,如內存、磁盤、vcpu等。使得調度下一個虛擬機時,使用的資源信息及時更新。
  • 更新數據庫設置instance的host=NULL。scheduled_at=now(),然后通過RPC調用上面選取host的nova-compute進行創建虛擬機。

       nova-scheduler相對而言,邏輯比較簡單。代碼也不難。只是須要注意在node-scheduler運行過程中,不會改變虛擬機的vm_state和tast_state。

所以在horizon上也不會有變化。


nova-compute

        nova-scheduler選定host后,隨即通過rpc調用,調用host的nova-compute服務的nova.compute.manager.ComputeManager.run_instance()。

由它運行真正的創建虛擬機操作。只是在介紹之前,須要簡單提及一下nova-compute的兩個定時任務:

  1. update_available_resource。該任務首先獲取當前計算節點的cpu個數、總內存大小、總磁盤大小,數據由nova.virt.libvirt..driver.LibvirtDriver.get_available_resource()獲取。

    然后從數據庫中查找到執行在該節點上的全部虛擬機信息,統計出這些虛擬機所使用的vcpu個數、內存大小、磁盤大小。並將計算節點的總資源數量減去虛擬機使用的數量,得到空暇內存大小和空暇磁盤大小。然后更新數據庫compute_node信息。這里須要注意從數據庫中獲取的虛擬機使用資源數量並非一定是計算節點消耗的資源數量,如1)虛擬機使用磁盤鏡像為qcow2格式時,它的大小要小於或等於實際分配的大小。2)也有可能由於數據不同步,使得統計的數據與實際的資源使用不一致。

  2. _report_driver_status。

    該任務是用於定時更新當前計算節點的capabilities信息,相同也是通過LibvirtDriver來獲取資源數據的,只是在計算已使用資源方面,是直接使用通過調用multiprocessing、libvirt、os等返回的cpu個數、內存、磁盤使用情況。並附加上host_ip信息。並由定時任務_publish_service_capabilities通過rpc call轉發到nova.scheduler.host_manager.HostManager.service_states中。

        這兩個定時任務為nova-scheduler提供了實時的host信息。所以才干實現准確調度。因為capabilities信息與compute_node表中信息有非常大的相似度,所以調度過程中非常少用到。

nova-scheduler調度到nova-compute的run_instance()主要完畢什么功能呢:

  • 檢查虛擬機是否已創建。及instance的image大小是否超過root的大小,超過則報錯。

  • 更新數據庫,將instance的vm_state=BUILDING,task_state=NULL狀態。horizon上面會有反應,但時間非常短。

  • 給虛擬機分配資源,包含cpu、內存、磁盤等。更新數據庫,設置instance的host字段。

  • 分配網絡。首先將instance的tast_state=NETWORKING,然后通過rpc調用nova-network的nova.network.manager.NetworkManager.allocate_for_instance(),返回網絡信息。nova-network處理的具體將在以下的nova-network模塊討論。

  • 建立塊設備。將task_state=BLOCK_DEVICE_MAPPING。由於未給分配塊設備,這步將不進行操作,有待后面討論。

  • 將task_state=SPAWNING,假設該類型的instance第一次在該計算節點上創建。該狀態要持續幾分鍾。由於須要下載鏡像。

  • 依據上面配置的虛擬機信息,生成xml,寫入libvirt,xml文件,生成console.log文件。
  • 下載鏡像,kernel、ramdisk、image,通過glance api下載。它們首先會被放在FLAGS.instances_path的_base文件夾下。然后copy一份到instance的文件夾以下。這樣,假設這個計算節點上創建同樣虛擬機時。首先查找_base中是否已經下載。假設已經下載過了,則直接copy就能夠了。

    對於image,一般採用qcow2格式,作為qemu-img的backing_file新建一個image使用,這樣能夠節約空間。

  • 向下載過后的磁盤文件。注入指定的內容。如public_key、/etc/network/interfaces、rootpassword、指定的文件路徑和內容、/etc/vmuuid等。

    原理比較簡單,將下載的image使用mount命令進行掛載,然后將要寫入的內容下到特定的位置。

  • 最后使用上面生成的xml,調用libvirt創建虛擬機,等待虛擬機正常執行。

  • 更新數據庫,將instance的vm_state=ACTIVE、task_state=None。

        nova-compute做的事情還是挺多的,生成虛擬機xml配置文件、下載鏡像並注入文件、調用libvirt創建虛擬機等。這里對於libvirt還需繼續研究。

nova-network
        nova-network是nova-compute在創建虛擬機之前。被其調用nova.network.manager.FlatDHCPManger.allocate_for_instance()。為虛擬機分配fixed ip和floating ip(假設auto_assign_floating_ip為True)。並返回網絡信息。

這里network採用 FlatDHCP模式,multihost=True。查看代碼可知 FlatDHCPManager繼承RPCAllocateFixedIP, FloatingIP, NetworkManager三個class,依據python屬性訪問流程(參考python對象之屬性訪問流程)可知,調用FlatDHCPManger.allocate_for_instance()首先運行FloatingIP.allocate_for_instance(),再由它調用NetworkManger.allocate_for_instance()。主要完畢任務例如以下:

  • 獲取網絡信息。用戶可指定network_uuid,否則將直接獲取networks表中全部network。
  • 給每個network。在數據庫創建一個virtual interface,給instance uuid和network id置為對應的值。注意virtual interface表與其他的表不太一樣。當刪除一個virtual interface時。將直接刪除該記錄。而不是將deleted=1。

  • 從數據庫中找出一個network id等於該network或為NULL的fixed ip。設置instance uuid、host、allocated=True、virtual_interface_id。更新dnsmasq的conf文件(默認在nova源代碼文件夾下),同一時候給dnsmasq發送HUP信號,重讀配置文件,當虛擬機動態獲取IP時。dnsmasq依據接收到的mac,查詢該配置文件。返回分配給該虛擬機的ip。這里須要注意下,dnsmasq的啟動參數--dhcp-script=/usr/bin/nova-dhcpbridge。在虛擬機請求和釋放ip時該腳本會被調用,用來設置fixed ip的leased字段。

  • 假設auto_assign_floating_ip為True,則給虛擬機分配floating ip。這里分配的floating ip原本不屬於不論什么project。分配過后才設置它的project_id和auto_assigned字段。並將fixed_ip_id字段設為上面分配到fixed ip。

  • 在對外出口網卡上綁定floating ip,設置iptables nat的nova-network-PREREOUTING、nova-network-OUTPUT和nova-network-float-snat表,做SNAT和DNAT轉換。

    這樣就能夠通過floating ip從外部訪問虛擬機了。

  • 通過instance uuid查詢數據庫。獲取它的vif、network、floating ip、fixed ip等信息,以nova.network.model.NetworkInfo結構構造。返回給nova-compute。





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM