在CentOS中部署KVM

KVM hypervisor和VM-extensions

早期的KVM技术提供的是类似VMWare的完全虚拟化,对于guest操作系统模拟成完整的物理服务器。而半虚拟化,如Xen,称为Paravirtualization则提供更高的性能,但需要修改guest操作系统。完全虚拟化可以运行没有修改的guest系统如Windows。为了能够使用完全虚拟化,需要CPU的虚拟化扩展或使用模拟器。

要确定物理主机是否支持VM扩展,在x86平台,如AMD-V或Intel VT-X

egrep -c '(vmx|svm)' /proc/cpuinfo

如果输出是0,则表示没有找到CPU中vmxsvm的标记。在这种没有VM扩展的CPU环境下,则需要使用KVM结合QEMU-emulation,则性能较差。本文的配置是使用物理主机BIOS和CPU都支持激活VM-extension。

安装KVM

如果需要图形界面,可以结合X11 forwarding来使用virt-manager,即同时安装virt-manager(图形管理程序),xauth(X11访问认证)和dejavu-lgc-sans-fonts(运行X11的基本字体)

sudo yum install libvirt virt-install qemu-kvm virt-manager xauth dejavu-lgc-sans-fonts

也可以安装纯字符终端管理工具

sudo yum install libvirt virt-install qemu-kvm

默认安装会启用一个NAT模式的bridgevirbr0

启动激活libvirtd服务

systemctl enable libvirtd && systemctl start libvirtd

安装OS

guest OS磁盘

默认的VM创建的磁盘在/var/lib/libvirt/images,需要确保有足够空间存放磁盘镜像。

KVM支持多种VM镜像格式,本文案例采用raw文件格式,即创建10GB的vm磁盘会实际在物理主机磁盘上生成10GB的磁盘文件。

guest OS网络

默认VM只访问一个NAT模式的网桥网络,采用私有网段192.168.122.0,如果要使用bridge模式对外直接提供服务访问(虚拟机直接使用网络中的IP地址连接局域网)可创立独立的bridge网桥。

Firewalld

在RHEL 6中,默认的包过滤和转发服务称为iptables,而在RHEL 7,默认是firewalld,提供了和iptables相同的的包过滤和转发功能,但是实现了动态规则以及增强的功能如network zone,可以实现更为伸缩性的网络结构。

注意在RHEL7中依然有iptables工具,不过这个工具是通过使用firewalld来和内核包过滤器通讯。

SELinux

如果使用SELinux增强模式,常见问题是要注意不能使用非默认的目录来创建VM镜像。如果你要使用非/var/lib/libvirt/images目录,则要创建目录的安全上下文。例如,选择/vm-images来存放镜像:

  • 创建目录

mkdir /vm-images
  • 安装policycoreutils-python软件包(包含了segmange这个SELinux工具)

yum -y install policycoreutils-python
  • 设置目录的安全上下文以及目录下的内容:

semange fcontext --add -t virt_image_t '/vm-images(/.*)?'
  • 验证

semanage fcontext -l | grep virt_image_t

显示输出类似

/var/lib/imagefactory/images(/.*)? all files  system_u:object_r:virt_image_t:s0
/var/lib/libvirt/images(/.*)?      all files  system_u:object_r:virt_image_t:s0
/vm-images(/.*)?                   all files  system_u:object_r:virt_image_t:s0
  • 恢复安全上下文

restorecon -R -v /vm-images

验证上下文更改:

ls -aZ /vm-images
  • 如果需要将 /vm-images 作为samba或者NFS共享,则需要设置SELinux Booleans

setsebool -P virt_use_samba 1
setsebool -P virt_use_nfs 1

创建VM

  • 举例:安装CentOS 7操作系统

使用virt-install创建,这个工具分为交互和非交互模式,以下命令使用非交互方式创建CentOS 7 x64虚拟机,名字是vm1使用1个Virtual CPU,1G内存和10GB磁盘

virt-install \
  --network bridge:virbr0 \
  --name centos7 \
  --ram=1024 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/centos7.img,size=10 \
  --graphics none \
  --location=http://mirrors.163.com/centos/7/os/x86_64/ \
  --extra-args="console=tty0 console=ttyS0,115200"

以下配置更为精确(指定qcow2磁盘,并指定os-type):

virt-install \
  --network bridge:virbr0 \
  --name centos7 \
  --ram=2048 \
  --vcpus=1 \
  --os-type=centos7.0 \
  --disk path=/var/lib/libvirt/images/centos7.qcow2,format=qcow2,bus=virtio,cache=none,size=16 \
  --graphics none \
  --location=http://mirrors.163.com/centos/7/os/x86_64/ \
  --extra-args="console=tty0 console=ttyS0,115200"

