NodeManager上线Cgroup实践
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1. 背景
NodeManager中,将机器的CPU资源抽象为vcore,将内存资源抽象为memory。例如机器为102核、256GB内存,希望赋予NodeManager196GB内存和72核CPU,用于给container分配资源。其配置如下:
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>200704</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>72</value>
</property>
每次申请contaienr时,都会指定请求的vcore数量和memory数量。当单台nodemanager分配的总内存内存超过yarn.nodemanager.resource.memory-mb
或者分配的vcore超过yarn.nodemanager.resource.cpu-vcores
,ResourceManager就不会往这台机器上调度作业了。
上述逻辑中,对于内存,NodeManager能够快速发现container启动的进程是否超过请求的内存量,如果超过container申请的内存,这一般代表内存的占用会越来越多,因此会立即kill container,关掉任务。
但是对于CPU资源,NodeManager默认情况下无法限制住。这是因为虽然指定了container的核数,当进程启动了多个重CPU的线程,可能抢占了大部分的CPU。NodeManager服务也不能因为作业占用了较多的CPU就把作业kill掉,一旦作业快速执行完,那么kill掉重新跑的成本非常高。
因此,对于CPU的资源,希望以控制为主。当作业使用的CPU资源超过container申请后,能够立刻控制CPU不给其继续分配资源,保证其他容器也能正常执行任务。
目前,在生产环境中,多种情况都需要CPU资源隔离技术:
- Presto On Yarn时,如果该NodeManager机器运行了其他重CPU任务,会导致CPU过高产生presto的慢查询现象。
- Flink ON Yarn时,对于基于kafka的清洗任务,也是重CPU的情况,导致CPU占用率过高,这会导致该nodemanager上的其他flink作业无法获取CPU资源导致消费延迟,任务堆积。flink非常敏感。
- Spark ML作业也会导致单个container的CPU使用率过高。
- CPU使用率过高可能导致机器死机。
目前,对于Yarn来说,它支持通过cgroup对启动的container CPU进行限制。
2. Cgroup简要介绍
Cgroup全称control groups,是linux内核用来限制、控制CPU、内存、磁盘等进程资源的一个功能。它不提供任何的借口给用户,而是通过vfs虚拟文件系统的方式把功能暴露给用户态。
通过mount命令使用cpu限制模块挂载cgroup到linux机器中:
mount -t cgroup -o cpu cgroup /sys/fs/cgroup/cpu
#其他资源限制也可以进行挂载
mount -t cgroup -o memory memory /sys/fs/cgroup/memory
mount -t cgroup -o cpuacct cpuacct /sys/fs/cgroup/cpuacct
mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
挂载好后,出现了两个新挂载点:
进入/sys/fs/cgroup/cpu的作业目录中,发现有以下文件。其中,以下几个文件非常重要:
- cgroup.procs:保存进程PID,表示要限制该进程的CPU资源。
- cpu.shares:设置CPU的相对值,假如进程A的shares时1024,进程B的shares是512,那么A得到66%的CPU资源,B得到33的CPU资源。
- cpu.cfs_period_us:调度的时间周期长度,一半是100000us(100ms),这个值一半不修改。
- cpu.cfs_quota_us:一个时间周期内,运行该进程执行的时间。
如下所示:
分配CPU相对时间:如果只设置cpu.shares,不设置cpu.cfs_quota_us,那么当机器空闲时,进程可以占满CPU,如下设置cpu.shares为256:
新增一个继承,其cpu.shares也为256:
分配CPU绝对时间:只要设置了cpu.cfs_quota_us大于0,那么进程最大就只能使用cpu.cfs_quota_us / cpu.cfs_period_us个vcore。如下,设置cpu.cfs_quota_us=600000:
cpu.cfs_quota_us / cpu.cfs_period_us = 600000 / 1000000 = 0.6cpu。可以看到,container最多使用60%的CPU,符合预期:
4. NodeManager配置Cgroup
在https://blog.51cto.com/u_15327484/7815432文章中曾经介绍过,NodeManager启动容器是在ContainerExecutor的实现类中执行的,默认时DefaultContainerExecutor启动shell脚本。但是,如果要支持Cgroup,就必须使用LinuxContainerExecutor。其配置如下:
<property>
<name>yarn.nodemanager.linux-container-executor.resources-handler.class</name>
<value>org.apache.hadoop.yarn.server.nodemanager.util.CgroupsLCEResourcesHandler</value>
</property>
<property>
#yarn的container在基础目录下的子目录
<name>yarn.nodemanager.linux-container-executor.cgroups.hierarchy</name>
<value>/hadoop-yarn</value>
</property>
<property>
#是否需要自动挂载cgroup
<name>yarn.nodemanager.linux-container-executor.cgroups.mount</name>
<value>true</value>
</property>
<property>
#基础挂载目录
<name>yarn.nodemanager.linux-container-executor.cgroups.mount-path</name>
<value>/sys/fs/cgroup</value>
</property>
<property>
#ContainerExcutor执行容器时使用的组
<name>yarn.nodemanager.linux-container-executor.group</name>
<value>hadoop</value>
</property>
<property>
#限制只使用nodemanager CPU的百分比
<name>yarn.nodemanager.resource.percentage-physical-cpu-limit</name>
<value>90</value>
</property>
<property>
#是否开启严格模式
<name>yarn.nodemanager.linux-container-executor.cgroups.strict-resource-usage</name>
<value>false</value>
</property>
<property>
#是否将逻辑核数看作总核数,false时指定物理核数为总核数
<name>yarn.nodemanager.resource.count-logical-processors-as-cores</name>
<value>true</value>
</property>
4.1 Yarn Cgroup代码计算逻辑
其生效逻辑如下所示。使用 yarn.nodemanager.resource.percentage-physical-cpu-limit 来设置所有 containers 的总的 CPU 使用率占用总的 CPU 资源的百分比。比如设置为 60,则所有的 containers 的 CPU 使用总和在任何情况下都不会超过机器总体 CPU 资源的 60 %:
public class NodeManagerHardwareUtils {
public static int getNodeCpuPercentage(Configuration conf) {
int nodeCpuPercentage =
Math.min(conf.getInt(
YarnConfiguration.NM_RESOURCE_PERCENTAGE_PHYSICAL_CPU_LIMIT,
YarnConfiguration.DEFAULT_NM_RESOURCE_PERCENTAGE_PHYSICAL_CPU_LIMIT),
100);
nodeCpuPercentage = Math.max(0, nodeCpuPercentage);
if (nodeCpuPercentage == 0) {
String message =
"Illegal value for "
+ YarnConfiguration.NM_RESOURCE_PERCENTAGE_PHYSICAL_CPU_LIMIT
+ ". Value cannot be less than or equal to 0.";
throw new IllegalArgumentException(message);
}
return nodeCpuPercentage;
}
}
第二步,设置cgroup文件:
- 在/sys/fs/cgroup/cpu路径下创建hadoop-yarn/container_xxx目录。
- 设置cpu.shares:每个container进程的大小是CPU_DEFAULT_WEIGHT * containerVCores,即1024 * containerVCores。将结果写入到cpu.shares文件中。
- 如果设置了严格模式,那么设置quotaUS = MAX_QUOTA_US,periodUS = MAX_QUOTA_US / yarnProcessors。quotaUS就是cpu.cfs_quota_us,periodUS就是cpu.cfs_period_us。那么最终的核数限制就是quotaUS/periodUS = yarnProcessors。而yarnProcessors=(containerVCores * yarnProcessors) / (float) nodeVCores = 总核数 * 限制cpu使用率 * (container vcores) / (nodemanager总vcores)。
- 如果没有设置严格模式,直接结束。
public void setupLimits(ContainerId containerId,
Resource containerResource) throws IOException {
String containerName = containerId.toString();
//默认为true
if (isCpuWeightEnabled()) {
//获取container申请的vcore
int containerVCores = containerResource.getVirtualCores();
//在/sys/fs/cgroup/cpu路径下创建hadoop-yarn/container_xxx子目录及其资源控制文件
createCgroup(CONTROLLER_CPU, containerName);
//cpuShares 默认 等于 1024 * 申请的vcores,这样,其cpu时间占比也按照container申请的核数呈等比例。
int cpuShares = CPU_DEFAULT_WEIGHT * containerVCores;
// cpuShares最小值为10
cpuShares = Math.max(cpuShares, 10);
//更新/sys/fs/cgroup/cpu/hadoop-yarn/container_xxx/cpu.shares文件
updateCgroup(CONTROLLER_CPU, containerName, "shares", String.valueOf(cpuShares));
if (strictResourceUsageMode) {
//获取节点上总的vcores数量
int nodeVCores = conf.getInt(YarnConfiguration.NM_VCORES, YarnConfiguration.DEFAULT_NM_VCORES);
//不能将节点上的所有核分配给
if (nodeVCores != containerVCores) {
// Yarn processor计算方式在下面
// 根据配置,计算当前container能够获得的CPU核数,其中,yarnProcessors是当前节点CPU逻辑核数 * 总体使用率
float containerCPU = (containerVCores * yarnProcessors) / (float) nodeVCores;
// 根据计算得到的CPU核数,设置合理的CPU_PERIOD_US和CPU_QUOTA_US的值
int[] limits = getOverallLimits(containerCPU);
//更新/sys/fs/cgroup/cpu/hadoop-yarn/container_xxx/cpu.CPU_PERIOD_US文件
updateCgroup(CONTROLLER_CPU, containerName, CPU_PERIOD_US, String.valueOf(limits[0]));
//更新/sys/fs/cgroup/cpu/hadoop-yarn/container_xxx/cpu.CPU_QUOTA_US文件
updateCgroup(CONTROLLER_CPU, containerName, CPU_QUOTA_US, String.valueOf(limits[1]));
}
}
}
}
int[] getOverallLimits(float yarnProcessors) {
int[] ret = new int[2];
//省略
int quotaUS = MAX_QUOTA_US;
int periodUS = (int) (MAX_QUOTA_US / yarnProcessors);
//省略
ret[0] = periodUS;
ret[1] = quotaUS;
return ret;
}
4.2 非严格模式和严格模式对比实践
非严格模式下,使用各container的cpu.shares比值规范每个容器使用CPU的比例。shares=1024 * container vcores。即每个container在非严格模式下,它们的CPU资源占比为container的vcores占比。
严格模式下,每个container使用的CPU资源=总核数 * 限制cpu使用率 * (container vcores) / (nodemanager总vcores)。可以发现,它们的CPU资源占比依然为container的vcores占比。因此,严格模式是在非严格模式的一种扩展。
nodemanager环境
- debian 8, 内核 4.9。必须要debian8以上的操作系统。建议升级至debian10。
- 0.9的CPU限制使用率,总共60vcores,每个container申请1个vcore。
非严格模式
默认cfs_quota_us为-1,即不使用固定的CPU时间分配:
它cpu.shares等比例占有cpu,没有开启严格模式时,cpu的值都是1024*1
可以看到,每个container都是用了较多的资源,它们的资源占比相同,符合预期:
严格模式
最终的cfs_quota_us结果为600000:
经过计算cpu.cfs_quota_us / cpu.cfs_period_us = 600000 / 1000000 = 0.6cpu。
同时,通过yarn的计算公式,container的核数 = 40(总核数) * 0.9 ( 物理cpu使用率) * 1 (个vcores ) / 60 ( 个总vcores )=0.6cpu。
两者结果相同,最终可以看到container最大使用为0.6,符合上述计算:
5. Cgroup CPU资源隔离效果review
通过设置nodemanager最大25%、10%、5%的使用限额,发现最高就使用了25%、10%、5%,因此资源隔离生效。
6. 问题及相关解决方法
6.1 启动nodemanager失败报错
`Caused by: java.io.IOException: Not able to enforce cpu weights; cannot write to cgroup at: /sys/fs/cgroup/cpu`
挂载异常,可以重新进行自动挂载:umount /sys/fs/cgroup/cpu
取消挂载,然后让yarn组件自动挂载。
6.2 flink出现性能降级
部署cgroup时,线上的jdk是8u191之前的版本,它对cgroup的支持不好,无法正确识别限制的cpu核数。如下,设置container运行48核,但是flink task只识别出2个核。导致作业性能下降80%:
如下,核数识别失败:
在jdk191以上版本中,支持开启-XX:+UseContainerSupport,提示jvm这是容器环境:
可以看到,升级了jdk版本的flink作业中,能够正确识别CPU限制:
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |