云主机性能提升实践

背景

说到性能提升,云主机项目组在此前已经在IAAS层面做过一些事情。比如通过优化nova流程提升云主机创建速度,通过静态IP注入和GuestOS启动项裁剪提升云主机操作系统启动速度。这些优化主要集中在云主机的管理层面,云主机的底层性能并没有得到提升。

前段时间,某外部游戏客户在对云主机性能测试过程中发现网易云的云主机性能与华为公有云云主机性能对比存在较大差距。运行机器人模拟游戏操作华为云可以达到8000用户而同样规格的网易云主机只能跑到2000用户。使用sar工具查看,可以看到网易云主机VCPU存在超过50%的steal time,华为云主机的VCPU steal time则不足1%。


原因分析及改进

开发人员收到客户的测试结果之后,针对客户的测试场景给出了具体的分析结果。

VCPU性能

sar工具给出的steal time参数是指虚拟机的vcpu没有运行在GuestOS内部的时间比例。

网易云和华为云都是使用的KVM虚拟化架构。在这种架构下虚拟机只是一个运行在宿主机上的qemu进程,虚拟机内的vcpu只是一个个运行在Host宿主机上的线程。这种架构的好处就是Hypervisor只要专注做好虚拟化相关的工作,像vcpu的调度,内存分配等通用的逻辑则交给Host kernel完成。但是同时虚拟机也只是host上的一个普通进程,在调度过程中host cpu时间片不能保证完全分配给虚拟机的vcpu线程。当host cpu时间片运行的内容不是虚拟机vcpu时,就会被虚拟机内部统计为steal time。当steal time过高时,可以判断宿主机OS出现了严重的cpu抢占现象。

从华为云主机sar命令跟踪情况来看,基本可以确认使用了严格的隔离措施。

网易云在宿主机上做了简单的隔离措施。宿主机预留了4个cpu核,云主机VCPU只能调度到除此之外剩下的物理核上。宿主机节点上所有云主机的VCPU线程在所有的非预留物理核上随机调度,可能会导致较大的性能损耗。

libvirt和qemu社区已经提供了一套VCPU绑定的方案用于处理这种host上cpu抢占导致的性能下降问题。libvirt提供接口通过cgroup或者简单的taskset设置云主机vcpu与host cpu的亲和性,将云主机VCPU与host cpu一一绑定,彻底隔离多个云主机之间的互相影响。

除此之外,宿主机上运行着的各种管理服务,host os上的守护进程等也可能被调度到虚拟机VCPU所在的物理cpu上。与虚拟机VCPU抢占计算资源,导致虚拟机VCPU线程被重新调度。在验证阶段我们使用了taskset设置了宿主机守护进程的CPU亲和性,实现了宿主机进程与虚拟机VCPU相互隔离。

经过初步验证,执行绑核及宿主机进程隔离操作之后网易云主机可以跑到大约7900模拟用户,与华为云基本持平。

NUMA亲和性

云主机VCPU一一绑核解决了VCPU频繁调度导致的性能损耗问题。但是网易云主机与华为云主机之间仍然存在一定的性能差距。分析之后认为网易云主机未设置VCPU内存numa亲和性可能是原因之一。

NUMA(Non Uniform Memory Access Architecture 非均匀内存访问架构)是二十世纪九十年代开发的一种用于提升smp系统下内存访问效率的技术。在这种技术架构下,cpu被分配到多个node上,每个node有单独的内存设备。当cpu访问本node的内存时在时延/cache命中率等方面都会优于访问其它node的内存。默认情况下,宿主机上的进程内存在多个numa node上随机分配,cpu也会随机调度。cpu和内存之间不存在numa亲和性设置,性能会存在较大的波动。

在物理节点上使用numactl可以看到当前网易云主机内存随机分配到宿主机的两个numa node上。理论分析最差的情况下云主机vcpu会完全访问相反的numa节点,最大会造成10%的性能损耗。

KVM下libvirt可以通过numad自动管理云主机内存的numa亲和性。在不改变当前配置的情况下,宿主机运行numad守护进程后云主机启动时倾向使用单一node节点的内存。配合云主机VCPU绑定策略可以保证云主机的VCPU和内存设置一致的numa node亲和性。

经过配置,网易云主机可以达到华为云主机8000模拟用户的水平。


性能优化的落地

之前的云主机性能调优手段都是通过手动操作完成的,如果后续需要大规模应用显然需要将上述的手段自动化。为此我们做了以下的一些工作。

策略的制定