以下命令增加了磁盘类型qcow2,推荐使用:

virt-install \
  --network bridge:virbr0 \
  --name centos7 \
  --ram=4096 \
  --vcpus=2 \
  --disk path=/var/lib/libvirt/images/centos7.qcow2,format=qcow2,bus=virtio,cache=none,size=16 \
  --graphics none \
  --location=http://mirrors.163.com/centos/7/os/x86_64/ \
  --extra-args="console=tty0 console=ttyS0,115200"
   virt-install \
     --network bridge:virbr0 \
     --name ubuntu18.04 \
     --ram=4096 \
     --vcpus=2 \
     --os-type=ubuntu18.04 \
     --disk path=/var/lib/libvirt/images/ubuntu18.04.qcow2,format=qcow2,bus=virtio,cache=none,size=16 \
     --graphics none \
     --location=http://mirrors.163.com/ubuntu/dists/bionic/main/installer-amd64/ \
     --extra-args="console=tty0 console=ttyS0,115200"
  • --graphics none 这个参数表示不使用VNC来访问VM的控制台,而是使用VM串口的字符控制台。如果希望使用X window的图形界面来安装VM操作系统,则可以忽略这个参数

  • --location=http://mirrors.163.com/centos/7/os/x86_64/ 这个是指定通过网络的CentOS 7安装目录进行安装。如果你使用本地的iso安装,可以修改成 --cdrom /root/CentOS-7-x86_64-DVD-1511.iso

  • --extra-args="console=tty0 console=ttyS0,115200" 这个extra-args是传递给OS installer的内核启动参数(注意:这个extra-args参数只能用于--location)。这里因为需要连接到VM的串口,所以要传递内核对应参数启动串口。此外,可以指定kickstart文件,这样就可以不用交互而自动完成安装,如

--extra-args="ks=http://my.server.com/pub/ks.cfg console=tty0 console=ttyS0,115200"

早期发型版本使用上述串口安装总有安装问题(例如安装初始化控制台无输出),不过最近测试了一下 CentOS 7.5.1804 则可以正常工作。安装过程完全采用字符终端,操作有些繁琐,但是对虚拟机内存要求可以很低(图形界面安装至少需要1G以上内存)。

上述安装是通过 virsh console 连接到虚拟机的串口控制台实现的,安装完成后,需要detach断开串口控制台: CTRL+Shift+] ,这就可以返回host主机的控制台。

测试CentOS 7早期版本遇到一个奇怪问题,命令使用--graphics vnc虽然可以用vnc连接,但是到了Started D-Bus System Message Bus之后字符界面停止了。改为直接通过下载的iso镜像安装初始操作系统

virt-install \
  --network bridge:virbr0 \
  --name centos7 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/centos7.img,size=10 \
  --graphics vnc \
  --cdrom=/var/lib/libvirt/images/CentOS-7-x86_64-DVD-1511.iso

使用现有磁盘镜像创建虚拟机

  • virt-install也支持从现有存在的磁盘镜像创建KVM VM,使用--import参数:

virt-install -n vmname -r 2048 --os-type=windows   --os-variant=win7 \
--disk /kvm/images/disk/vmname_boot.img,device=disk,bus=virtio \
--network bridge=virbr0,model=virtio --vnc --noautoconsole --import

–disk /kvm/images/disk/vmname_boot.img,device=disk,bus=virtio是一个完整定义磁盘镜像路径以及通过,分隔的磁盘参数选项。注意,使用virtio速度最快但是需要确保windows安装驱动,并且太陈旧的Linux版本不支持。

-w bridge=virbr0,model=virtio 连接virbr0虚拟网桥,并使用virtio驱动以获得更好网络性能。如果操作系统不支持,则使用e1000或者rtl8139

--noautoconsole 安装不会自动代开virt-viewer来查看控制台已完成安装。这对远程使用SSH系统有用。

  • 以前安装过的dev7.img镜像重新启用

virt-install \
  --network bridge:virbr0 \
  --name dev7 \
  --os-type=linux \
  --os-variant=rhel7.3 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/dev7.img,device=disk,bus=virtio \
  --network bridge=virbr0,model=virtio \
  --graphics vnc --noautoconsole --import
  • 多网卡并且使用以前安装的dev7.img镜像启动案例:

