1. 丢包监控点

2. 控制进程资源隔离的几种方式
-
taskset -
cgroup -
cset shield
taskset 和 cgroup 都是用于管理进程在 CPU 核心上运行的工具,但它们有一些区别:
taskset:
taskset 是一个命令行工具,用于将一个或多个进程绑定到指定的 CPU 核心上运行。 使用 taskset 命令可以直接将指定进程或任务绑定到指定的 CPU 核心,从而实现进程与 CPU 核心的绑定关系。 taskset 是一个针对单个进程的工具,适用于简单的 CPU 核心绑定需求。
cgroup:
cgroup 是 Linux 内核的一个子系统,用于管理和控制进程组(control groups)的资源使用,包括 CPU、内存、磁盘 I/O 等。
通过 cgroup,系统管理员可以对一组进程的资源使用进行集中管理和限制。
cgroup 可以通过配置 CPU 控制组(CPU control group)来管理进程与 CPU 核心之间的绑定关系,实现更精细的资源控制和调度。
cgroup 是一个更为高级和复杂的资源管理工具,适用于需要对一组进程进行统一资源管理的场景。
虽然 taskset 和 cgroup 都与进程与 CPU 核心的绑定关系有关,但它们的使用场景和功能略有不同。 在简单的 CPU 核心绑定需求下,可以使用 taskset;而在对一组进程进行统一资源管理和控制时,可以使用 cgroup。
taskset 、cgroup 和 cset shield 对比分析:
taskset 、cgroup 和 cset shield 都是用于控制进程和线程在多核系统上的调度和资源分配的工具,但它们有一些不同之处:
taskset: taskset 是一个工具,用于将进程或线程绑定到特定的 CPU 上。通过设置 CPU 亲和性,任务可以在特定的 CPU 核心上运行,从而有效地控制调度和提高性能。
cgroup (Control Groups): cgroup 是 Linux 内核提供的一种资源控制机制,可以对一组进程进行资源限制和分配。通过 cgroup,可以控制进程的 CPU 使用情况、内存使用情况、I/O 操作等,使得系统管理员能够更好地管理系统资源。
cset shield (cset): cset 是一个工具集,用于创建和管理控制组(control groups)以限制进程或线程在系统上的资源使用。cset shield 则是 cset 工具集提供的一个功能,用于将一组任务限制在指定的 CPU 核心上,从而避免它们受到其他任务的干扰。这是一种更高级的任务调度和资源管理技术。
总的来说,taskset 主要用于绑定单个任务到指定 CPU 核心上,cgroup 用于对一组任务进行资源控制和限制,而 cset shield 则是提供了更高级的任务管理和调度功能,可以更灵活地管理和控制任务在系统上的运行。
在物理机 isolate cpu 以及 kubevirt 虚拟机绑核的情况下,会出现一个 bug:

