案例描述

本案例共讲述了多个Linux系统调优或者OpenStack平台优化的例子。具体如下:

1.Linux系统句柄优化;

2.OpenStack平台调度策略优化;

3.OpenStack平台镜像优化;

4.OpenStack平台云主机I/O策略优化。

案例一:Linux系统句柄优化

规划节点

节点规划见表1。

表1 节点规划

IP 主机名 节点
192.168.200.21 test 实验节点

基础准备

登录物理OpenStack平台,使用CentOS7.9镜像创建云主机,云主机类型使用1VCPU/2GB内存/20GB硬盘。创建后的云主机作为本次案例的实验节点。

案例实施

Linux系统句柄介绍

文件句柄,会随着进程数增加而增加。其实Linux是有文件句柄限制的,而且Linux默认一般都是1024。在生产环境中很容易到达这个值,因此这里就会成为系统的瓶颈。

在Linux系统的生产环境中,会经常遇到“too many open files”的报错。这个报错顾名思义是打开过多文件数。不过这里的files不单是文件的意思,也包括打开的通讯链接(比如socket),正在监听的端口等等,所以有时候也可以叫做句柄(handle),这个错误通常也可以叫做句柄数超出系统限制。

在出现“too many open files”报错的时候,大多数情况是由于程序没有正常关闭一些资源引起的,所以出现这种情况,这个时候需要检查I/O读写,socket通讯等是否正常关闭等。当然也可以通过修改参数,将系统的文件句柄限制提高,来缓解这一压力。


Linux系统句柄优化

(1)查看当前的句柄数

使用CRT等远程连接工具,连接至192.168.200.21,查看当前系统默认的文件句柄数量,命令如下:

1
2
[root@test ~]# ulimit -n
1024

可以看到当前的系统默认限制是1024。

(2)修改句柄数.

修改Linux系统的句柄数限制有两种方法,一种是使用ulimit命令临时生效,另外一种是修改配置文件,永久生效。此处使用修改配置文件的方式。

注意:如果使用命令临时生效句柄限制,root用户可以调大或者调小句柄的限制,而非root用户一旦设定了值,就只能调小这个限制,不能调大,不然会报“bash: ulimit: open files: cannot modify limit: Operation not permitted”的错误。

修改配置文件调整句柄限制为65535,命令如下:

1
2
[root@test ~]# echo "* soft nofile 65535"  >> /etc/security/limits.conf
[root@test ~]# echo "* hard nofile 65535" >> /etc/security/limits.conf

添加配置的解释如下:

1
2
3
*       #代表所有用户
soft #超过文件句柄数时,仅提示
hard #超过文件句柄数时,直接限制

修改完之后,不需要重启系统即会生效,但是需要重新登录。退出重新登录该云主机,查看当前的句柄限制,命令如下:

1
2
3
4
5
6
[root@test ~]# logout

Connection closed.
Last login: Tue Feb 8 01:58:31 2022 from 192.168.0.70
[root@test ~]# ulimit -n
65535

可以看到当前的限制为65535。

至此,Linux系统文件句柄优化配置结束。在生产环境中,系统默认的1024远远不够,会导致系统报“too many open files”的错误。所以优化句柄数量限制是很有必要的。此案例默认使用的root用户进行修改,如果使用非root用户,在修改句柄限制的时候,会有比较多的限制,感兴趣的读者,可以自行查找资料学习。

案例二:OpenStack平台调度策略优化

规划节点

使用云主机搭建的OpenStack平台。节点规划见表2。

表2 节点规划

IP 主机名 节点
192.168.200.11 controller 控制节点
192.168.200.12 compute 计算节点

基础准备

使用云主机搭建的OpenStack平台作为实验节点,该案例只研究云平台在大规模创建云主机遇到特定报错时的解决方法。

案例实施

OpenStack平台报错分析

在OpenStack平台经历大并发的时候,比如同一个平台,大量的用户同时创建云主机(单个用户创建大量云主机不会触发此种现象),会达到云平台的性能瓶颈,导致创建云主机报错。

大量用户同时创建云主机,会对云平台的两个服务造成性能瓶颈,一个是RabbitMQ,当RabbitMQ达到瓶颈时,会报如下错误:

1
ERROR oslo.messaging._drivers.impl_rabbit [req-eb79ea09-247e-49e0-960b-0896ef978661 - - - - -] [303415c0-e494-4ea2-8158-d66d4165600d] AMQP server on controller:5672 is unreachable: timed out. Trying again in 10 seconds.: timeout: timed out

另一个就是DHCP。本案例重点讨论DHCP报错的情况,不考虑RabbitMQ的性能瓶颈。DHCP报错信息如下:

1
WARNING nova.compute.manager [req-8d9240cd-6a47-4979-a289-bdd58d399f0a 891c061e4aea4af8909a4affe0c24f92 c509a52800de4902845460fcc5318f3f - 8d899afee33641e0a094f85fbeb9b2c6 8d899afee33641e0a094f85fbeb9b2c6] [instance: 374aa944-6cd7-4dbf-a741-5bd44623919d] Received unexpected event network-vif-plugged-b803941a-c3ae-45d7-b962-13ba1ff00a31 for instance with vm_state active and task_state None.

在这种情况下,因为大量的创建云主机,导致获取IP地址超时,然后就发生了如上的报错。

注意:这个报错还和计算节点的性能有关,大量创建云主机的时候,计算节点如果没有创建过该镜像的云主机,会先从控制节点复制镜像到计算节点,这也会导致速度变慢。而且在创建大量云主机的时候,对计算节点的硬盘也是一个考验,如果硬盘性能差的话,也会导致创建速度慢,大量排队,超时等现象。


解决策略

在解决问题之前,首先了解创建云主机的过程,在创建虚机过程中,nova-compute会调用wait_for_instance_event函数(nova/compute/manage.py)进行network-vif-plugged的事件等待。

在nova-compute配置文件中有两个与该事件相关的参数- vif_plugging_timeout、vif_plugging_if_fatal,前者是等待事件的最大时间,后者是处理超时异常的方式。若在规定时间内,nova-compute接受到了事件响应,那么虚机可正常创建,那么当超时现象发生时,nova-compute会根据vif_plugging_is_fatal的配置采取两种处理方式。

若超时发生,并且配置文件中vif_plugging_is_fatal为True,nova首先执行guest.poweroff,停止qemu进程;然后执行cleanup函数,先清除网络资源,使用函数_unplug_vifs,删除plug_vifs函数创建的ovs port,ovs agent检测到了删除端口的事件然后通知neutron-server删除neutron db中的port信息,最后抛VirtualInterfaceCreateException。

如果vif_plugging_is_fatal为False,即便发生eventlet.timeout.Timeout异常,创建过程也会继续。然后等虚拟机创建成功后,依然可以拿到IP地址。

通过上述虚拟机创建的过程,对于上述云平台发生的错误,就有了解决的思路,通过修改/etc/nova/nova.conf配置文件,将vif_plugging_is_fatal参数由true改为false,命令如下:

在控制节点,修改/etc/nova/nova.conf配置文件:

1
[root@controller ~]# vi /etc/nova/nova.conf

找到如下这行

1
#vif_plugging_is_fatal=true

将该行的注释去掉,并将true改为false,修改完之后如下:

1
vif_plugging_is_fatal=false

保存退出nova.conf,最后重启nova服务,也可以重启所有服务,命令如下:

1
[root@controller ~]# openstack-service restart

等待重启完毕即可。通过该参数的修改,可解决在大并发量创建虚拟机时,因排队超时导致虚拟机获取不到IP地址的报错。

案例三:OpenStack平台镜像优化

规划节点

使用云主机搭建的OpenStack平台。节点规划见表3。

表3 节点规划

IP 主机名 节点
192.168.200.11 controller 控制节点
192.168.200.12 compute 计算节点

基础准备

使用云主机搭建的OpenStack平台作为实验节点,本案例只讲述如何优化通过打快照方式制作的镜像

案例实施

OpenStack平台制作镜像

在OpenStack平台的使用过程中,镜像是一个不可或缺的组件与服务,一般来说,使用最多的是基础镜像,比如CentOS7.5;CentOS7.6;CentOS7.9等,获取这些基础镜像的方式也很简单,可以通过官网直接下载。

当用户想使用自定义镜像的时候,可以使用OpenStack平台中打快照的方式,去制作镜像,比如想制作一个带有数据库服务的镜像,可以按照如下步骤操作:(具体操作不再演示)