我们把接下来的工作分为两个部分:

  • 宿主机上的云主机绑核操作

    • 云主机VCPU绑定信息的管理
    • 制定云主机VCPU一一绑定的策略
  • 宿主机上的守护进程隔离操作

    • 宿主机守护进程隔离策略的制定
    • 宿主机守护进程的生命周期监控

云主机VCPU绑定

云主机VCPU绑定方案选择

方案一

现有的云主机都是通过nova管理生命周期,所以首先想到的就是将云主机VCPU绑定信息放在nova中管理。这样nova在创建云主机的时候就分配好了VCPU的绑定信息,无需再做二次操作。但是这样带来的问题也非常多。

  • 管理逻辑复杂。需要在nova中云主机所有生命周期相关的操作流程中侵入式修改代码。
  • 云主机关机但是未删除场景的处理逻辑复杂。云主机关机之后,计算节点上就不再占用VCPU资源,需要频繁更新对应的绑定信息。如果不更新这部分信息,可能会出现仍有空闲CPU时就不能继续绑定云主机VCPU的情况。
  • 无法处理节点资源残留的场景。VCPU绑定信息存储在nova的数据库中,最终生效是在libvirt的配置文件中。当计算节点出现异常,云主机资源残留在libvirt中时,会出现nova数据库和libvirt资源不一致的情况。

我们最初计划需要4个人月的工作量来解决上面的这些问题,这么多开发内容很难保证及时上线。因此需要一个更简单,更快捷的解决方案。

方案二

基于libvirt的hook机制,我们提出了另一个解决方案。通过libvirt的hook机制在生命周期操作结束时触发一个shell或者python脚本,根据一定的策略重新绑定当前计算节点上的云主机VCPU。这种方案的好处:

  • 云主机的VCPU绑定信息每次都是实时生成,规避了计算节点云主机残留的问题。
  • VCPU亲和性绑定的操作对nova完全透明,不需要做任何适配,极大的降低了工作量。
  • hook脚本对于libvirt而言是一个可插拔的组件,不需要的时候可以关闭VCPU绑定策略。

因为这种方案优势比较明显,所以我们根据这个方案做了第一个demo程序用于评估方案的可行性。demo hook程序中制定了一个简单的策略在程序被触发的时候通过libvirt接口对云主机VCPU做一一绑定。demo程序调试过程中可以实现云主机VCPU的绑定,但是与libvirtd服务结合在一起执行之后就会出现libvirtd服务死锁的问题。在libvirt官方文档中提示hook脚本中不允许调用libvirt接口,否则可能出现云主机job锁死锁。我们也尝试使用hook脚本中fork子进程调用libvirt接口等形式试图规避这个问题,但是没有效果。这个方案也不能满足我们的需求。

方案三

虽然第二种方案因为死锁问题无法继续,但是因为hook调用的机制非常高效,我们还是希望能够使用这种机制来实现云主机VCPU绑定。由此我们想到nova中也存在hook机制,可以在云主机操作前后触发通过python安装包注册的hook操作。直接把方案二中提供的demo hook脚本移植到这种方案下,经过验证可以正常实现云主机的VCPU绑定。相对于方案二对nova完全透明,这个方案虽然需要在nova中做一些简单的修改,但是比方案一中大量入侵式修改已经优雅很多了。最终我们选用了方案三作为最终的实施方案,下面继续介绍这种方案的实现思路。

云主机VCPU绑定策略

经过讨论,我们制定了如下的云主机VCPU绑定策略:

名词解释
PCPU:物理CPU,包含超线程
可分配的VCPU数量:PCPU数量 × 超售比
一一绑定:云主机一个VCPU对应绑定一个PCPU, 一个PCPU对应绑定一个云主机的一个VCPU
范围绑定:云主机一个VCPU对应绑定多个PCPU, 一个PCPU对应绑定多个云主机的VCPU
  • 云主机生命周期(对计算节点的云主机VCPU运行状态产生影响的所有操作)操作结束之后扫描整个节点的云主机及其VCPU的数量。
  • 如果云主机VCPU总量超过可分配的VCPU数量,所有云主机做整个节点的范围绑定
  • 如果云主机VCPU总量未超过可分配的VCPU数量:
    • 如果一个numa node上云主机总VCPU数超过当前numa node上可分配的VCPU数量,则对所有云主机做整个节点的范围绑定
    • 如果一个numa node上云主机总VCPU数量超过当前numa node上PCPU数量,但是未超过可分配的VCPU数量,则当前numa node上的云主机VCPU范围绑定到当前numa node的PCPU
    • 如果一个numa node上云主机总VCPU数量不超过当前numa node上的PCPU数量,则当前numa node上的云主机VCPU一一绑定到当前numa node的PCPU上
  • 在以上策略基础上如果出现云主机跨node分配内存
    • 如果跨node分配内存的云主机总VCPU数量超过当前物理节点上未做一一绑定的PCPU可分配的VCPU数,则对所有云主机做整个节点的范围绑定
    • 如果跨node分配内存的云主机总VCPU数量少于当前物理节点上未做一一绑定的PCPU可分配的,则把这些虚拟机vcpu范围绑定到当前节点上未做绑核的pcpu上。

