本文我们来对 YARN 的 Linux Container Executor 进行一次非常详细的解析。
LCE 详解
一、 什么是 LCE?它的定位是什么?
Linux Container Executor 是 Apache Hadoop YARN 框架中一种关键的 Container Executor 实现。
- 核心职责:Container Executor 是 YARN NodeManager 的一个组件,它负责在物理机或虚拟机上安全地启动和清理 YARN 容器进程。它是 YARN 与底层操作系统之间的“桥梁”。
- LCE 的独特之处:与最简单的 DefaultContainerExecutor(几乎无隔离)不同,LCE 利用 Linux 内核的原生特性 来为每个 YARN 容器提供资源隔离和一定的运行隔离。它的目标是让 YARN 能够以一种类似容器的方式运行任务,而不依赖于 Docker 这样的重型工具。
简单来说,LCE 是 YARN 在云原生时代之前,实现生产级资源管理和多租户隔离的核心技术。
二、 LCE 的核心工作原理
LCE 的本质是一个由 C 语言编写的、设置了特殊权限的二进制可执行文件。它通过操控 Linux 内核的以下两大机制来实现隔离:
1. cgroups - 资源隔离
cgroups 是 Linux 内核的一个功能,用于限制、记录和隔离一组进程所使用的物理资源。
- 内存 (
memory):
- LCE 为每个容器创建一个 cgroup,并设置
memory.limit_in_bytes参数。 - 当容器进程尝试使用的内存超过此硬性限制时,Linux 内核的 OOM Killer 会被触发,强制终止该容器中最“罪魁祸首”的进程(通常是 Java 进程)。这对应了 YARN 中常见的
Container killed due to memory usage错误。
- CPU (
cpu/cpuacct):
- CPU 份额:通过
cpu.shares来设置。这不是一个绝对的上限,而是一个优先级权重。例如,一个设置了 1024 shares 的容器,在 CPU 竞争时获得的时长是设置了 512 shares 容器的两倍。 - CPU 硬上限:通过
cpuset.cpus可以将容器绑定到特定的 CPU 核上,实现严格的 CPU 隔离和避免上下文切换开销。这对于性能敏感的作业非常有用。 - CPU 时间统计:
cpuacct控制器用于报告容器使用的 CPU 时间。
- 磁盘 I/O (
blkio):
- 可以限制容器对块设备(如硬盘)的读写速率,防止某个容器耗尽整个节点的 I/O 带宽。
2. namespaces - 环境隔离
namespaces 负责对全局系统资源进行封装,使得在一个 namespace 中的进程拥有独立的资源视图,仿佛运行在一个独立的系统中。
- PID namespace:容器内的进程只能看到自己 namespace 内的进程,其 PID 从 1 开始编号。这提供了进程树的隔离。
- Network namespace (可选):为容器提供独立的网络设备、IP 地址、端口范围、路由表等。注意:在早期 YARN 版本中,默认不启用网络隔离,因为这会增加与 Hadoop 生态其他组件(如 HDFS)通信的复杂性。启用后需要进行额外配置。
- Mount namespace:容器拥有独立的文件系统挂载点视图,不会看到宿主或其他容器的挂载信息。
- UTS namespace:允许容器拥有自己的主机名和域名。
- User namespace (较少使用):映射容器内外的 UID/GID,增强安全性。
三、 LCE 的架构与执行流程
- 用户提交应用:用户通过
yarn jar或 REST API 向 ResourceManager 提交应用。 - 资源分配:ResourceManager 进行调度,决定在哪个 NodeManager 上启动 ApplicationMaster 和任务容器。
- NodeManager 接收指令:NodeManager 从 ResourceManager 收到启动容器的指令。
- 调用 LCE:NodeManager 不以目标用户身份直接启动 Java 进程,而是调用配置好的 LCE 二进制文件(
container-executor)。 - LCE 执行特权操作:
- LCE 二进制文件具有
setuid权限(属主通常是root),它以 root 权限 运行。 - 它根据配置和指令,进行一系列操作:
- 创建 cgroup:在对应的 cgroup 子系统下为容器创建目录。
- 设置资源限制:向 cgroup 文件(如
memory.limit_in_bytes)写入限制值。 - 切换用户:使用
seteuid等系统调用,从 root 切换到容器指定的运行用户(如yarn)。这是为了安全,防止用户代码以 root 权限运行。 - 准备环境:设置工作目录、环境变量等。
- 启动进程:最终,
fork并exec目标进程(如 Java 虚拟机)。这个子进程会自动继承其父进程(LCE)的 namespaces 和 cgroups 设置,从而“活在”一个被隔离的容器中。
- 进程运行:容器进程在隔离的环境中运行。
- 清理:当容器结束时,NodeManager 会再次调用 LCE 来清理 cgroup 和任何其他资源。
四、 LCE 的关键配置
LCE 的配置主要涉及两个文件:
container-executor.cfg:
这是 LCE 二进制文件本身的配置文件,通常位于$HADOOP_HOME/etc/hadoop/或类似路径。
# LCE 二进制文件所在的目录
yarn.nodemanager.linux-container-executor.path=/path/to/bin/container-executor
# 容器运行时使用的 cgroup 层级结构
yarn.nodemanager.linux-container-executor.cgroups.hierarchy=/hadoop-yarn
# 允许使用 LCE 的用户和组(通常是 yarn 用户)
yarn.nodemanager.linux-container-executor.group=yarn
# 是否启用 cgroup 的 memory 子系统
yarn.nodemanager.linux-container-executor.cgroups.mount=true
yarn.nodemanager.linux-container-executor.cgroups.strict-resource-usage=true
# 允许运行的容器用户列表(白名单)
yarn.nodemanager.linux-container-executor.allowed.system-users=user1,user2,yarn
# 用于存储容器临时文件的目录,需要特定权限
yarn.nodemanager.linux-container-executor.dirs.handler.class=org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutorDirectoriesHandleryarn-site.xml:
在 YARN 的主配置文件中,需要告诉 NodeManager 使用 LCE。
<property>
<name>yarn.nodemanager.container-executor.class</name>
<value>org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor</value>
</property>五、 LCE 的优缺点
优点:
- 轻量级:直接使用内核特性,无需额外的守护进程(如 Docker Daemon),启动速度快,资源开销小。
- 成熟稳定:在 Hadoop 生态中经过多年实践检验,非常可靠。
- 与 Hadoop 生态无缝集成:特别是与 HDFS 的本地认证和 Kerberos 安全集成得很好。
- 资源控制精确:能够对 CPU、内存等核心资源进行有效和可预测的限制。
缺点:
- 环境一致性差:容器依赖宿主机节点的底层环境(如 Java 版本、库文件)。这也就是常说的“它在我这儿能跑,在你那儿为啥不行?”的问题。
- 依赖管理复杂:需要将应用依赖(如 Jar 包、Python 环境)通过分布式缓存(
-archives,-files)分发到各个节点,管理繁琐。 - 网络隔离配置复杂:默认不启用,启用后需要精细的网络配置。
- 镜像构建和分发能力弱:不像 Docker 那样有强大的镜像分层、构建和分发生态。
- 与云原生趋势脱节:业界主流已转向以 Kubernetes 为代表的、以镜像为核心的云原生范式。
总结
LCE 是 YARN 为了在大数据领域实现多租户、资源隔离和安全性而设计的一个精巧的“内核级”解决方案。 它在 Docker 和 Kubernetes 普及之前,是构建大规模、共享式 Hadoop 集群的基石技术。
然而,随着以 Docker 镜像 为代表的环境一致性和以 Kubernetes 为代表的声明式编排成为云原生时代的主流,LCE 所代表的“轻量级进程隔离+分布式文件分发”模式在易用性和现代化方面逐渐显现出不足。这也是为什么 Flink、Spark 等社区都在大力推动其与 Kubernetes 原生集成的原因。
