(1)启动一个基础的CentOS镜像;

(2)连接虚拟机,配置Yum源;

(3)安装数据库服务;

(4)初始化数据库;

(5)设置开机自启;

(6)在OpenStack平台中将该虚拟机实例打成快照;

(7)在控制节点的镜像和快照存放目录/var/lib/glance/images目录下找到这个镜像文件,改名成mysql.qcow2;

通过上述的方法打快照制作镜像,可以获得一个mysql.qcow2的镜像文件,使用该文件上传至其他云平台中,今后使用该镜像启动的虚拟机,就带有数据库服务了。


OpenStack平台镜像优化

在使用打快照方式制作镜像后,镜像的大小会变得非常大,比如一个基础的CentOS镜像大小为400M左右,但是使用打快照方式制作的镜像大小会有1个G左右,具体的大小还要根据安装的东西来实际情况实际分析。

qemu-img命令中提供一个可用于镜像转换与压缩的选项,即qemu-img convert。接下来使用该命令,对已经打快照完成的镜像进行压缩操作。

使用提供的镜像CentOS7.5-compress.qcow2,上传至controller节点的/root目录下,查看镜像的大小,命令如下:

1
2
[root@controller ~]# du -sh CentOS7.5-compress.qcow2
892M CentOS7.5-compress.qcow2

可以看到当前的镜像大小为892M,接下来使用命令,对镜像进行压缩,命令如下:

1
[root@controller ~]# qemu-img convert -c -O qcow2 CentOS7.5-compress.qcow2 CentOS7.5-compress2.qcow2

该命令参数的解释如下:

1
2
3
4
-c  压缩
-O qcow2 输出格式为 qcow2
CentOS7.5-compress.qcow2 被压缩的文件
CentOS7.5-compress2.qcow2 压缩完成后文件

等待一小段时间后,压缩完成,查看当前目录下CentOS7.5-compress2.qcow2镜像文件的大小,命令如下:

1
2
[root@controller ~]# du -sh CentOS7.5-compress2.qcow2 
405M CentOS7.5-compress2.qcow2

可以看到镜像大概被压缩到了一半的大小。使用qemu-img convert命令可以压缩qcow2镜像,在日常的工作中,经常会用到此命令进行镜像压缩。

案例四:OpenStack平台I/O优化

规划节点

使用云主机搭建的OpenStack平台。节点规划见表4。

表4 节点规划

IP 主机名 节点
192.168.200.11 controller 控制节点
192.168.200.12 compute 计算节点

基础准备

使用云主机搭建的OpenStack平台作为实验节点

案例实施

为什么需要IO调度呢?在最开始的时候,Linux存储在磁盘上。磁盘盘片高速旋转,通过磁臂的移动读取数据。磁臂的移动是物理上的机械上的移动,它无法瞬移,这速度是很慢的。如果我们读取的数据位置很随机,一会在A地点,一会在隔着老远的B地点,移动的时间就全做了无用功,这也就是我们说的随机读写性能慢的原因。如果读取的数据地址是连续的,即使不是连续的也是地址接近的,那么移动磁臂的时间损耗就少了。在最开始,IO调度的作用就是为了合并相近的IO请求,减少磁臂的移动损耗。

单队列I/O调度介绍

登录物理OpenStack平台的Controller节点,查看I/O调度策略,命令如下:

1
2
[root@controller ~]# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq

可以看到,物理节点默认使用的是deadline算法。常用的单队列算法就是noop、deadline和cfq,关于这三种调度算法的详细解释如下:

(1)noop

noop只会对请求做一些简单的排序,其本质就是一个FIFO的队列,只会简单地合并临近的I/O请求后,本质还是按先来先处理的原则提交给磁盘。

根据它的原理,我们可以发现它倾向于饿死读利于写,为什么呢?异步写是把数据直接放到page cache的,也就意味着可以通过page cache缓存大量的写数据,再一次性往下提交I/O请求。而读呢?读一般是同步的,也就意味着必须在读完一笔后再读下一笔,两次读之间是可能被写请求插足的。

(2)cfq