virt-install \
  --name dev7 \
  --os-type=linux \
  --os-variant=rhel7.3 \
  --ram=8192 \
  --vcpus=4 \
  --disk path=/var/lib/libvirt/images/dev7.img,device=disk,bus=virtio \
  --network bridge=virbr0,model=virtio \
  --network bridge=vr0,model=virtio \
  --graphics vnc --noautoconsole --import
  • 多磁盘的安装启动案例

virt-install -n vmname -r 2048 --os-type=windows --os-variant=win7 \
--disk /kvm/images/disk/vmname_boot.img,device=disk,bus=virtio \
--disk /kvm/images/disk/vmname_data_1.img,device=disk,bus=virtio \
-w bridge=virbr0,model=virtio --vnc --noautoconsole --import
  • LVM磁盘镜像

virt-install -n vmname -r 2048 --os-type=windows --os-variant=win7 \
--disk /dev/vg_name/lv_name,device=disk,bus=virtio \
-w bridge=virbr0,model=virtio --vnc --noautoconsole --import
  • 非virtio(使用IDE和e1000网卡模拟)

virt-install -n vmname -r 2048 --os-type=windows --os-variant=win7 \
--disk /kvm/images/disk/vmname_boot.img,device=disk,bus=ide \
-w bridge=virbr0,model=e1000 --vnc --noautoconsole --import

一些其他安装案例

virt-install \
  --network bridge:virbr0 \
  --name centos6 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/centos6.img,size=10 \
  --graphics vnc \
  --cdrom=/var/lib/libvirt/images/CentOS-6.9-x86_64-netinstall.iso
virt-install \
  --network bridge:virbr0 \
  --name centos5 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/centos5.img,size=10 \
  --graphics vnc \
  --os-type=linux --os-variant=rhel5.11 \
  --cdrom=/var/lib/libvirt/images/CentOS-5.11-x86_64-netinstall.iso
virt-install \
  --network bridge:virbr0 \
  --name freebsd10 \
  --os-type=freebsd --os-variant=freebsd10.1 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/freebsd10.img,size=10 \
  --graphics vnc \
  --cdrom=/var/lib/libvirt/images/FreeBSD-10.3-RELEASE-amd64-disc1.iso
virt-install \
  --network bridge:virbr0 \
  --name ubuntu16 \
  --os-type=ubuntu --os-variant=ubuntu16.04 \
  --ram=2048 \
  --vcpus=1 \
  --disk path=/var/lib/libvirt/images/ubuntu16.img,size=10 \
  --graphics vnc \
  --cdrom=/var/lib/libvirt/images/ubuntu-16.10-server-amd64.iso
  • 举例:安装windows 2012操作系统

virt-install \
   --name=win2012 \
   --os-type=windows \
   --network bridge:virbr0 \
   --disk path=/var/lib/libvirt/images/win2012.img,size=10 \
   --cdrom=/root/win2012.iso \
   --graphics vnc --ram=2028 \
   --vcpus=1

提示报错无法打开image文件

ERROR    internal error: process exited while connecting to monitor: 2016-10-10T16:56:22.361462Z qemu-kvm: -drive file=/root/win2012.iso,if=none,id=drive-ide0-0-1,readonly=on,format=raw: could not open disk image /root/win2012.iso: Could not open '/root/win2012.iso': Permission denied

这个报错应该就是和前面提到的默认安全上下文要求image在指定目录下,所以将iso文件移动到/var/lib/libvirt/images,然后修改执行命令

virt-install \
   --name=win2012 \
   --os-type=windows \
   --network bridge:virbr0 \
   --disk path=/var/lib/libvirt/images/win2012.img,size=16 \
   --cdrom=/var/lib/libvirt/images/win2012.iso \
   --graphics vnc --ram=2028 \
   --vcpus=1
  • 使用virtio驱动方式(paravirtual)安装Windows

virt-install \
   --name=win2012desktop \
   --os-type=windows --os-variant=win2k12r2 \
   --boot cdrom,hd \
   --network=default,model=virtio \
   --disk path=/var/lib/libvirt/images/win2016desktop.img,size=16,format=qcow2,bus=virtio,cache=none \
   --disk device=cdrom,path=/var/lib/libvirt/images/win2012.iso \
   --disk device=cdrom,path=/var/lib/libvirt/images/virtio-win.iso \
   --boot cdrom,hd \
   --graphics vnc --ram=2048 \
   --vcpus=2

