Dive into the VM
这次来给大家分享的是我们 Cloud Infra 团队的 Gray,是的这名字跟 Gay 一样充满着搅基的味道,他主要是接我这个老父亲的盘,把调度和编排发扬光大,顺便把我们自己的 VM 引擎给完善。VM 是我们 Infra 团队最重要的产品之一,配合 Eru 的混合调度编排,我们把 VM 当做类似于 Container 一样的容器在使用,也提供裸机形式的支持,这次他将为大家简单的介绍一下 VM 技术。
QEMU
QEMU (Quick Emulator) 提供了硬件和处理器虚拟化, 它跑在用户态, 无需内核。首先我们看看如何在 Ubuntu16 上安装它。至于为什么是 Ubuntu 16,那又是另外一个故事了。
> 安装
安装 QEMU 的话其实很简单,只要执行这 2 句就行了。
apt-get updateapt-get install qemu
然后检查一下
dpkg --list | grep qemu
这样就算安装完 QEMU 了,简不简单,厉不厉害?
> 拉个镜像
接着我们要给拉个镜像下来,QEMU 支持多种虚拟机镜像,这里我们用 CentOS 7 的 QCOW2 镜像做例子,至于什么是 QCOW2 这里就不深究了。
curl -OL https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2
下载时间不等,取决于你的网络,下载完成后我们可以用这个命令来检查下:
qemu-img info CentOS-7-x86_64-GenericCloud.qcow2
这个命令正常的话会输出 format qcow2, virtual size 8G,至于不正常是什么样子,呃……我也么见到过。
> 第一个虚拟机
然后当然是启动自己的第一个虚拟机,用这个命令即可:
qemu-system-x86_64 -name zc -cpu Nehalem -m 1024 -drive format=qcow2,file=CentOS-7-x86_64-GenericCloud.qcow2 -vnc 0.0.0.0:0 -daemonize
这里我们稍微解释下这个指令都做了啥幺蛾子。
-cpu Nehalem 指定 Nahalem Intel Core i7, -m 1024 指定 1G 内存, -drive 指定刚才下载的镜像, -vnc 绑定一个端口让你访问虚拟机。如果没有最后这个 VNC 又没配置网络的话,这个虚拟机就真的只能看不能用了。
检查一下:
pgrep -la qemu
如果要停止的话直接
pkill -9 qemu
简单粗暴和高效。
> 登录
我们需要先下载一个 VNC Viewer,然后连上宿主机的 IP:5900。这时候你会发现一个问题,账号密码到底是个球儿?
那么 改镜像密码, 先安装:
apt install -y libguestfs-tools
然后设置 root 密码为 root:
virt-customize -a CentOS-7-x86_64-GenericCloud.qcow2 --root-password password:root
结果这个命令报错了 virt-customize: error: libguestfs error: guestfs_launch failed 这是符合预期的,因 为在这个镜像上正跑着一个 VM 呢,关掉先。 改好密码后最后重新启动一个虚拟机,VNC 连接登录,然后你就登陆进了这台 VM。
进去后检查一下 cpu, memory, disk
free -hlscpudf -h
应该是和我们指定的一样的, 分别是 1G, 1C, 8G。
> KVM
KVM (Kernel-based Virtual Machine) 和 QEMU 最大的不同是它跑在内核态, 所以需要内核支持。
由于需要硬件支持所以首先要检查 cpu 是否支持 KVM 虚拟化:
lscpu | grep vmx | uniq
然后检查内核模块:
modprobe kvmlsmod | grep kvm
那么都支持的话,我们来创建一个基于 KVM 的虚拟机:
qemu-system-x86_64 -name zc -cpu Nehalem -m 1024 -drive format=qcow2,file=CentOS-7-x86_64-GenericCloud.qcow2 -vnc 0.0.0.0:0 -enable-kvm -daemonize
注意新增的 -enable-kvm 就是说这个鸡儿 VM,是 KVM 启动的。
另一种做法是下载 kvm 二进制:
apt install qemu-kvmkvm -name zc -cpu Nehalem -m 1024 -drive format=qcow2,file=CentOS-7-x86_64-GenericCloud.qcow2 -vnc 0.0.0.0:0 -daemonize
不过本质上 kvm 二进制只是把 qemu-system-x86_64 包了一下, pgrep -la qemu 可以看到两种拉起来的进程是一样的。
libvirt
libvirt 对不同的虚拟化技术提供了统一的上层接口, 比方说管理 QEMU/KVM, XEN, LXC。所以 libvirt 是目前市面上所有 VM 引擎的底层支持库没有之一,你所知道的 kubevirt virtlet 以及我们自己做的 yavirtd 都是基于它。
> 安装
libvirt 的安装同样这么简单:
apt install -y libvirt0-dbg/xenial-updatesapt install -y libvirt-bin
然后 libvirtd daemon 就起来了,可以通过 systemctl 查看状态:
systemctl status libvirtd
我们还可以看看配置:
cat /etc/libvirt/libvirtd.conf | grep -v '^#\|^$'
很多东西在 /etc/libvirt/ 里, 猫一眼
# ls -lF /etc/libvirt/total 84drwxr-xr-x 2 root root 4096 Mar 11 21:52 hooks/-rw-r--r-- 1 root root 450 Mar 11 21:52 libvirt-admin.conf-rw-r--r-- 1 root root 547 Mar 11 21:52 libvirt.conf-rw-r--r-- 1 root root 15276 Mar 11 21:52 libvirtd.conf-rw-r--r-- 1 root root 834 Mar 11 21:52 libxl.conf-rw-r--r-- 1 root root 2169 Mar 11 21:52 libxl-lockd.conf-rw-r--r-- 1 root root 1235 Mar 11 21:52 lxc.confdrwxr-xr-x 2 root root 4096 May 19 22:23 nwfilter/drwxr-xr-x 3 root root 4096 May 19 22:23 qemu/-rw------- 1 root root 19536 Mar 11 21:52 qemu.conf-rw-r--r-- 1 root root 2169 Mar 11 21:52 qemu-lockd.conf-rw-r--r-- 1 root root 2134 Mar 11 21:52 virtlockd.conf-rw-r--r-- 1 root root 1802 Mar 11 21:52 virtlogd.conf-rw-r--r-- 1 root root 1217 Mar 11 21:52 virt-login-shell.conf
其中 libvirt.conf 是给 libvirt client 用的, libvirtd.conf 是给 libvirtd 用的, qemu.conf 是 libvirtd 驾驭 QEMU/KVM 的配置, qemu/ 里是创建的 VM 配置。
> 第二个虚拟机
先安装工具
apt install virtinst/xenial-updates
然后生成 libvirt domain xml
virt-install --name v2 --ram 1024 --disk path=CentOS-7-x86_64-GenericCloud.qcow2,format=qcow2 --print-xml > v2.xml
看一下 v2.xml
<domain type="kvm"><name>v2</name><uuid>27144641-23ff-42ad-9bd5-6ab0a4ff6ec7</uuid><memory>1048576</memory><currentMemory>1048576</currentMemory><vcpu>1</vcpu><os><type arch="x86_64">hvm</type><boot dev="hd"/></os><features><acpi/><apic/></features><cpu mode="custom" match="exact"><model>Broadwell-IBRS</model></cpu><clock offset="utc"><timer name="rtc" tickpolicy="catchup"/><timer name="pit" tickpolicy="delay"/><timer name="hpet" present="no"/></clock><on_poweroff>destroy</on_poweroff><on_reboot>restart</on_reboot><on_crash>restart</on_crash><pm><suspend-to-mem enabled="no"/><suspend-to-disk enabled="no"/></pm><devices><emulator>/usr/bin/kvm-spice</emulator><disk type="file" device="disk"><driver name="qemu" type="qcow2"/><source file="/root/CentOS-7-x86_64-GenericCloud.qcow2"/><target dev="hda" bus="ide"/></disk><controller type="usb" index="0" model="ich9-ehci1"/><controller type="usb" index="0" model="ich9-uhci1"><master startport="0"/></controller><controller type="usb" index="0" model="ich9-uhci2"><master startport="2"/></controller><controller type="usb" index="0" model="ich9-uhci3"><master startport="4"/></controller><interface type="network"><source network="default"/><mac address="52:54:00:d5:af:0b"/></interface><input type="mouse" bus="ps2"/><graphics type="spice" port="-1" tlsPort="-1" autoport="yes"><image compression="off"/></graphics><console type="pty"/><channel type="spicevmc"><target type="virtio" name="com.redhat.spice.0"/></channel><sound model="ich6"/><video><model type="qxl"/></video><redirdev bus="usb" type="spicevmc"/><redirdev bus="usb" type="spicevmc"/></devices></domain>
关注里面的 disk, vcpu, memory 的值可以直接编辑文件修改,但不会立即生效。
接着可以用 libvirt client virsh 来检查一下,并启动这个 VM。
virsh define v2.xmlvirsh list --allvirsh start v2
是的,它又报错了。 error: Cannot access storage file '/root/CentOS-7-x86_64-GenericCloud.qcow2' (as uid:64055, gid:121): Permission denied , 看一下 uid 64055 是用户 libvirt-qemu, 读不了 /root, 所以得把镜像放在 /opt/qemu-img/ 下:
mkdir /opt/qemu-img/cp /root/CentOS-7-x86_64-GenericCloud.qcow2 /opt/qemu-img/v2.qcow2
改下 xml 里的镜像路径到 /opt/qemu-img/v2.qcow2, 重来一遍:
virsh undefine v2virsh define v2.xmlvirsh start v2virsh list
成功!
创建好的 vm 的 xml 都在 /etc/libvirt/qemu/ 里。通过 pgrep qemu -la 能看到启动的进程依然是 qemu-system-x86_64 那一套, 只不过 flag 就多了……
29915 qemu-system-x86_64 -enable-kvm -name v2 -S -machine pc-i440fx-xenial,accel=kvm,usb=off -cpu Broadwell-IBRS -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid ab376ac8-20bf-43cb-abdd-a43cfb472f2f -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-v2/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=discard -no-hpet -no-shutdown -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -boot strict=on -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x6.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x6 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x6.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x6.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x5 -drive file=/opt/qemu-img/v2.qcow2,format=qcow2,if=none,id=drive-ide0-0-0 -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=26,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:d8:a3:e7,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev spicevmc,id=charchannel0,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.spice.0 -spice port=5900,addr=127.0.0.1,disable-ticketing,image-compression=off,seamless-migration=on -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vgamem_mb=16,bus=pci.0,addr=0x2 -device intel-hda,id=sound0,bus=pci.0,addr=0x4 -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0 -chardev spicevmc,id=charredir0,name=usbredir -device usb-redir,chardev=charredir0,id=redir0 -chardev spicevmc,id=charredir1,name=usbredir -device usb-redir,chardev=charredir1,id=redir1 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x7 -msg timestamp=on
> 登录
可以注意到,第二个基于 libvirt 的虚拟机我们并没有指定 VNC 服务,因此需要用到 virsh 的 console 指令。
virsh console v2
输入密码 root,成功进入 VM。
> 管理内存
那么老师还能不能给力点,当然可以。不如我们就改下内存吧。
先查看当前的内存状态:
xmllint --xpath 'string(//memory)' /etc/libvirt/qemu/v2.xmlxmllint --xpath 'string(//currentMemory)' /etc/libvirt/qemu/v2.xml
输出是一样的 1048576, currentMemory 必须比 memory 小, 出现两个内存值的目的是让 VM 可以动态的调整内存。
比如这样:
virsh setmem v2 --size 500000
这时候通过 console 进去 free -m 就会发现内存减少了。
同样可以把 memory 调大, 以增加 currentMemory, 但这次就需要先停机:
virsh destroy v2virsh setmaxmem v2 --size 2097152virsh start v2virsh setmem v2 --size 2097152
通过 virsh setmem 命令修改资源是不会同步修改到 xml 里的, 因此需要固化配置的话还是需要手动的修改配置文件。
> 管理 disk
那么老师还能不能再给力点,比如改个状态,好吧这其实也是可以的。在镜像里 df -h 能看到:
/dev/sda1 8.0G 850M 7.2G 11% /
这里的 8G 是镜像里定义的:
qemu-img info /opt/qemu-img/v2.qcow2
要修改的话:
qemu-img resize -f qcow2 /opt/qemu-img/v2.qcow2 +2GB
重启这个 VM:
virsh destroy v2virsh start v2
然后 fdisk 传统技能走一套:
fdisk -l /dev/sda
看到已经是 10G 了,接下来是传统杂技时间, fdisk /dev/sda 分区,不表演,得加钱。
> 管理 CPU
那老湿我想改 CPU 怎么办,直接改 xml:
virsh edit v2
修改 vcpu 的值为 2:
<vcpu placement='static'>2</vcpu>
然后重启:
virsh destroy v2 && virsh start v2
再进去检查 lscpu 可以看到已经是 2 核
> 管理网络
最后我们玩点传统而又高端点的,如何管理 VM 的网络,这里以网桥+TUN/TAP 举例。
先创建 TUN/TAP:
ip tuntap a dev tap0 mode tapip l s tap0 up
然后创建网桥:
ip l a name bri0 type bridgeip l s bri0 upip a a 10.16.0.1/16 dev bri0
接着把 tap0 连到 bri0 :
ip l s tap0 master bri0
最后创建 KVM:
kvm -name v3 -cpu Nehalem -m 1024 -drive format=qcow2,file=/opt/qemu-img/v3.qcow2 -vnc 0.0.0.0:1 -net tap,ifname=tap0,script=/bin/true -net nic,macaddr=c6:3a:0d:2a:2e:b1 -daemonize
通过 vnc 登录上去, 在 vm 里配置 ip 和路由:
ip a a 10.16.0.2 dev eth0ip r a default dev eth0
这时候你就会发现,VM 和 Host 可以互相 ping 通了。
尾声
通过这篇简单的 Dive into VM 我们简略了介绍了下裸 QEMU 和 KVM 的安装和操作,有了 libvirt 后其实 VM 早已没有早些年那么复杂难用,完全可以和容器一样包装一下用起来。当然了诸如热更新啊,网络啊依然需要更加深入 VM 体系和结合已有的基础设施,这也是各家公有云的看家本领,这就各显神通了。