k8s 多 pod 环境运行 dpdk 多进程问题

问题描述

在 k8s 多个 pod 中运行 dpdk secondary 进程时,启动某个 dpdk secondary 进程时有如下报错信息:

EAL: Cannot initialize local memory map
EAL: FATAL: Cannot init memory
EAL: Cannot init memory

dpdk 版本:dpdk-19.11

初步排查

  1. 排查大页配置
    配置正常
  2. 排查 /dev/hugepages 目录共享配置
    配置正常

扩大调试信息

修改 dpdk secondary 进程 eal 启动参数,添加 –log-level=lib.eal:debug 选项,重新启动 pod,此时报错信息如下:

EAL: rte_fbarray_init(): couldn't lock /var/run/dpdk/rte/fbarray_memseg-2048k-0-0_1: Resource temporarily unavailable
EAL: Cannot initialize local memory map
EAL: FATAL: Cannot init memory
EAL: Cannot init memory

报错信息表明获取 /var/run/dpdk/rte 目录下特定文件的 lock 失败,需要阅读代码进一步定位。

阅读代码

函数调用栈

此问题中,函数调用栈如下所示:

rte_eal_init
	rte_eal_memory_init
		eal_memalloc_init
			rte_memseg_list_walk
				secondary_msl_create_walk
					rte_fbarray_init

rte_memseg_list_walk 函数遍历 mcfg->memsegs 中的所有 rte_memseg_list,执行 secondary_msl_create_walk 函数,为每一个 secondary 进程以 pid 为后缀在 /var/run/dpdk/rte 下面创建 fbarray_memseg-xxx_pid 文件。

问题出在 secondary_msl_create_walk 函数调用 rte_fbarray_init 初始化一个 fbarray 结构过程中,相关代码如下:

/* create distinct fbarrays for each secondary */
	snprintf(name, RTE_FBARRAY_NAME_LEN, "%s_%i",
		primary_msl->memseg_arr.name, getpid());

	ret = rte_fbarray_init(&local_msl->memseg_arr, name,
		primary_msl->memseg_arr.len,
		primary_msl->memseg_arr.elt_sz);
	if (ret < 0) {
		RTE_LOG(ERR, EAL, "Cannot initialize local memory map\n");
		return -1;
	}

上述代码中,使用 memseg_arr 中的 name 字段与进程的 pid 拼接成 name 字段,对照上文添加了 debug 信息后的输出,fbarray_memseg-2048k-0-0 为 memseg_arr 中 name 字段的值,1 为进程的 pid。

rte_fbarray_init 函数中关键代码如下:

   else if (flock(fd, LOCK_EX | LOCK_NB)) {
			RTE_LOG(DEBUG, EAL, "%s(): couldn't lock %s: %s\n",
					__func__, path, strerror(errno));
			rte_errno = EBUSY;
			goto fail;
		}

		/* take out a non-exclusive lock, so that other processes could
		 * still attach to it, but no other process could reinitialize
		 * it.
		 */
		if (flock(fd, LOCK_SH | LOCK_NB)) {
			rte_errno = errno;
			goto fail;
		}

第一次 flock 函数调用以非阻塞方式获取 fbarray_memseg-xxx-pid 文件的互斥锁,第二次 flock 函数调用以非阻塞方式获取 fbarray_memseg-xxx-pid 文件的共享锁。

第一次 flock 函数调用保证每个 secondary 自己的 fbarray_memseg 映射文件只被初始化一次,第二次 flock 调用保证在当前 secondary 进程初始化完成后 fbarray_memseg-xxx-pid 文件能够被其它进程 attach。

根本原因是什么?

在 k8s 多个 pod 中运行的 dpdk secondary 进程有自己独立的 pid namespace 空间,然而却需要在同一个共享目录—— /var/run/dpdk/rte 中以 pid 为后缀区分创建独立的文件名并获取互斥锁。

每个 pod 中运行的 dpdk secondary 进程的 pid 可能会相同,此时只能有一个进程能够获取到互斥锁,其它 pid 相同的进程会启动失败。

解决方案

方案一:

修改 k8s pod 配置,dpdk 进程所在的 pod 共享同一个 pid namespace。

方案二:

修改 dpdk 代码,使用其它标识保障 fbarray_memseg 映射文件的唯一性。

扩展问题

dpdk lib 源码目录中是否还存在其它使用进程 pid 区分的实现?

dpdk mp_channel 框架会为每个 dpdk 进程在 /var/run/dpdk/rte 目录中 bind 一个 unix 本地套接字,dpdk secondary 进程本地套接字名称生成代码如下:

if (rte_eal_process_type() == RTE_PROC_SECONDARY)
                snprintf(peer_name, sizeof(peer_name),
                                "%d_%"PRIx64, getpid(), rte_rdtsc());

上述代码使用 pid + tsc 时间来标识,即就两个进程的 pid 相同,只要在同一个机器上运行,tsc 的值是不同的,这样也不会存在冲突。

既然如此,是否可以修改 fbarray_memseg 后缀生成代码,也加上 tsc 来唯一标识呢?

答案是 yes,上文中的方案二可以使用此种方案实现。

总结

本文描述了 dpdk 多进程程序在 k8s 环境运行的一个问题,从 dpdk 代码实现上来看,dpdk 对这种场景的支持并不好,可能还存在其它问题。上半年我写的 程序启动顺序引发的血案之 dpdk 进程死锁 这篇博客描述了 dpdk 多进程共享用户态锁潜在的死锁问题,在这个场景中也存在。这些问题比较隐晦,缺少对 dpdk 底层原理的理解则很难在方案设计前期识别出来,是一个风险点。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