虚拟化是云计算时代的基石,它的本质就是将原先的物理设备进行逻辑化,转化成一个文件夹或文件,实现软硬件的解耦。使用虚拟化后,物理服务器转变成一个文件夹或文件,这里面一般会包含两部分,一部分用来记录虚拟机的配置信息,另一部分用来保存用户数据的磁盘文件。使用虚拟化前,每台物理机上只能同时运行一个操作系统,如果在服务器上运行多个主应用程序,那么不同应用之间可能会产生冲突和性能问题。实际上,最佳做法是每个服务器仅运行一个应用程序以避免这些问题,但是这么做的结果是系统硬件资源长时间利用率较低,投入成本高。使用虚拟化后,每台物理机上可以同时运行多个虚拟机,每个虚拟机上又可以运行一个操作系统,硬件资源利用率得到了有效提高。并且由于虚拟化技术实现了软硬件的解耦合,虚拟化可以不受当前服务器的限制,在集群范围内实现业务的在线动态迁移,并且在迁移过程中可以做到业务无中断、用户无感知。按照虚拟化的程度不同,可以将虚拟化分成CPU虚拟化、内存虚拟化和IO虚拟化。
CPU虚拟化技术的通用实现方式是将软件和硬件相互分离,在操作系统与硬件之间加入一个虚拟化的软件层,通过空间上的分割(空分),时间上的分时(时分),以及指令的模拟,将服务器物理资源抽象成逻辑资源,向上层操作系统提供一个与它它原先期待一致的服务器硬件环境。使得上层操作系统可以直接运行在虚拟环境上。并允许具有不同操作系统的多个虚拟机相互隔离,组织故障传递,并发运行在同一台物理机上,从而提供更高的IT资源利用率和更好的分配灵活性。
计算机科学科学家巴特勒·兰普森有句名言,在计算机科学中的任何问题,都可以用加上另一层间接参照来解决。(Any problem in computer science can be solved with another level of indirection)。虚拟化也可以通过加层的方式来解决。CPU虚拟化的这个软件层,叫做VMM,也叫做Hypervisor,常见的Hypervisor软件架构方案分为两类,即Type I型和Type II型。
Type I型也叫裸金属型,指的是那红VMM直接运行在裸机上,使用和管理底层的硬件资源。guest OS对真实硬件资源的访问都要通过VMM来完成,VMM拥有硬件的驱动程序,因为他是底层硬件的直接操作者。
Type II型指VMM之下还有一层宿主操作系统,由于guest os对硬件的访问必须通过宿主操作系统,因而带来了额外的性能开销。好处是可以充分利用宿主操作系统上完备的软件服务来进行管理。
两种类型的Hypervisor示意图如下:
虚拟化技术并非应用层面的技术,而是扎根于ICT产业的基石芯片之中,能力来源于芯片架构设计和指令集设计。从指令集的角度看,虚拟化需要将一些敏感的特权指令,例如修改虚拟机的运行模式或宿主状态的指令,剥夺特权后交给VMM执行,这就要求敏感指令集是特权指令级的一个子集。
框架设计的很好,但是实现起来并没有那样规整,敏感指令集是特权指令级的一个子集这个要求可能不会满足。以X86为例,它的CPUID指令是一个非特权指令,可以在user space执行,以获取CPU相关的配置和能力信息。这显然是一个敏感指令,但却不是特权指令:
The CPUID Explorer Part 1
Feature and Interface Discovery | Microsoft Learn
x86 - Please explain the issue of sensitive instructions in Virtualization and how it is resolved - Stack Overflow
Hypervisor通过VM exit事件监控guest的一举一动,稍微“大”一点的动作(进程切换、读写msr、执行cpuid等)都会在guest触发exit,回到host的handle函数处理,host对guest有绝对的监控和处理的全力,在x86架构上,存在多个虚拟化敏感指令,这些指令在虚拟化环境中具有特殊的行为或被虚拟机监视器(Hypervisor)劫持和处理。以下是一些常见的虚拟化敏感指令:
wget -c https://mirrors.edge.kernel.org/pub/linux/utils/cpu/msr-tools/msr-tools-1.1.2.tar.gz
这些指令在虚拟化环境中具有特殊的行为,虚拟机监视器会拦截和处理这些指令,以提供虚拟化功能、管理虚拟机和处理器状态。普通应用程序通常无法直接执行这些指令,而是通过与虚拟机监视器的交互来间接使用它们。
关于CPUID指令KVM模拟的验证,可以参考如下测试,KVMTOOL虚拟机中启动一个不断执行cpuid指令的应用,在HOST主机中拦截kvm_emulate_cpuid的调用情况,发现确实按照固定周期调用,说明CPUID指令会导致虚拟机退出,是模拟的:
Xen:
KVM:
开始部署前了解下KVM-Qemu-Libvirt-Openstack之间的关系
Qemu
Qemu是一个模拟器,它向Guest OS模拟CPU和其他硬件,Guest OS认为自己和硬件直接打交道,其实是同Qemu模拟出来的硬件打交道,Qemu将这些指令转译给真正的硬件。
由于所有的指令都要从Qemu里面过一手,因而性能较差。
KVM
KVM是linux内核的模块,它需要CPU的支持,采用硬件辅助虚拟化技术Intel-VT,AMD-V,内存的相关如Intel的EPT和AMD的RVI技术,Guest OS的CPU指令不用再经过Qemu转译,直接运行,大大提高了速度,KVM通过/dev/kvm暴露接口,用户态程序可以通过ioctl函数来访问这个接口。
KVM内核模块本身只能提供CPU和内存的虚拟化,所以它必须结合QEMU才能构成一个完成的虚拟化技术,这就是下面要说的qemu-kvm。
qemu-kvm
Qemu将KVM整合进来,通过ioctl调用/dev/kvm接口,将有关CPU指令的部分交由内核模块来做。kvm负责cpu虚拟化+内存虚拟化,实现了cpu和内存的虚拟化,但kvm不能模拟其他设备。qemu模拟IO设备(网卡,磁盘等),kvm加上qemu之后就能实现真正意义上服务器虚拟化。因为用到了上面两个东西,所以称之为qemu-kvm。
Qemu模拟其他的硬件,如Network, Disk,同样会影响这些设备的性能,于是又产生了pass through半虚拟化设备virtio_blk, virtio_net,提高设备性能。
qemu-kvm曾经是kernel社区维护的专门用于KVM的QEMU分支。
在2012年,这个分支并入了主流的QEMU,从此不在需要QEMU-KVM,而是通过在qemu加上 -enable-kvm选项就可以创建kvm guest了。
libvirt
libvirt是目前使用最为广泛的对KVM虚拟机进行管理的工具和API。Libvirtd是一个daemon进程,可以被本地的virsh调用,也可以被远程的virsh调用,Libvirtd调用qemu-kvm操作虚拟机。
Openstack
Openstack不会直接控制qemu-kvm,使用libvirt库去间接控制qemu-kvm。libvirt提供了跨VM平台的功能,它可以控制除了QEMU之外的模拟器,包括vmware, virtualbox, xen等等。
所以为了openstack的跨VM性,openstack只会用libvirt而不直接用qemu-kvm。libvirt还提供了一些高级的功能,例如pool/vol管理。
对VT-x和VT-d的简单介绍
VT-x:
原理:CPU运行有Ring0 ~ Ring3,一些底层操作必须Ring0。如果没有VT-x,虚拟机软件只能到Ring1,那么有些内核级别的东西就必须靠软件模拟,而效率降低。 有了VT-x,相当于多出来一套虚拟机的Ring0 ~ Ring3,这样在虚拟机内的内核请求和虚拟机外的就等于性质上/效率上没有差别了,从而提高效率。
用途:提高虚拟机效率,让虚拟机没有CPU性能的短板(当然还是受限于你CPU本身的能力)。另外,在32位系统上要跑64位虚拟机的话,也必须要VT-x支持。
VT-d:
原理:是一种基于North Bridge北桥芯片(或者按照较新的说法:MCH)的硬件辅助虚拟化技术,通过在北桥中内置提供DMA虚拟化和IRQ虚拟化硬件,实现了新型的I/O虚 拟化方式,Intel VT-d能够在虚拟环境中大大地提升 I/O 的可靠性、灵活性与性能。
用途:运用VT-d技术,虚拟机得以使用直接I/O设备分配方式或者I/O设备共享方式来代替传统的设备模拟/额外设备接口方式,从而大大提升了虚拟化的I/O性能,让虚拟机性能更接近于真实主机。
验证cpu是否支持kvm,vmx是intel的 svm是AMD的,VMX和SVM指的是同一件事,都是虚拟化技术,VMX-virtual machine extension, AMD平台称为SVM-secure virtual machine extension.
$ grep -Eoc '(vmx|svm)' /proc/cpuinfo
如果CPU支持硬件虚拟化,则该命令将输出一个大于零的数字,即CPU核心的数量。否则,如果输出是,0则表示CPU不支持硬件虚拟化。
$ sudo apt update -y
$ sudo apt install cpu-checker -y
$ kvm-ok
kvm-ok会起一个线程去尝试打开/dev/kvm节点,根据返回值判断是否支持kvm.
kvm-ok是shell脚本程序
支持KVM的系统,内核中安装了kvm_intel和kvm两个内核模块。
configuration environment
$ sudo apt install virt-manager qemu qemu-kvm libvirt-bin bridge-utils libvirt-daemon-system libvirt-clients virtinst ssh-askpass-gnome --no-install-recommends
$ sudo addgroup libvirt
$ sudo usermod -aG kvm $USER
$ sudo usermod -aG libvirt $USER
$ systemctl start libvirtd.service
$ systemctl status libvirtd.service
$ systemctl is-active libvirtd
if the result of "systemctl is-active libvirtd" is active, the environment is fine.
launch the virt-manager
$ sudo virt-manager
press the new vm buttons
创建后,将会在/var/lib/libvirt/images目录生成虚拟机磁盘镜像:
go on and then install:
install fine:
after install
and the virt-manger console
below command used to view the vm status.
$ sudo virsh -c qemu:///system list
the virtual manager based on qemu, the proofs are below.
czl@czl-Vostro-3268:~$ sudo fuser -uv /dev/kvm
用户 进程号 权限 命令
/dev/kvm: libvirt-qemu 3641 F.... (libvirt-qemu)qemu-system-x86
czl@czl-Vostro-3268:~$
czl@czl-Vostro-3268:~$ cat /proc/3641/comm
qemu-system-x86
czl@czl-Vostro-3268:~$ cat /proc/3641/cmdline
qemu-system-x86_64-enable-kvm-nameguest=ubuntu22.4,debug-threads=on-S-objectsecret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-1-ubuntu22.4/master-key.aes-machinepc-i440fx-bionic,accel=kvm,usb=off,vmport=off,dump-guest-core=off-cpuBroadwell-noTSX-IBRS-m1024-realtimemlock=off-smp2,sockets=2,cores=1,threads=1-uuid08e5f591-a8df-4e1f-8e9f-7c454fb4bd83-no-user-config-nodefaults-chardevsocket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-1-ubuntu22.4/monitor.sock,server,nowait-monchardev=charmonitor,id=monitor,mode=control-rtcbase=utc,driftfix=slew-globalkvm-pit.lost_tick_policy=delay-no-hpet-no-shutdown-globalPIIX4_PM.disable_s3=1-globalPIIX4_PM.disable_s4=1-bootstrict=on-deviceich9-usb-ehci1,id=usb,bus=pci.0,addr=0x5.0x7-deviceich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x5-deviceich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x5.0x1-deviceich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x5.0x2-devicevirtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x6-drivefile=/var/lib/libvirt/images/ubuntu22.4.qcow2,format=qcow2,if=none,id=drive-ide0-0-0-deviceide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1-driveif=none,id=drive-ide0-0-1,readonly=on-deviceide-cd,bus=ide.0,unit=1,drive=drive-ide0-0-1,id=ide0-0-1-netdevtap,fd=26,id=hostnet0-devicertl8139,netdev=hostnet0,id=net0,mac=52:54:00:76:da:e5,bus=pci.0,addr=0x3-chardevpty,id=charserial0-deviceisa-serial,chardev=charserial0,id=serial0-chardevspicevmc,id=charchannel0,name=vdagent-devicevirtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.spice.0-spiceport=5900,addr=127.0.0.1,disable-ticketing,image-compression=off,seamless-migration=on-deviceqx