# 在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中`vmx`或`svm`的标记。在这种没有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`模式的bridge`virbr0`

启动激活`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驱动](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/virtual/kvm/performance/kvm_performance_tunning_in_action/install_windows_with_virtio/README.md)
>
> 参考：[Creating guests with the virt-install command](http://www.ibm.com/support/knowledgecenter/en/linuxonibm/liabp/liabpusingvirtinstall.htm)

```
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](http://serverfault.com/questions/631317/windows-7-as-kvm-guest-installation-with-virtio-drivers-detected-virtio-scsi-d)

## 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](http://sourceforge.net/projects/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?](http://serverfault.com/questions/567716/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](http://www.cyberciti.biz/faq/linux-kvm-vnc-for-guest-machine/)

* 方法一

在启动虚拟机时候使用参数 `-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](http://www.cyberciti.biz/tips/tunneling-vnc-connections-over-ssh-howto.html)

```
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虚拟机](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/virtual/kvm/startup/in_action/clone_kvm_vm/README.md)

## NAT网络的调整

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

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

* [KVM/libvirt Host主机NAT网络固定DHCP分配和端口映射](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/virtual/kvm/startup/in_action/kvm_libvirt_static_ip_for_dhcp_and_port_forwarding/README.md)

## 参考

* [Install and use CentOS 7 or RHEL 7 as KVM virtualization host](http://jensd.be/207/linux/install-and-use-centos-7-as-kvm-virtualization-host)
* [KVM Virtualization in RHEL 7 Made Easy](https://linux.dell.com/files/whitepapers/KVM_Virtualization_in_RHEL_7_Made_Easy.pdf)
* [Chapter 9. Installing a Fully-virtualized Windows Guest](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Virtualization_Host_Configuration_and_Guest_Installation_Guide/sect-Virtualization_Host_Configuration_and_Guest_Installation_Guide-Windows_Installations-Installing_Windows_XP_as_a_fully_virtualized_guest.html)
* [KVM Guests: Using Virt-Install to Import an Existing Disk Image](https://www.itfromallangles.com/2011/03/kvm-guests-using-virt-install-to-import-an-existing-disk-image/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/virtual/kvm/startup/in_action/deploy_kvm_on_centos.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
