# 最佳实践

<!-- TOC -->

- [最佳实践](#最佳实践)
    - [性能最佳实践](#性能最佳实践)
        - [halt-polling](#halt-polling)
        - [IOThread配置](#IOThread配置)
        - [裸设备映射](#裸设备映射)
        - [kworker隔离绑定](#kworker隔离绑定)
        - [内存大页](#内存大页)
    - [安全最佳实践](#安全最佳实践)
        - [Libvirt鉴权](#Libvirt鉴权)
        - [qemu-ga](#qemu-ga)
        - [sVirt保护](#sVirt保护)

<!-- /TOC -->

## 性能最佳实践

### halt-polling

#### 概述

在计算资源充足的情况下，为使虚拟机获得接近物理机的性能，可以使用halt-polling特性。没有使用halt-polling特性时，当vCPU空闲退出后，主机会把CPU资源分配给其他进程使用。当主机开启halt-polling特性时，虚拟机vCPU处于空闲时会polling一段时间，polling的时间由具体配置决定。若该vCPU在polling期间被唤醒，可以不从主机侧调度而继续运行，减少了调度流程的开销，从而在一定程度上提高了虚拟机系统的性能。

>![](public_sys-resources/icon-note.gif) **说明：**   
>halt-polling的机制保证虚拟机的vCPU线程的及时响应，但在虚拟机空载的时候，主机侧也会polling，导致主机看到vCPU所在CPU占用率比较高，而实际虚拟机内部CPU占用率并不高。  

#### 操作指导

系统默认开启了halt-polling特性，polling的时间默认为500000ns。用户可以通过文件halt\_poll\_ns内容动态修改vCPU用于halt-polling的时间，单位为ns。

例如设置polling时间为400000，使用root用户执行命令如下：

```
# echo 400000 > /sys/module/kvm/parameters/halt_poll_ns
```

### IOThread配置

#### 概述

KVM平台上，对虚拟磁盘的读写在后端默认由QEMU主线程负责处理。这样会造成如下问题：

-   虚拟机的I/O请求都由一个QEMU主线程进行处理，因此单线程的CPU利用率成为虚拟机I/O性能的瓶颈。
-   虚拟机I/O在QEMU主线程处理时会持有QEMU全局锁\(qemu\_global\_mutex\)，一旦I/O处理耗时较长，QEMU主线程长时间占有全局锁，会导致虚拟机vCPU无法正常调度，影响虚拟机整体性能及用户体验。

可以为virtio-blk磁盘或者virtio-scsi控制器配置IOThread属性，在QEMU后端单独开辟IOThread线程处理虚拟磁盘读写请求，IOThread线程和virtio-blk磁盘或virtio-scsi控制器可配置成一对一的映射关系，尽可能地减少对QEMU主线程的影响，提高虚拟机整体I/O性能，提升用户体验。

#### 配置说明

使用IOThread线程处理虚拟机磁盘读写请求，需要修改虚拟机配置，这里给出具体的配置说明。

-   配置虚拟机高性能虚拟磁盘的总数。例如通过<iothreads\>配置IOThread线程的总数为4：

    ```
    <domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>   
         <name>VMName</name>
         <memory>4194304</memory>
         <currentMemory>4194304</currentMemory>
         <vcpu>4</vcpu>
         <iothreads>4</iothreads>
    ```

-   给virtio-blk磁盘配置IOThread属性。**<**iothread**\>**表示IOThread线程编号，编号从1开始配置，最大为<iothreads\>的配置值，且编号不能重复使用。例如将编号为2的IOThread配置给virtio-blk磁盘使用：

    ```
    <disk type='file' device='disk'>
          <driver name='qemu' type='raw' cache='none' io='native' iothread='2'/>
          <source file='/path/test.raw'/>
          <target dev='vdb' bus='virtio'/>
          <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </disk>
    ```

-   给virtio-scsi控制器配置IOThread属性。例如将编号为2的IOThread配置给virtio-scsi控制器使用：

    ```
    <controller type='scsi' index='0' model='virtio-scsi'>
          <driver iothread='2'/>
          <alias name='scsi0'/>
          <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </controller>
    ```

-   IOThread线程绑定物理CPU

    虚拟磁盘IOThread线程的绑核配置，将IOThread线程绑定到用户指定的物理CPU范围内，不影响vCPU线程的资源占用诉求。<iothread\>表示IOThread线程编号，<cpuset\>表示绑定的物理CPU编号。

    ```
    <cputune>
    <iothreadpin iothread='1' cpuset='1-3,5,7-12' />
    <iothreadpin iothread='2' cpuset='1-3,5,7-12' />
    </cputune>
    ```


### 裸设备映射

#### 概述

配置虚拟机存储设备时，除了将文件配置给虚拟机作为虚拟磁盘使用外，还可以将块设备（物理LUN、逻辑卷等）直接配置给虚拟机使用，从而提升存储性能。该配置方式称为裸设备映射。在该配置方式下，虚拟磁盘向虚拟机呈现为一个SCSI设备，且支持大部分SCSI命令。

裸设备映射根据后端实现特点，分为虚拟裸设备映射和物理裸设备映射，物理裸设备映射相对虚拟裸设备映射具有更优秀的性能和更丰富的SCSI命令，但物理裸设备映射需要将整块SCSI磁盘挂载给虚拟机使用，如果使用分区、逻辑卷等方式配置，虚拟机将无法识别。

#### 配置示例

裸设备映射需要修改虚拟机配置文件，这里给出配置示例。

-   虚拟裸设备映射

    将主机上存在的SCSI磁盘“/dev/sdc”挂载给虚拟机作为虚拟裸设备的配置示例如下：

    ```
    <domain type='kvm'>
     <devices>
        ...
        <controller type='scsi' model='virtio-scsi' index='0'/>
        <disk type='block' device='disk'>
            <driver name='qemu' type='raw' cache='none' io='native'/>
            <source dev='/dev/sdc'/>
            <target dev='sdc' bus='scsi'/>
            <address type='drive' controller='0' bus='0' target='0' unit='0'/>
        </disk>
        ...
     </devices>
    </domain>
    ```


-   物理裸设备映射

    将主机上存在的SCSI磁盘“/dev/sdc”挂载给虚拟机作为物理裸设备的配置示例如下：

    ```
    <domain type='kvm'>
     <devices>
        ...
        <controller type='scsi' model='virtio-scsi' index='0'/>
        <disk type='block' device='lun' rawio='yes'>
            <driver name='qemu' type='raw' cache='none' io='native'/>
            <source dev='/dev/sdc'/>
            <target dev='sdc' bus='scsi'/>
            <address type='drive' controller='0' bus='0' target='0' unit='0'/>
        </disk>
        ...
     </devices>
    </domain>
    ```


### kworker隔离绑定

#### 概述

kworker是Linux内核实现的per-CPU线程，用来执行系统中的workqueue请求。kworker线程会和vCPU线程争抢物理核资源，导致虚拟化业务性能抖动。为了使虚拟机能够稳定的运行，减少kworker线程对虚拟机的干扰，可以将主机上的kworker线程绑定到特定的CPU上运行。

#### 操作步骤

用户可以通过修改/sys/devices/virtual/workqueue/cpumask文件，将workqueue中的任务绑定到cpumask中指定的CPU上。cpumask中的掩码以十六进制表示，例如将kworker绑定到CPU0\~CPU7上，对应掩码为ff，使用root用户执行命令如下：

```
# echo ff > /sys/devices/virtual/workqueue/cpumask
```

### 内存大页

#### 概述

相比传统的4K内存分页，openEuler也支持2MB/1GB的大内存分页。内存大页可以有效减少TLB miss，显著提升内存访问密集型业务的性能。openEuler使用两种技术来实现内存大页。

-   静态大页

    静态大页要求宿主机操作系统在加载前提前预留一个静态大页池，虚拟机创建时通过修改xml配置文件的方式，指定虚拟机的内存从静态大页池中分配。静态大页能保证虚拟机的所有内存在host上始终以大页形式存在，保证物理连续，但增加了部署的困难，修改静态大页池的页面大小后需要重启host才能生效。静态大页的页面大小支持2M或1G。


-   透明大页

    如果开启透明大页模式THP（Transparent Huge Pages），虚拟机分配内存时自动选择可用的2M连续页，同时自动完成大页的拆分合并，当没有可用的2M连续页时，它会选择可用的64K（AArch64架构）或4K（x86_64架构）页面进行分配。透明大页的好处是不需要用户感知，同时能尽量使用2M大页以提升内存访问性能。


在虚拟机完全使用静态大页的场景下，可以通过关闭透明大页的方法，减少宿主机操作系统的开销，以便虚拟机获得更稳定的性能。

#### 操作指导

-   使用静态大页

    在创建虚拟机之前通过修改XML的方式，为虚拟机配置使用静态大页。

    ```
      <memoryBacking>
        <hugepages>
          <page size='1' unit='GiB'/>
        </hugepages>
      </memoryBacking>
    ```

    以上XML片段表示为虚拟机配置1G静态大页。

    ```
      <memoryBacking>
        <hugepages>
          <page size='2' unit='MiB'/>
        </hugepages>
      </memoryBacking>
    ```

    以上XML片段表示为虚拟机配置2M静态大页。

-   使用透明大页

    通过sysfs可以动态开启使用透明大页：

    ```
    # echo always > /sys/kernel/mm/transparent_hugepage/enabled
    ```

    动态关闭使用透明大页：

    ```
    # echo never > /sys/kernel/mm/transparent_hugepage/enabled
    ```


## 安全最佳实践

### Libvirt鉴权

#### 简介

用户使用libvirt远程调用功能时，如果不进行任何鉴权校验，所有连接到主机所在网络的第三方程序都可以通过libvirt的远程调用操作虚拟机，存在安全隐患。为了提升系统安全性，openEuler提供了libvirt鉴权功能，即用户通过libvirt远程调用操作虚拟机前，必须经过身份校验，只有特定用户允许访问虚拟机，从而保护组网中的虚拟机。

#### 开启libvirt鉴权

openEuler默认关闭libvirt远程调用功能，这里给出开启libvirt远程调用和libvirt鉴权功能的方法。

1.  使用root用户登录主机。
2.  修改libvirt服务配置文件/etc/libvirt/libvirtd.conf，开启libvirt远程调用和libvirt鉴权功能。例如使用基于SASL（Simple Authentication and Security Layer）协议的TCP远程调用配置参考如下：

    ```
    # 传输层安全协议，0表示关闭，1表示开启，由用户自行配置
    listen_tls = 0
    # 开启基于TCP的远程调用，开启libvirt远程调用和libvirt鉴权功能必须配置为1     
    listen_tcp = 1
    # TCP远程调用所使用的协议，由用户自行配置，此处以sasl为例    
    auth_tcp = "sasl" 
    ```

3.  修改/etc/sasl2/libvirt.conf配置文件，设置SASL认证机制和sasldb数据库。

    ```
    # sasl协议的认证机制
    mech_list: digest-md5
    # 存放用户和用户密码的数据库
    sasldb_path: /etc/libvirt/passwd.db
    ```

4.  添加用于SASL验证的用户并设置其密码，假设用户名为userName，命令参考如下：

    ```
    # saslpasswd2 -a libvirt userName
    Password:
    Again (for verification):
    ```

5.  修改/etc/sysconfig/libvirtd配置文件，开启libvirt侦听选项。

    ```
    LIBVIRTD_ARGS="--listen"
    ```

6.  重启libvirtd服务，使修改生效。

    ```
    # systemctl restart libvirtd
    ```

7.  确认libvirt远程调用的鉴权功能是否生效。根据提示输入用户名和密码能够成功连接libvirt服务，说明开启成功。

    ```
    # virsh -c qemu+tcp://192.168.0.1/system
    Please enter your authentication name: openeuler
    Please enter your password:
    Welcome to virsh, the virtualization interactive terminal.
    
    Type:  'help' for help with commands
           'quit' to quit
    
    virsh #
    ```


#### 管理SASL

这里给出管理SASL用户的操作，请使用root用户操作。

-   查询数据库中存在的用户

    ```
    # sasldblistusers2 -f /etc/libvirt/passwd.db
    user@localhost.localdomain: userPassword
    ```

-   从数据库中删除用户user

    ```
    # saslpasswd2 -a libvirt -d user
    ```


### qemu-ga

#### 概述

qemu-ga（Qemu Guest Agent）它是运行在虚拟机内部的守护进程，它允许用户在host OS上通过QEMU提供带外通道实现对guest OS的多种管理操作：包括文件操作（open、read、write、close，seek、flush等）、内部关机、虚拟机休眠（suspend-disk、suspend-ram、suspend-hybrid），获取虚拟机内部的信息（包括内存，CPU，网卡，OS等相关信息 ）等。

在一些对安全要求较高的使用场景，为了防止虚拟机内部信息泄露，qemu-ga提供了黑名单功能，用户可以通过黑名单选择性屏蔽qemu-ga提供的部分功能。

>![](public_sys-resources/icon-note.gif) **说明：**   
>qemu-ga对应的安装包是qemu-guest-agent-xx.rpm，openEuler默认不安装。xx为实际版本号。  

#### 操作方法

请使用root用户按照如下操作步骤添加qemu-ga黑名单：

1.  登录虚拟机，确定qemu-guest-agent服务存在且处于运行状态。

    ```
    # systemctl status qemu-guest-agent |grep Active
       Active: active (running) since Wed 2018-03-28 08:17:33 CST; 9h ago
    ```

2.  查询qemu-ga哪些命令可以加入黑名单：

    ```
    # qemu-ga --blacklist ?
    guest-sync-delimited
    guest-sync
    guest-ping
    guest-get-time
    guest-set-time
    guest-info
    ...
    ```


1.  设置黑名单。通过修改/usr/lib/systemd/system/qemu-guest-agent.service，将需要屏蔽的命令添加到该文件的--blacklist中，不同命令使用空格分隔。例如将guest-file-open和guest-file-close命令加入黑名单的配置参考如下：

    ```
    [Service]
    ExecStart=-/usr/bin/qemu-ga \
          --blacklist=guest-file-open guest-file-close
    ```


1.  重启qemu-guest-agent服务：

    ```
    # systemctl daemon-reload
    # systemctl restart qemu-guest-agent
    ```

2.  确认虚拟机开启qemu-ga黑名单功能是否生效，即qemu-ga进程配置的参数--blacklist是否正确。

    ```
    # ps -ef|grep qemu-ga|grep -E "blacklist=|b="
    root       727     1  0 08:17 ?        00:00:00 /usr/bin/qemu-ga --method=virtio-serial --path=/dev/virtio-ports/org.qemu.guest_agent.0 --blacklist=guest-file-open guest-file-close guest-file-read guest-file-write guest-file-seek guest-file-flush -F/etc/qemu-ga/fsfreeze-hook
    ```

    >![](public_sys-resources/icon-note.gif) **说明：**   
    >更多关于qemu-ga的资料可以参见[https://wiki.qemu.org/Features/GuestAgent](https://wiki.qemu.org/Features/GuestAgent)。  


### sVirt保护

#### 概述

在只使用自由访问控制DAC（Discretionary Acces Control）策略的虚拟化环境中，主机上运行的恶意虚拟机可能存在攻击hypervisor或其他虚拟机的情况。为了提升虚拟化场景的安全性，openEuler使用了sVirt保护。sVirt是基于SELinux，适用于KVM虚拟化场景的安全防护技术。虚拟机本质是主机操作系统上的普通进程，sVirt机制在hypervisor将虚拟机对应的QEMU进程进行SELinux标记分类，除了使用type表示虚拟化专有进程和文件，还用不同的的category（在seclevel区间）表示不同虚拟机，每个虚拟机只能访问自身相同category的文件设备，防止虚拟机访问非授权的主机或其他虚拟机的文件和设备，从而防止虚拟机逃逸，提升主机和虚拟机的安全性。

#### 开启sVirt保护

**一、使用root用户按照如下操作步骤开启主机的SELinux**

1.  登录主机。
2.  开启主机SELinux功能。
    1.  修改系统启动的grub.cfg，将selinux设置为1。

        ```
        selinux=1
        ```

    2.  修改/etc/selinux/config，将SELINUX模式设置为enforcing。

        ```
        SELINUX=enforcing
        ```

3.  重启主机。

    ```
    # reboot
    ```


**二、创建开启sVirt功能的虚拟机**

1.  虚拟机配置文件中添加如下配置：

    ```
    <seclabel type='dynamic' model='selinux' relabel='yes'/>
    ```

    或确认没有下述配置：

    ```
    <seclabel type='none' model='selinux'/>
    ```

2.  创建虚拟机。

    ```
    # virsh define openEulerVM.xml
    ```


**三、确认sVirt开启成功**

执行下述命令检查运行中的虚拟机QEMU进程是否已经启用sVirt防护，若存在"svirt\_t:s0:c"表示已经启用sVirt防护。

```
# ps -eZ|grep qemu |grep "svirt_t:s0:c"
system_u:system_r:svirt_t:s0:c200,c947 11359 ? 00:03:59 qemu-kvm
system_u:system_r:svirt_t:s0:c427,c670 13790 ? 19:02:07 qemu-kvm
```

