本文我们来对 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 的架构与执行流程
  1. 用户提交应用:用户通过 yarn jar 或 REST API 向 ResourceManager 提交应用。
  2. 资源分配:ResourceManager 进行调度,决定在哪个 NodeManager 上启动 ApplicationMaster 和任务容器。
  3. NodeManager 接收指令:NodeManager 从 ResourceManager 收到启动容器的指令。
  4. 调用 LCE:NodeManager 不以目标用户身份直接启动 Java 进程,而是调用配置好的 LCE 二进制文件(container-executor)。
  5. LCE 执行特权操作
  • LCE 二进制文件具有 setuid 权限(属主通常是 root),它以 root 权限 运行。
  • 它根据配置和指令,进行一系列操作:
  • 创建 cgroup:在对应的 cgroup 子系统下为容器创建目录。
  • 设置资源限制:向 cgroup 文件(如 memory.limit_in_bytes)写入限制值。
  • 切换用户:使用 seteuid 等系统调用,从 root 切换到容器指定的运行用户(如 yarn)。这是为了安全,防止用户代码以 root 权限运行。
  • 准备环境:设置工作目录、环境变量等。
  • 启动进程:最终,forkexec 目标进程(如 Java 虚拟机)。这个子进程会自动继承其父进程(LCE)的 namespaces 和 cgroups 设置,从而“活在”一个被隔离的容器中。
  1. 进程运行:容器进程在隔离的环境中运行。
  2. 清理:当容器结束时,NodeManager 会再次调用 LCE 来清理 cgroup 和任何其他资源。
四、 LCE 的关键配置

LCE 的配置主要涉及两个文件:

  1. 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.LinuxContainerExecutorDirectoriesHandler
  1. yarn-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 原生集成的原因。