1. 程式人生 > >OpenStack Nova啟動例項流程

OpenStack Nova啟動例項流程

1、概述

         啟動一個新的例項,會涉及到OpenStack Nova中的多個元件:

API伺服器,接收使用者端的請求,並且將其傳遞給雲控制器。

雲控制器,處理計算節點、網路控制器、API伺服器和排程器之前的通訊。

排程器,選擇一個執行命令的主機。

計算節點,管理計算例項:啟動/終止例項,新增/刪除卷…

網路控制器,管理網路資源:分配固定IP地址,配置VLAN

         啟動一個例項的流程如下:API伺服器收到使用者的run_instance命令,API伺服器將訊息傳遞給雲控制器(1);對使用者進行身份驗證;雲控制器將訊息轉發到排程器(2);排程器將訊息扔給一個隨機的一個主機,並通知它啟動一個新例項(3);主機上的計算服務接收該訊息;計算服務啟動例項需要一個固定的IP,所以傳送訊息給網路控制器(5,6,7,8);之後,計算服務生產出一個新的例項。

2、API

         可以使用OpenStack API或者EC2 API來啟動新的例項。下面以EC2 API為例。新增一個新的key pair,並使用它啟動一個新的m1.tiny型別的例項:

cd /tmp/
euca-add-keypair test > test.pem
euca-run-instances -k test -t m1.tiny ami-tiny

         呼叫檔案api/ec2/cloud.py中的run_instances()函式,在該函式中會呼叫檔案compute/API.py中的compute API的create函式:

def run_instances(self, context, **kwargs):
  ...
  instances = self.compute_api.create(context,
            instance_type=instance_types.get_by_type(
                kwargs.get('instance_type', None)),
            image_id=kwargs['image_id'],
            ...

         compute API  create函式流程如下:

檢查此種類型的例項數是否已經達到最大值;

如果不存在安全組,則建立一個;

為新的例項建立MAC地址和主機名;

傳送訊息給排程器以執行例項。

3、訊息投遞

         傳送訊息給排程器,在OpenStack中,這種型別的訊息傳遞定義為RPC投遞,使用RabbitMQ進行傳遞。訊息生產者(API)傳送訊息到一個topic exchange(scheduler  topic)。消費者(排程器)從佇列中接收訊息。因為是訊息投遞,所以無需響應訊息。

         下面是訊息投遞的程式碼:

LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
        " instance %(instance_id)s") % locals())
rpc.cast(context,
         FLAGS.scheduler_topic,
         {"method": "run_instance",
          "args": {"topic": FLAGS.compute_topic,
                   "instance_id": instance_id,
                   "availability_zone": availability_zone}})

       可見在訊息投遞時使用了scheduler topic,並且希望排程器在傳送訊息時,使用compute topic。

4、排程器

         排程器接收訊息,並且發”run_instance”訊息給隨機的主機。這裡使用的是chance 排程器。還有其他型別的排程器,比如zone排程器(在一個特定的可用區域內隨機選擇主機)、簡單排程器(選擇最小負載的主機)。現在主機已經選擇好了,接下來就是傳送訊息給該主機的計算服務了。

rpc.cast(context,
         db.queue_get_for(context, topic, host),
         {"method": method,
          "args": kwargs})
LOG.debug(_("Casting to %(topic)s %(host)s for %(method)s") % locals())

5、計算節點

         計算節點接收訊息,然後呼叫compute/manager.py中的run_instance方法:

def run_instance(self, context, instance_id, **_kwargs):
  """Launch a new instance with specified options."""
  ...

         run_instance()的流程如下:

檢查例項是否已經執行;

分配固定的ip低值;

設定一個VLAN或者橋;

使用virtualization driver產生一個例項。

6、呼叫網路控制器

         在分配固定IP時,會用到RPC呼叫。RPC呼叫不同於RPC投遞,它使用topic.hostexchange,表明它的目的地為特定主機,而且RPC呼叫需要響應。

 

7、產生例項

         下面就是由virtualization driver執行產生例項的過程。以libvirt為例,程式碼在virt/libvirt_conn.py中。

         啟動例項的第一件事就是建立libvirt xml。方法to_xml用來生成xml的內容。下面是我們例項的XML檔案:

<domain type='qemu'>
    <name>instance-00000001</name>
    <memory>524288</memory>
    <os>
        <type>hvm</type>
        <kernel>/opt/novascript/trunk/nova/..//instances/instance-00000001/kernel</kernel>
        <cmdline>root=/dev/vda console=ttyS0</cmdline>
        <initrd>/opt/novascript/trunk/nova/..//instances/instance-00000001/ramdisk</initrd>
    </os>
    <features>
        <acpi/>
    </features>
    <vcpu>1</vcpu>
    <devices>
        <disk type='file'>
            <driver type='qcow2'/>
            <source file='/opt/novascript/trunk/nova/..//instances/instance-00000001/disk'/>
            <target dev='vda' bus='virtio'/>
        </disk>
        <interface type='bridge'>
            <source bridge='br100'/>
            <mac address='02:16:3e:17:35:39'/>
            <!--   <model type='virtio'/>  CANT RUN virtio network right now -->
            <filterref filter="nova-instance-instance-00000001">
                <parameter name="IP" value="10.0.0.3" />
                <parameter name="DHCPSERVER" value="10.0.0.1" />
                <parameter name="RASERVER" value="fe80::1031:39ff:fe04:58f5/64" />
                <parameter name="PROJNET" value="10.0.0.0" />
                <parameter name="PROJMASK" value="255.255.255.224" />
                <parameter name="PROJNETV6" value="fd00::" />
                <parameter name="PROJMASKV6" value="64" />
            </filterref>
        </interface>

        <!-- The order is significant here.  File must be defined first -->
        <serial type="file">
            <source path='/opt/novascript/trunk/nova/..//instances/instance-00000001/console.log'/>
            <target port='1'/>
        </serial>

        <console type='pty' tty='/dev/pts/2'>
            <source path='/dev/pts/2'/>
            <target port='0'/>
        </console>

        <serial type='pty'>
            <source path='/dev/pts/2'/>
            <target port='0'/>
        </serial>

    </devices>