osinfo-query os可以获得--os-variant所有支持的参数(参考man virt-install

当前virtio-win.iso驱动签名在Windows 2016无法通过,需要先安装非virtio模式,安装完成后,关闭驱动签名功能重启系统,并增加virtio模式磁盘,使得系统能够安装virtio驱动 - KVM环境安装Windows virtio驱动

参考:Creating guests with the virt-install command

virt-install --connect qemu:///system \
    --name win7vnc --ram 2048 --vcpus=2 --cpuset=auto \
    --disk path=win7.img,bus=virtio,size=100,format=qcow2 \
    --network=network=default,model=virtio,mac=RANDOM \
    --graphics vnc,port=5900
    --disk device=cdrom,path=../../isos/virtio-win-0.1-81.iso \
    --disk device=cdrom,path=../../isos/win7_sp1_ult_64bit/Windows\ 7\ SP1\ Ultimate\ \(64\ Bit\).iso \
    --os-type=windows --os-variant=win7 --boot cdrom,hd

参考 windows 7 as kvm guest installation with virtio drivers - detected virtio scsi disk shows wrong capacity

VNC访问

VNC监听

注意:默认的vnc端口只允许本地访问,在启动服务的命令中有 -vnc 127.0.0.1:0,可以检查如下

sudo virsh vncdisplay win2012

显示是本地端口监听

127.0.0.1:0

VNC客户端连接

经过反复测试,我发现在Mac OS X平台上,很多vnc viewer客户端都不能正常连接到qemu提供的VNC界面,折腾了很长时间。

最后发现,开源的 Chicken 是最好的VNC客户端,可以支持访问VNC界面。

修改VNC监听网络接口

如果要监听所有端口,可以修改 /etc/libvirt/qemu.conf

# VNC is configured to listen on 127.0.0.1 by default.
# To make it listen on all public interfaces, uncomment
# this next option.
#
# NB, strong recommendation to enable TLS + x509 certificate
# verification when allowing public access
#
vnc_listen = "0.0.0.0"

然后重启libvirtd服务

systemctl restart libvirtd

以上参考 virt-install ignoring vnc port/listen?

注意,默认开启的iptables防火墙需要开启端口访问,例如:

iptables -A INPUT -p tcp --dport 5900 -j ACCEPT

CentOS 7开始推荐使用firewalld来管理防火墙,而不是直接使用iptables

开启VNC监听(不推荐)

参考 KVM Virtualization: Start VNC Remote Access For Guest Operating Systems

  • 方法一

在启动虚拟机时候使用参数 -vnc 0.0.0.0:1

  • 方法二

编辑 /etc/libvirt/qemu/win2012.xml

<graphics type='vnc' port='-1' autoport='yes' keymap='en-us'/>

重启 libvirtd

/etc/init.d/libvirtd restart
virsh shutdown win2012
virsh start win2012

这个方法测试可行

  • 方法三

sudo virsh edit win2012

动态修改 win2012.xml 修改添加 listen='0.0.0.0'后再次重启虚拟机

<graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'/>

通过ssh tunnel使用(推荐)

不过上述修改方法都不是很好,存在安全隐患,并且需要打通防火墙端口。

更好的方法是使用 ssh tunnel,可参考 HowTo: Tunneling VNC Connections Over SSH

ssh -L 5901:localhost:5901 -N -f -l username SERVER_IP

举例:

ssh -L 5901:127.0.0.1:5901 -N -f -l rocky 192.168.1.100

-N 参数表示不执行远程命令,这个参数是用于端口转发的

-f 参数表示后台执行ssh

然后就可以使用vnc viewer客户端访问本地回环地址 127.0.0.1 来访问远程服务器的VNC来进行进一步安装。

对于Mac系统,内建有一个VNC客户端(但是 很不幸 不支持libvirtd所使用的VNC),可以尝试 open vnc://127.0.0.1:5901 来访问VNC服务器。

如果要批量开启一批端口转发(例如51个端口转发,可以支持服务器上51个虚拟机的VNC访问):

SERVER=192.168.1.100
PORTS=`echo {5900..5950}`
for i in $PORTS;do
    ssh -L $i:127.0.0.1:$i -N -f $SERVER
done

安装好虚拟机初始操作系统之后,可以clone KVM虚拟机

NAT网络的调整

上述安装配置适合作为一个个人使用的开发工作站,但是如果需要对外提供访问服务,默认的NAT网络就存在限制了。

此外,默认的NAT网络使用了DHCP分配地址,所以启动虚拟机可能每次获得的IP地址不一定相同,对外做端口映射提供服务就存在问题。

参考

Last updated