红帽曾基于该问题对内核提了一个 bug:bugzilla.kernel.org/show_bug.cg…
但是内核人员认为这不是内核的 bug,但应该和 cgroup 有关系, cgroup 只是内核的一个子系统,不属于内核。
3. 内核子系统与内核
内核及其子系统之间在代码维护上通常是相互关联的,因为内核是一个高度集成的系统,由众多子系统组成,每个子系统负责处理特定的功能或任务。以下是内核及其子系统在代码维护上的一些关系:
子系统独立性: 每个子系统通常由不同的开发团队负责维护,这些团队有专门的知识和技能来处理特定子系统的问题。这种分工有助于提高代码质量和开发效率。
接口和协作: 不同子系统之间需要定义清晰的接口和协作机制,以确保它们可以正确地协同工作。这些接口通常由内核开发人员定义和维护,以确保整个内核系统的稳定性和一致性。
代码交叉引用: 内核代码通常会涉及多个子系统,因此在代码维护过程中需要进行适当的交叉引用和测试,以确保修改一个子系统的代码不会对其他子系统造成负面影响。
版本控制和合并: 内核开发人员通常会使用版本控制系统(如 Git )来管理内核代码,以便跟踪修改历史和管理代码版本。在合并新功能或修复程序时,需要考虑不同子系统之间的依赖关系,以确保代码的一致性。
代码审查和测试: 内核开发团队会进行代码审查和测试来确保每个子系统的代码质量和功能性。这有助于提前发现潜在的问题和错误,并及时进行修复。
总的来说,内核及其子系统在代码维护上需要密切合作和协调,以确保整个系统的稳定性和可靠性。每个子系统的维护团队需要不断地进行代码更新、测试和审查,以确保内核系统能够正常运行并满足用户的需求。
4. qemu Emulator Thread cpu 调度的 bug 复现
Examples of emulator threads include:
-
qemu-kvm threads
-
vnc_worker threads
-
vhost- kernel threads (When virtio-net is used (kernel networking on the hypervisor)
宿主机 isolate 之后(内核 CFS 完全公平调度不再对这些核心起作用,所以这不是内核的 bug),k8s 配置 isloated 的核,然后分配给 kubevirt 虚拟机 pod。
创建虚拟机之后,pod 本身基于 cgroup 管理绑核,宿主机上存在 /sys/fs/cgroup/ 关于 pod 进程 id 的配置。
而且虚拟机 pod 内部又有 /sys/fs/cgroup/ 关于 qemu 进程的配置。此时可以复现红帽在 bug 中描述的现象,即所有 Emulator Thread scheuling 都作用到第一个 cpu 上,这个 cpu 即虚拟机启动时所在的 cpu,也是虚拟机内部的 0 号 cpu。
- 红帽使用 tasket 可以稳定复现该情况。
- 基于目前 kubevirt 的设计以及 k8s cgroup 也可以稳定复现该情况。
- 但是红帽在 bug 中描述,使用
cset可以完全不存在该 bug。
所以这个问题,应该是 tasket 以及 cgroup 设计上的一个问题。使用 cset 则不存在该问题。
但是容器(包括 k8s)都采用 cgroup,所以应该只能从 cgroup 的角度解决。
openstack nova 组件将计算资源配额管理方案从 cgroup 改为 cset 是很方便的,但是基于容器的方案由于绑定较深,一时很难替换。

5. Optimal Location of Emulator Threads (优化模拟线程的所在 cpu 位置)
ovs datapath 类型:
- system (内核)
- netdev (用户态 dpdk)
-
每种 datapath 可以支持多个网桥,不同 datapath 类型的网桥不能互通。
-
patch 端口只存在于 ofproto 层面,不会下发到 datapath 中,所以通过
ovs-appctl dpctl/show命令是看不到 patch 端口的。 -
system datapath 在 openvswitch.ko 模块中直接调用网卡驱动进行收发包。
-
netdev datapath 中,对于 non-pmd 端口,使用 af_socket 或者 read|write /dev/net/tun 实现收发包,对于 pmd 类型端口,使用其 dpdk driver 在用户态实现收发包。
emulator threads 的 cpu 的放置位置,需要考虑以下三种网络类型:
-
DPDK networking within the instance and netdev datapath in Open vSwitch
-
DPDK networking within the instance, system datapath in Open vSwitch and kernel space networking on the hypervisor
-
Kernel networking within the instance and netdev datapath in Open vSwitch
5.1 Optimal Placement of Emulator Threads with DPDK Networking Within the Instance and netdev datapath in Open vSwitch
场景:宿主机使用 ovs-dpdk, 而且虚拟机内部也使用 dpdk
Open vSwitch 使用 netdev datapath,在实例内部也通过 DPDK 网络优化的模拟器线程的放置
在物理机上:因此,emulator pin cpuset 需要以这一种方式配置:不与虚拟机内部编号为 1 及以上的 vCPU 对应的物理 CPU 重叠。
在虚拟机内:如果 DPDK 在实例内部运行,数据包处理完全在用户空间进行。不要将 PMD 调度到运行在 vCPU0 上,因为这应该保留给操作系统和中断处理。由于实例内部的 PMD CPU 在活跃循环中运行并且需要占用 100% 的 CPU,它们不应该被抢占。如果其中一个 vCPU 被抢占,可能会导致数据包丢失。
使用 DPDK 网络在实例内部时,模拟器线程的最佳位置要么是处理 vCPU 0 的 pCPU,要么是一个完全不处理任何虚拟 CPU 的专用物理 CPU。
结论:如果在虚拟化主机上使用 OVS-DPDK 且实例内部使用 DPDK,将模拟器线程(Emulator Threads)放置在 vCPU 0 上是最佳选择,虚拟机内部 DPDK 不要使用 vcpu0 (虚拟机最好使用 isolate 隔离下 0 号 cpu,操作系统和 DPDK 都不要使用这颗 vcpu0) 。
5.2 Optimal Placement of Emulator Threads with DPDK Networking Within the Instance and System datapath in Open vSwitch
场景:宿主机使用 System datapath, 而虚拟机内部使用 dpdk
Open vSwitch 使用 内核 datapath,在虚拟机内部仍通过 DPDK 网络优化的模拟器线程的放置
情况1:
在物理机上: 如果在虚拟化管理程序(qemu Hypervisor)中使用内核空间网络,则在内核中执行虚拟化管理程序的数据包处理。
在虚拟机内: 如果在实例内使用 DPDK 网络,
结论:
- 最佳的 Emulator 线程位置要么是处理 vCPU 0 的 pCPU。
- 要么是专门的物理 CPU:不对应任何虚拟机使用的虚拟 CPU,物理机操作系统也不要用。
需要注意的是,在这种情况下,vNIC 队列的数据包处理是在虚拟机管理程序的 vhost- 内核线程中执行的。在高流量情况下,这些内核线程可能会产生较大的 CPU 负载。Emulator 线程的最佳位置需要根据具体情况来确定(内核相对 dpdk 转发效率较低,一个核心可能不够用,如果考虑这种场景,业务会很难编排)。 如果是 dpdk 的话,一个核心应该是足够的,对于整个物理机来说可能固定几个核心就可以(业务也好统一编排)。
5.3 Optimal Placement of Emulator Threads with Kernel Networking within the Instance and netdev datapath in Open vSwitch
场景:宿主机使用 ovs-dpdk, 而虚拟机内部使用内核网络协议栈
虚拟机使用内核网络的时,最佳的 Emulator 线程位置有两个选项:
-
优化中断分配,例如,在实例内核中使用 softirq。在这种情况下,您无需为模拟器线程分配额外的 pCPU,并且可以将模拟器线程分配给一个不处理任何网络中断的 pCPU。
-
在同一个 NUMA 节点上为模拟器线程使用专用的 pCPU。
由于第一种选择的复杂性,建议选择第二种选项。
第一种选项,在宿主机 isolate 的背景中:
- 虚拟机隔离出一个核,然后模拟器线程只用这个核心。 虚拟机和物理机要有统一编排。
- 虚拟机创建时,可以多分配一个核,虚拟机虚拟机 qumu libvirt 不配置这个核,然后模拟器线程只用这个核心。
第二种选项,在宿主机 isolate 的背景中: 虚拟机的 cpu 核心,最好只在同一个 numa 节点上。这样流量在宿主机上也不会跨 numa。
6. 控制 qemu 模拟器线程漂移到其他 cpu 的常用命令
将 qemu 进程绑定到宿主机物理 cpu
# 查看 qemu 进程所在的物理 cpu 的范围
# virsh emulatorpin openEulerVM
emulator: CPU Affinity
----------------------------------
*: 0-63
# 在线绑定:
# virsh emulatorpin openEulerVM --live 2-3
# virsh emulatorpin openEulerVM
emulator: CPU Affinity
----------------------------------
*: 2-3
# 永久绑定:
# 基于 --config 修改 VM 和 QEMU 进程之间的绑定关系
# virsh emulatorpin openEulerVM --config 0-3,^1
# virsh emulatorpin euler
emulator: CPU Affinity
----------------------------------
*: 0,2-3
That is, the QEMU process is scheduled only on the three physical CPUs. The modification of the binding relationship does not take effect immediately. Instead, the modification takes effect after the next startup of the VM and takes effect permanently.
QEMU 进程只会在这几个 物理 cpu 上,核心永久绑定不会立刻生效,下次重启才会生效。(但是 kubevirt 好像重启就会丢掉这个配置)
关于 cpu pin 的几个命令:
# virsh vcpupin openEulerVM
VCPU CPU Affinity
----------------------
0 0-63
1 0-63
2 0-63
3 0-63
# virsh vcpupin openEulerVM --live 0 2-3
# virsh vcpupin euler
VCPU CPU Affinity
----------------------
0 2-3
1 0-63
2 0-63
3 0-63
# virsh vcpupin openEulerVM --config 0 0-3,^1
# virsh vcpupin openEulerVM
VCPU CPU Affinity
----------------------
0 0,2-3
1 0-63
2 0-63
3 0-63