3.3.1 Libvirt虚拟机运行时关键技术实现
3.3.1.1 驱动配置解析及加载
将nomad-driver-libvirt驱动插件项目编译放入驱动加载文件夹下。如图3.8所示,在Nomad Client配置中新增并解析libvirt插件配置。libvirt公共API将其实现委托给一个或多个内部驱动程序,具体取决于初始化库时传递的连接URI,例如qemu:///system。如果libvirt守护程序可用,且有一个虚拟机监控程序处于活动状态,通常将有一个网络和存储驱动程序处于活动状态。
图3.8 nomad-driver-libvirt驱动插件配置
如图3.9所示,从Nomad Client日志中驱动插件以加载,Fingerprint健康检查正常。
图3.9 nomad-driver-libvirt驱动插件加载及Fingerprint
3.3.1.2 虚拟机生命周期管理
虚拟机运行时作业负载中包含了虚拟机任务组,任务组中包含的任务的生命周期管理需要实现运行时驱动接口,而更底层封装的是虚拟机的生命周期。
Libvirt能够控制域即虚拟机的整个生命周期。域可以在它的整个生命周期的几个状态之间进行转换:
(1)Undefined是一个基本的状态,任何没有定义或者没有建立的域的状态。
(2)Defined是域已定义但是没有运行的状态,这种状态也可以被描述为停止。
(3)Running是已经定义并运行在一个Hypervisor上面的域的状态。
(4)Paused是形容一个域系统从运行转换为暂停的状态。它的内存镜像已经被暂时地存储,它可以恢复到运行状态。
(5)Saved可以将一个域持久化,并再次恢复。
图3.10 域生命周期的状态图
域的生命周期比较详细,在上一层Libvirt虚拟机运行时包装里,本文减少了一些状态,提供了从配置文件创建虚拟机、停止虚拟机、删除虚拟机、虚拟机状态查询,以及事件监听和数据采集等接口抽象,如图3.11所示。
图3.11 虚拟机管理抽象接口
3.3.1.3 虚拟机任务配置
虚拟机任务配置由Libvirt虚拟机运行时定义和解析,沿用HCL格式,嵌套在原Nomad作业定义的task节部分。如表3.1所示,需要说明例如task.driver是指task域中的driver字段。
表3.1 虚拟机任务配置
| 字段 | 值 |
|---|---|
| task.driver | libvirt,字符串 |
| task.config.name | 任务名,字符串,例如centostask |
| task.config.memory.value | 虚拟机内存大小,整形,例如1 |
| task.config.memory.unit | 内存描述的单位,字符串,有gib、mib、kib、b |
| task.config.vcpu | 虚拟机的虚拟CPU,整形,例如1 |
| task.config.disks | 磁盘存储,列表类型 |
| task.config.disks[0].device | 磁盘设备,字符串,有disk、lun、floppy、cdrom |
| task.config.disks[0].type | 文件类型,字符串,device为disk时有file、block |
| task.config.disks[0].source | 虚拟机镜像地址,字符串,例如/var/lib/libvirt/images/centos.qcow2 |
| task.config.disks[0].target_bus | 总线,字符串,有virtio、sata、scsi、fdc |
| task.config.machine | 虚拟的硬件机器类型,字符串,例如pc-i440fx-2.12 |
| task.config.interfaces | 列表类型,网络接口配置,实现了设备透传 |
| task.config.interfaces[0].name | 指定虚拟机内部网络接口名,字符串,例如eth0 |
| task.config.interfaces[0].model | 网络适配器类型,字符串,例如virtio、e1000、rtl8139 |
| task.config.interfaces[0].interface_binding_method | 网卡绑定方式,字符串,有virtio、slirp、sriov、bridge、masquerade、network |
| task.config.interfaces[0].pci_address | 网卡设备透传时指定的PCI地址,字符串,如0000:00:1c.3 |
| task.config.interfaces[0].mac_address | 指定网卡MAC地址,字符串,例如52:54:00:a5:ef:66 |
| task.config.interfaces[0].source_name | 字符串,例如NAT网络设置default,Bridge网络指定网桥br0 |
作业提交到Nomad Server后会分配到具有Libvirt虚拟机运行时的Nomad Client执行作业,以上HCL定义的作业再转交给Libvirt虚拟机运行时解析,过程中被转换为XML格式发给Libvirt守护进程具体执行。
3.3.1.4 虚拟机运行时网络管理
Libvirt虚拟机运行时设计上支持NAT和Bridge两种网络方式。
KVM安装后的默认网络是NAT方式,宿主机上会有一个virbr0虚拟接口(即NAT方式里面的default虚拟网络),并有一块virbr0-nic的虚拟网卡,也是一个switch和bridge,负责把内容分发到各虚拟机。virbr0是一个网桥,默认接收所有到网络192.168.122.0/24的内容。virbr0并未直接绑定到实际的物理网卡,数据包经过virbr0,进行网络地址解析,IP包转发后从实际的物理网络设备出去。在NAT模式下,需要在宿主机上运行一个DHCP服务器给内网的机器分配IP地址,可以使用dnsmasq工具实现。
NAT网络实现,如图3.12所示,虚拟接口和物理接口之间没有连接关系,所以虚拟机只能在通过虚拟的网络访问外部世界,无法从网络上定位和访问虚拟主机。
图3.12 nomad-driver-libvirt NAT网络模型
以创建虚拟机为例,网络相关的创建流程:
1. 修改内核参数允许IP数据包转发。
2. 定义NAT虚拟网络,包含IP段范围。
3. 启动NAT网络,修改iptables规则。主机系统负责进行SNAT。
4. DNS/DHCP 的实现,本地宿主机通过启动一个dnsmasq来负责管理。
5. 宿主机创建iptables规则负责进行SNAT。
6. 创建虚拟机,指定虚拟网络类型virtio,并选用上述NAT网络。
7. 虚拟机启动,采取DHCP协议向dnsmasq请求动态配置IP。
Bridge网络实现,如图3.13所示,创建一个桥接接口br0,将这个网桥绑定到物理网卡eth0上并分配一个对外的地址,此时br0就成为了交换机设备,再将对应的虚拟机网络设备绑定到该虚拟网桥的一个端口上,在物理网卡和虚拟网络接口之间传递数据。
图3.13 nomad-driver-libvirt桥接网络模型
以创建虚拟机为例,网络相关的创建流程和上述NAT模式类似,略有区别:
1. 编辑修改网络设备脚本文件,创建网桥设备br0。
2. 为网桥启用生成树协议。
3. 为网桥br0设置IP获取式动态或静态配置。
4. 修改服务启动顺序,桥接方式虚拟网卡需要在宿主机网卡启动之前启动。
5. 修改网卡设备eth0,禁用NetworkManager服务,该服务不支持桥接。
6. 重启网络服务
7. 创建虚拟机,DHCP动态配置IP。
以上有关Libvirt虚拟机运行时创建虚机后动态获取IP,需要在虚拟机镜像中提前安装好qemu-guest-agent,实现是通过使用QEMU Guest Agent协议与虚拟机中的该客户端通信,执行命令guest-network-get-interfaces,来获取客户机的虚拟机IP地址、MAC地址、子网掩码等信息。获取虚拟机IP之后,用户可以通过SSH登陆进行操作。