CFQ全称Completely Fair Scheduler,中文名称完全公平调度器,它是现在许多Linux发行版的默认调度器,CFQ是内核默认选择的I/O调度器。它将由进程提交的同步请求放到多个进程队列中,然后为每个队列分配时间片以访问磁盘。对于通用的服务器是最好的选择,CFQ均匀地分布对I/O带宽的访问。CFQ为每个进程和线程,单独创建一个队列来管理该进程所产生的请求,以此来保证每个进程都能被很好的分配到I/O带宽,I/O调度器每次执行一个进程的4次请求。该算法的特点是按照I/O请求的地址进行排序,而不是按照先来后到的顺序来进行响应。简单来说就是给所有同步进程分配时间片,然后才排队访问磁盘。

(3)deadline

deadline确保请求在一个用户可配置的时间内得到响应,避免请求饿死。其分别为读I/O和写I/O提供不同的FIFO队列,读FIFO队列的最大等待时间是500ms,写FIFO队列的最大等待时间是5s。deadline会把提交时间相近的请求放在一批。在同一批中,请求会被排序。当一批请求达到了大小上限或着定时器超时,这批请求就会提交到设备队列上去。

总体来讲,deadline算法对request进行了优先权控制调度,主要表现在如下几个方面:

读写请求分离,读请求具有高优先调度权,除非写请求即将被饿死的时候,才会去调度处理写请求。这种处理可以保证读请求的延迟时间最小化。

对请求的顺序批量处理。对那些地址临近的顺序化请求,deadline给予了高优先级处理权。例如一个写请求得到调度后,其临近的request会在紧接着的调度过程中被处理掉。这种顺序批量处理的方法可以最大程度的减少磁盘抖动。

保证每个请求的延迟时间。每个请求都赋予了一个最大延迟时间,如果达到延迟时间的上限,那么这个请求就会被提前处理掉,此时,会破坏磁盘访问的顺序化特征,回影响性能,但是,保证了每个请求的最大延迟时间。

对于这三种调度算法的总结如下:

● noop

对于闪存设备和嵌入式系统是最好的选择。对于固态硬盘来说使用noop是最好的,deadline次之,而CFQ效率最低。

● cfq

为所有进程分配等量的带宽,适用于有大量进程的多用户系统,CFQ是一种比较通用的调度算法,它是一种以进程为出发点考虑的调度算法,保证大家尽量公平,为所有进程分配等量的带宽,适合于桌面多任务及多媒体应用。

● deadline

适用于大多数环境,特别是写入较多的文件服务器,从原理上看,DeadLine是一种以提高机械硬盘吞吐量为思考出发点的调度算法,尽量保证在有I/O请求达到最终期限的时候进行调度,非常适合业务比较单一并且I/O压力比较重的业务,比如Web服务器,数据库应用等。


多队列I/O调度介绍

现在有多种multi-queue(多队列)调度器,分别为bfq,none,kyber和mq-deadline。下面对常用调度器的进行介绍:

(1)mq-deadline

mq-deadline调度器跟单队列的deadline调度器发挥的功能很相似。它有个insert_request()函数,不会使用多个staging队列,而是把请求放到两个全局的基于时间的队列中,一个放读请求,一个放写请求,先尝试把该新请求与已经存在的请求合并,如果合并不了,则把这个新请求放到队列尾部。dispatch_request()函数会从这些队列中返回第一个请求:基于时间的队列,基于请求批大小,以及避免写饥饿的队列。

(2)none

多队列无操作I/O调度程序。不对请求进行重新排序,最小的开销。NVME等快速随机I/O设备的理想选择。


I/O调度策略修改

调度策略的修改是比较简单的,首先查看当前使用的调度算法,使用CRT工具连接到controller节点,查看调度算法,命令如下:

1
2
[root@controller ~]# cat /sys/block/vda/queue/scheduler 
[mq-deadline] kyber none

可以看到当前的I/O调度算法为mq-deadline,如果当前全是用的是SSD硬盘,那么显然none算法更合适,修改算法为none,命令如下:

1
[root@test ~]# echo none > /sys/block/vda/queue/scheduler

修改完之后,查看当前使用的算法,命令如下:

1
2
[root@test ~]# cat /sys/block/vda/queue/scheduler 
[none] mq-deadline kyber

可以看到当前的I/O调度算法为none模式。

以上就是I/O策略的优化,无论是单队列还是多队列,都可以使用echo命令去修改当前的I/O策略。选择何种策略,也需要根据当前使用的硬盘与应用场景来决定,不能盲目修改。