</domain>

         使用的虛機管理程式為qemu,客戶機的記憶體為524位元組,客戶端OS從儲存於主機OS中的核心和initrd啟動。

         分配各客戶機的虛擬CPU個數為1. 電源管理中使能了ACPI。還定義了多個裝置:

         磁碟映象為主機OS上的一個檔案,使用qcow2驅動器,qcow2是一種qemu磁碟映象的寫時複製格式;

         網路介面是客戶機可見的橋,定義了一系列網路過濾器引數,比如IP地址10.0.0.3意味著始終使用該地址作為源IP地址;

         日誌檔案,所有傳送給字元裝置的資料全部寫入console.log中;

         偽終端,virsh控制檯可用於連線本地串列埠。

         接下來是網路過濾器的配置。預設使用的防火牆driver是iptables。規則由IptablesFirewallDriver類的apply_ruleset方法定義。下面是本例項的防火牆規則:

*filter
...
:nova-ipv4-fallback - [0:0]
:nova-local - [0:0]
:nova-inst-1 - [0:0]
:nova-sg-1 - [0:0]
-A nova-ipv4-fallback -j DROP
-A FORWARD -j nova-local
-A nova-local -d 10.0.0.3 -j nova-inst-1
-A nova-inst-1 -m state --state INVALID -j DROP
-A nova-inst-1 -m state --state ESTABLISHED,RELATED -j ACCEPT
-A nova-inst-1 -j nova-sg-1
-A nova-inst-1 -s 10.1.3.254 -p udp --sport 67 --dport 68
-A nova-inst-1 -j nova-ipv4-fallback
-A nova-sg-1 -p tcp -s 10.0.0.0/27 -m multiport --dports 1:65535 -j ACCEPT
-A nova-sg-1 -p udp -s 10.0.0.0/27 -m multiport --dports 1:65535 -j ACCEPT
-A nova-sg-1 -p icmp -s 10.0.0.0/27 -m icmp --icmp-type 1/65535 -j ACCEPT
COMMIT

         定義了防火牆規則之後,就是建立映象,由方法_create_image()處理:

def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None):
  ...

         在該方法中,會根據上面的XML建立libvirt.xml;複製虛擬機器管理程式要使用的randisk、initrd和磁碟映象;如果使用flat網路管理器,則會將一個網路配置植入到客戶端的OS映象中。本例中使用VLAN管理器。

         例項的SSH key植入到映象中,本過程是呼叫disk.inject_data方法:

disk.inject_data(basepath('disk'), key, net,
                 partition=target_partition,
                 nbd=FLAGS.use_cow_images)

         basepath('disk')表示例項的磁碟映象在主機OS中的位置,key是SSH key字串,在我們的例子中不設定網路,因為不需要植入網路配置。因為使用的是核心映象,所以沒有分割槽,否則的話會使用分割槽的磁碟映象。在inject_data內部:

         第一件事就是連結映象到一個裝置,這發生在_link_device中:

device = _allocate_device()
utils.execute('sudo qemu-nbd -c %s %s' % (device, image))
# NOTE(vish): this forks into another process, so give it a chance
#             to set up before continuuing
for i in xrange(10):
    if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
        return device
    time.sleep(1)
raise exception.Error(_('nbd device %s did not show up') % device)

         _allocate_device()返回下一個可用的ndb裝置:/dev/ndbx,其中x在0到15之間。qemu-nbd是一個QEMU磁碟網路塊裝置伺服器。一旦該過程返回,則得到了裝置,比如/dev/ndb0.

         對該裝置禁用檔案系統檢查,這裡的mapped_device是”/dev/ndb0”。

out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)

         將該檔案系統掛載到臨時目錄,並將SSH KEY新增到authorized_keys檔案中。

sshdir = os.path.join(fs, 'root', '.ssh')
utils.execute('sudo mkdir -p %s' % sshdir)  # existing dir doesn't matter
utils.execute('sudo chown root %s' % sshdir)
utils.execute('sudo chmod 700 %s' % sshdir)
keyfile = os.path.join(sshdir, 'authorized_keys')
utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')

         上面的程式碼中,fs就是臨時目錄。

         最後,unmount檔案系統,刪除裝置。這就結束了映象的建立和安裝。

         在虛擬機器化驅動器中的spawn方法中的下一步就是使用驅動器的createXML()載入例項,然後就是應用防火牆規則。

原文:http://www.laurentluce.com/posts/openstack-nova-internals-of-instance-launching/