分类: 云计算
2015-10-30 11:09:07
学习 KVM 的系列文章:
在 Nova Compute 节点上运行的 nova-compute 服务调用 Hypervisor API 去管理运行在该 Hypervisor 的虚机。Nova 使用 libvirt 管理 QEMU/KVM 虚机,还使用别的 API 去管理别的虚机。
libvirt 的实现代码在 /nova/virt/libvirt/driver.py 文件中。
这里是 OpenStack Hypervisor Matrix。
这里是 每个 Linux 发行版里面 libvirt, QEMU/KVM 的版本号。
请注意Juno 版本 Nova 对 libvirt 和 QEMU 的各种最低版本要求:
功能 | 最低 libvirt 版本 | 最低 QEMU 版本 | 不支持的后果 |
所有 | 0.9.11 | Nova 不能使用 libvirt driver | |
支持 device callback |
1.1.1 | 不支持的话,就无法支持 Detach PCI/SR-IOV 设备 | |
Live snapshot | 1.3.0 | 1.3.0 | 只能使用 Clod Snapshot |
挂载卷时设置卷的 block 大小(Block IO) | 0.10.2 | 不能使用的话,就不能设置卷的特定 block size,只能使用其默认的 block size。 | |
Block Job Info | 1.1.1 | 不能在线删除卷的快照 (online deletion of volume snapshots) | |
Discard | 1.0.6 | 1.6.0 |
不支持 image 设置 hw_disk_discard 属性,具体参考 BluePrint |
NUMA topology | 1.0.4 | 无法获取 node 的 NUMA topology 信息,就无法将虚机的 vCPU 指定到特定的 node CPU 上,会影响虚机的性能 |
Nova 使用 libvirt 来管理虚机,包括:
创建虚机的配置有几个来源:
(注意:image 的元数据属性的优先级高于 nova.conf 中的配置。只有在没有property的情况下才使用nova.conf中的配置)
创建虚机的过程的几个主要阶段:
(1)消息由 nova-api 路由到某个 nova compute 节点
(2)调用 Neutron REST API 去准备网络。其返回的数据类似:
[VIF({'profile': {}, 'ovs_interfaceid': u'59cfa0b8-2f5c-481a-89a8-7a8711b368a2', 'network': Network({'bridge': 'br-int', 'subnets': [Subnet({'ips': [FixedIP({'meta': {}, 'version': 4, 'type': 'fixed', 'floating_ips': [], 'address': u'10.0.10.14'})], 'version': 4, 'meta': {'dhcp_server': u'10.0.10.11'}, 'dns': [], 'routes': [], 'cidr': u'10.0.10.0/24', 'gateway': IP({'meta': {}, 'version': 4, 'type': 'gateway', 'address': u'10.0.10.1'})})], 'meta': {'injected': False, 'tenant_id': u'74c8ada23a3449f888d9e19b76d13aab'}, 'id': u'a924e87a-826b-4109-bb03-523a8b3f6f9e', 'label': u'demo-net2'}), 'devname': u'tap59cfa0b8-2f', 'vnic_type': u'normal', 'qbh_params': None, 'meta': {}, 'details': {u'port_filter': True, u'ovs_hybrid_plug': True}, 'address': u'fa:16:3e:e0:30:e7', 'active': False, 'type': u'ovs', 'id': u'59cfa0b8-2f5c-481a-89a8-7a8711b368a2', 'qbg_params': None})]
(3)从 image 启动话,nova 会调用 Glane REST API 后者 image metadata 和准备本地启动盘
image metadata:
{u'status': u'active', u'deleted': False, u'container_format': u'bare', u'min_ram': 0, u'updated_at': u'2015-04-26T04:34:40.000000', u'min_disk': 0, u'owner': u'74c8ada23a3449f888d9e19b76d13aab', u'is_public': False, u'deleted_at': None, u'properties': {}, u'size': 13167616, u'name': u'image', u'checksum': u'64d7c1cd2b6f60c92c14662941cb7913', u'created_at': u'2015-04-26T04:34:39.000000', u'disk_format': u'qcow2', u'id': u'bb9318db-5554-4857-a309-268c6653b9ff'}
本地启动盘:
{'disk_bus': 'virtio', 'cdrom_bus': 'ide', 'mapping': {'disk': {'bus': 'virtio', 'boot_index': '1', 'type': 'disk', 'dev': u'vda'}, 'root': {'bus': 'virtio', 'boot_index': '1', 'type': 'disk', 'dev': u'vda'}, 'disk.local': {'bus': 'virtio', 'type': 'disk', 'dev': 'vdb'}, 'disk.swap': {'bus': 'virtio', 'type': 'disk', 'dev': 'vdc'}}}
本地启动盘的文件信息:
root@compute2:/home/s1# qemu-img info /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local
image: /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local
file format: qcow2
virtual size: 1.0G (1073741824 bytes) #由 flavor.ephemeral_disk 指定其 size disk size: 324K
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/ephemeral_1_default
Format specific information:
compat: 1.1
lazy refcounts: false
root@compute2:/home/s1# qemu-img info /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap
image: /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap
file format: qcow2
virtual size: 30M (31457280 bytes) # 由 flavor.swap_disk 指定其size disk size: 196K
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/swap_30
Format specific information:
compat: 1.1
lazy refcounts: false
(4)根据这些信息,生成 domain xml,然后生成其配置使得它是一个持久性虚机 (调用 libvirt Python DefineXML API)。
一个从 image 启动的 Domain 的配置 XML 实例(蓝色部分是注释说明):
"qemu"> 8352e969-0a25-4abf-978f-d9d0ec4de0cd instance-0000002f 51200 # guest.memory = flavor.memory_mb * units.Ki 即 50 * 1024 = 51200 "0">1 #flavor.vcpus ""> "2014.2.2"/> vm11 #input.name 2015-06-09 23:54:04 "tiny2"> #input.flavor 50 1 30 1 1 "bcd37e6272184f34993b4d7686ca4479">admin "74c8ada23a3449f888d9e19b76d13aab">admin "image" uuid="bb9318db-5554-4857-a309-268c6653b9ff"/> #input.source "smbios"> # Nova 中写死的 "manufacturer">OpenStack Foundation "product">OpenStack Nova "version">2014.2.2 "serial">03bb1a0f-ae04-4765-9f3c-d200a2540675 "uuid">8352e969-0a25-4abf-978f-d9d0ec4de0cd hvm #表示 Guest OS 需要 full virtualiaiton 支持 "hd"/> #指定启动盘 "sysinfo"/> #去读取 的定义 # Soft Reboot 需要 ACPI 的支持,否则只能使用 Hard reboot。 # 没 APIC 的话,Windows Guest 会在 Xen 或者 KVM 上崩溃。 "utc"/> #如果Guest OS 是 MS,则是 localtime,否则都是 utc "host-model" match="exact"> # 对于 KVM,如果 CONF.libvirt.cpu_mode 是 none,mode 则设为 "host-model"。具体可参考 "1" cores="1" threads="1"/> #默认的时候,sockets 数目设为 vcpu 的数目,cores 和 threads 都设为 1. 可以通过设置 image 的 hw_cpu_topology 属性来改变这里的设置,具体请参考 以及 "file" device="disk"> # 从 image 启动时候的启动盘(flavor.root_disk) "qemu" type="qcow2" cache="none"/> "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk"/> "virtio" dev="vda"/> #对于 KVM,disk 的 bus 为 "virtio",cdrom 的 bus 为 "ide",floppy 的 bus 为 "fdc" "file" device="disk"> #临时分区 (falvor.ephemeral_disk) "qemu" type="qcow2" cache="none"/> "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local"/> "virtio" dev="vdb"/> "file" device="disk"> #swap 分区 (flavor.swap_disk) "qemu" type="qcow2" cache="none"/> "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.swap"/> "virtio" dev="vdc"/> <interface type="bridge"> # 虚机通过网桥连接到 OVS "fa:16:3e:e0:30:e7"/> "virtio"/> #该 type 可以由 image metadata hw_vif_type 指定。未指定的话,如果配置了 conf.libvirt.use_virtio_for_bridges = true (默认就是 true)的话,QEMU/KVM 会使用 virtio 类型。 "qemu"/> "qbr59cfa0b8-2f"/> #qbr59cfa0b8-2f 连接虚机的 vNIC tap59cfa0b8-2f 和 qvb59cfa0b8-2f ,而 qvb59cfa0b8-2f 练到 OVS 的 br-int 上。 "tap59cfa0b8-2f"/> interface> "file"> 当 CONF.serial_console.enabled = true 时,type 为 "tcp",使用 config 配置,其 XML 为 ;当为 false 时,使用 console.log 文件,type 为 file。 "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/console.log"/> "pty"/> #每个domain都有 type 为 "pty" 的 serial 配置。 "tablet" bus="usb"/> #当 CONF.vnc_enabled = true 或者 CONF.spice.enabled = true 并且 CONF.spice.agent_enabled = false 时添加 tablet,type 和 bus 都是固定的。 "vnc" autoport="yes" keymap="en-us" listen="0.0.0.0"/> #如果 CONF.vnc_enabled = true,那么 keymap=CONF.vnc_keymap;listen=CONF.vncserver_listen #如果 CONF.vnc_enabled 或者 CONF.spice.enabled,则添加该 video 配置 "cirrus"/> #如果 CONF.spice.enabled,则 type 为 qxl;否则为 cirrus。 "virtio"> #如果 CONF.libvirt.mem_stats_period_seconds >0 则添加 memballoon;对 KVM,model 固定为 "virtio" "10"/>
从 bootable volume 启动的话,disk 部分为:
"file" device="disk"> "qemu" type="qcow2" cache="none"/> "/var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local"/> "virtio" dev="vdb"/> "file" device="disk"> "qemu" type="qcow2" cache="none"/> "/var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap"/> "virtio" dev="vdc"/> 26446902-5a56-4c79-b839-a8e13a66dc7a
(5). 启动 domain (调用 libvirt Python createWithFlags API)
(1)通过 domain name 来找到指定 domain 对象 (通过调用 lookupByName API)
(2)调用 volume driver 来建立主机和 Volume 之间的连接
(3)生成 volume 连接的配置 xml,比如:
"block" device="disk"> "qemu" type="raw" cache="none"/> "/dev/disk/by-path/ip-10.0.2.41:3260-iscsi-iqn.2010-10.org.openstack:volume-31367039-0da5-4dac-bf9a-40303b869126-lun-1"/> "virtio" dev="vdd"/> 31367039-0da5-4dac-bf9a-40303b869126
(4)调用 attachDeviceFlags API 将 volume 挂载到该虚机
(1)将 Neutron 分配的 port 连接到 OVS
(2)生成 interface 配置的xml,比如:
<interface type="bridge"> "fa:16:3e:3b:ba:72"/> "virtio"/> "qemu"/> "qbr61fb4206-ae"/> "tap61fb4206-ae"/> interface>
(3)调用 attachDeviceFlags API 来挂载该 interface 到虚机
又纠结了几天,终于到了可以写文章记录下来的时刻了。
简单的讲,nova最核心的功能就是对一大堆的虚拟机进行管理,虚拟机可以是各种各样(kvm, qemu, xen, vmware...),而且管理的方法也可以是各种各样(libvirt, xenapi, vmwareapi...),因为我的电脑不支持CPU的VT,而且以前在visualbox中装openstack,也只能使用qemu,所以这次测试的主要是qemu的使用,另外nova中默认使用的管理虚拟机的API是libvirt,所以这里的测试使用libvirt。
首先来简单说一下我对libvirt和qemu这两个的理解:
1. libvirt
libvirt是一套用c语言写的API,旨在为各种虚拟机提供一套通用的编程接口,而且支持与java,python等语言的绑定。基于libvirt的虚拟机管理工具也有很多:virt-manager(GUI工具),virsh(命令行工具)。其架构示意图如下:
(左图是没有使用libvirt的情况)
这里涉及到几个概念:
(1)Domain:虚拟机的一个运行实例,简单的理解,就是一个虚拟机虚拟出来的操作系统。它的叫法可是多种多样:instance,guest OS,virsual machine,其实都指的同一个概念。
(2)Hypervisor:指的就是虚拟机本身,比如qemu, kvm, xen...
libvirt 由几个不同的部分组成,其中包括应用程序编程接口 (API) 库、一个守护进程 (libvirtd),以及一个默认命令行实用工具 (virsh),libvirtd守护进程负责对虚拟机的管理工作,在用各种工具对虚拟机进行管理的时候,这个守护进程一定要跑起来,而且这个进程可以分为两种,一种是root权限的libvirtd,一种是普通用户权限的libvirtd,前者权限大,可以虚拟计算机的各种设备。
开启root权限的libvirtd守护进程要以root身份去运行:sudo libvirtd --daemon
2. qemu
qemu是一个仿真器,即可用于来宾操作系统的虚拟化,也可以作为完整的机器仿真器使用,运行使用主机 CPU 或其他 CPU 架构的操作系统。
qemu 支持两种操作模式:用户模式仿真和系统模式仿真。用户模式仿真 允许一个 CPU 构建的进程在另一个 CPU 上执行(执行主机 CPU 指令的动态翻译并相应地转换 Linux 系统调用)。系统模式仿真 允许对整个系统进行仿真,包括处理器和配套的外围设备,这时就应该使用root权限的libvirtd。
qemu相关的命令:
接下来,使用visual manager工具来模仿一下openstack中Flat模式的网络,因为我在安装openstack的时候,最后一步总是无法ping或ssh上实例,所以这里通过对实例的直接操作来进行演示,其网络拓扑图如下:
(网络是我的一大浩劫,一遇到和网络相关的东西,立马就纠结了,网桥,路由,网关,网段,交换机,nat,dhcp等等,这些东西不实践一下,完全弄不明白他们是干什么的,还好,通过这一段时间的学习,基本上知道了怎么回事,开学之后,一定要补一下网络知识。)
从开始安装,到今晚之前,在这个拓扑图里,一直有一个不懂的东西,就是那个br100!原来它就是传说中的网桥,网卡可以桥接到这个网桥上,来连接两个局域网。网桥工作在物理链路层,相当于一个“低层”的路由器,通过mac帧来识别主机,所以桥接到这个网桥上的网卡是不需要配ip的,这解开了我“多年”的疑惑:为什么那个eth1不需要配ip啊?那两个局域网是怎么连到一块的?原来如此!
还有一个困惑就是多网卡的问题,一直不明白,为什么一台电脑要多个网卡,一个网卡不够用吗?原来真的不够用!一个网卡只能负责接入到一个网络,如果一个电脑要同时接入多个网络的话,那么就得用多个网卡了,也就是说一台电脑可以同时处于不同的局域网中。那这样的话,以前在我脑海中,一台电脑在网络中的身份是唯一的,这样的概念就不成立了啊,一台电脑也可以有多个身份,是由网卡决定的。
好了,吐槽完毕,下面是我模拟的简单的拓扑图:
我的目的就是让host能ping通任意一个虚拟机,虚拟机之间能够相互ping通,并且host能ssh上任意一个虚拟机。
1. 首先要做的就是建立br100网桥,在安装nova时,这个网桥是自动创建的,这里来手动创建,创建网桥使用到的工具是bridge-utils,即使用brctl相关的命令:
eth0的ip可配可不配。这样创建的网桥不是永久的,系统重启的话,就会销毁了。
2. 建立虚拟机:
用visual manager建立3个虚拟机,我用的系统镜像是cflinux-1.0.iso,一个只有几兆的linux发行版,磁盘映像是用virtual manager创建的raw格式的image。
需要注意的是在第5步选择网络的时候,要选“specify shared device name”,然后在下面的输入框中输入刚刚创建好的网桥的名字:br100,如下图:
3. 改虚拟机的ip
不知道为什么visual manager创建的虚拟机初识状态的ip都是一样的,都是10.0.0.2,使用ifconfig命令把3个虚拟机的ip改为:
10.0.0.2 10.0.0.3 10.0.0.4
让他们在同一个网段。
这样,主机可以ping通实例,实例之间也可以相互ping通了,通过ssh root@10.0.0.2也能连上虚拟机了!
还未明白的问题:
每次虚拟机启动之后,用ifconfig在主机上查询网络信息,会看到vnet0, vnet1等这些信息,还有virbr0, virbr1等,后者我知道表示的是一个网段,但是前者是做什么用的呢?是虚拟机的虚拟出来的网卡吗?