nova适配云主机VCPU绑定

  • nova hook的注入 nova中的如下操作会影响云主机VCPU线程的运行状态:
      创建云主机 run_instance
      删除云主机 terminate_instance
      云主机关机 stop_instance
      云主机开机 start_instance
      云主机重启 reboot_instance
      云主机重建 rebuild_instance
      云主机垂直扩容 live_resize
      云主机软删除 soft_delete_instance
      云主机恢复 restore_instance
      云主机进入修复模式 rescue_instance
      云主机解除修复模式 unrescue_instance
      云主机离线迁移源端 resize_instance
      云主机离线迁移目的端 finish_resize
      云主机离线迁移回滚 
          revert_resize
          finish_revert_resize
      云主机热迁移源端 _post_live_migration
      云主机热迁移目的端 post_live_migration_at_destination
      云主机热迁移目的端回滚 rollback_live_migration_at_destination
    
    需要对以上操作函数增加如下的装饰器:
      @hooks.add_hook("bind_numa_affinity")
    
    nova hook机制基于stevedore插件动态加载库。nova中实现了stevedore的HookManager子类,根据传入的参数bind_numa_affinity会自动执行entry_point中指定了nova.hooks的python包中对应的代码。
  • 超售比获取

    云主机VCPU绑定逻辑中需要获取计算节点的超售比信息,当nova-compute服务初始化时需要把对应的信息写入一个配置文件中。


守护进程隔离

守护进程隔离方案

宿主机上的守护进程隔离使用Linux提供的cgroup。将宿主机进程pid分别加入cpuset和memory两个子系统用于绑定进程CPU亲和性和内存使用限额。需要完成两个方面的工作:

  • 系统启动时的初始化
  • 后续宿主机守护进程发生变更时的自动化处理(如新建,重启,停止等)

守护进程隔离的实现

  • 初始化

    在计算节点启动时同步启动一个宿主机上运行的守护进程。启动守护进程后完成cgroup的初始化工作,获取当前节点上运行的所有需要隔离的进程pid,但是如下进程不能包含在内:

      init进程:作为所有宿主机进程的父进程,如果加入cgroup,会导致所有子进程同时也被加入
      内核线程:ps命令看到的[]包括的线程,不能绑定CPU亲和性
      ovs相关的进程:网络已经独立隔离了相关的线程
    
  • 监听变更

    如果只做初始化的操作不监听相关的变更,如果进程重启pid变化之后将不能做正常的隔离。对变更的监听有两种方式:

    • 运行定时任务。实现简单,可以监控所有守护进程。但是不是实时的监控,定时频率过高可能占用过多的系统资源,定时频率低则可能启动很长时间都没有加入隔离的cgroup组。
    • 使用inotify机制。

      Linux提供了对文件inode监听的机制,用户态进程可以通过接口注册对文件系统任一文件的add,delete, modify等事件的监听。事件发生后触发注册的回调函数。

      对比之下,确认应当使用第二种方案。但是在实际使用中又遇到了一些问题。我们的目的是监控云主机上所有守护进程的变更,首先想到应该监听/proc/目录下的pid目录。验证之后发现inotify不能正常的监听/proc目录,因为这个目录实际上不是文件系统而是内存数据。我们注意到大部分通过systemd服务启动的守护进程都会在/var/run目录下保存一个pid文件,而inotify是可以正常监听这个文件的。这样就可以实现对宿主机大部分守护进程的监听,但是某些不遵循systemd服务规范的守护进程则不能被管控。


最终的优化结果

经过以上的优化,现在网易云可以做到:

  • 宿主机消耗较高的守护进程完全隔离
  • 宿主机未超售情况下,保证所有云主机独享独享CPU,规避调度造成的性能损耗。
  • 宿主机部分超售情况下,保证还有部分云主机能享受未超售的性能指标。
  • 云主机性能保证不低于宣称的超售性能指标。

本文来自网易实践者社区,经作者岳文远授权发布