【YARN 权威指南】11. 从 Distributed-Shell 理解 YARN 应用


Distributed-Shell 是理解 YARN 应用模型的一个好工具。它本身不复杂:用户提交一个 shell 命令,ApplicationMaster 通过 ResourceManager 申请若干 Container,真正执行命令的是 NodeManager 在 Container 内启动的进程。正因为它简单,反而能把 YARN 应用的骨架暴露出来。

MapReduce 会把很多细节封装起来。用户看到的是 Map、Reduce、InputSplit、Shuffle、Counter、JobHistory,很容易把注意力放在 MapReduce 框架上。Distributed-Shell 没有这些业务语义,它只保留 Client、ApplicationMaster、Container、队列、资源、日志这些 YARN 基础对象。用它观察 YARN,干扰更少。

这次我提交了一个 3 个 Container 的 Distributed-Shell 应用:

hadoop jar /opt/hadoop/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.4.1.jar \
  -jar /opt/hadoop/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.4.1.jar \
  -appname YarnDistributedShellDemo \
  -queue adhoc \
  -master_memory 512 \
  -container_memory 256 \
  -num_containers 3 \
  -shell_command 'echo YARN_DISTRIBUTED_SHELL_20250305; hostname; date; sleep 8'

运行结果如下:

Application ID: application_1741162032445_0001
Application Name: YarnDistributedShellDemo
Application Type: YARN
Queue: root.adhoc
State: FINISHED
Final-State: SUCCEEDED
Log Aggregation Status: SUCCEEDED

ResourceManager 中的 Distributed-Shell 应用详情

1. Distributed-Shell 的对象模型

1.1 Distributed-Shell 是最小化的 YARN 应用

一个完整 YARN 应用至少包含三个部分:

部分 Distributed-Shell 中的体现
Client 解析参数、准备资源、提交 Application
ApplicationMaster 注册 RM、申请 Container、构造启动上下文
Container NodeManager 启动的运行时资源单元,内部进程执行用户命令

Distributed-Shell 的好处是这三层非常清楚。Client 不负责执行 shell;ApplicationMaster 不负责全局调度;Container 不负责申请资源。每一层只做自己的事情。

这和 MapReduce on YARN 的底层运行模型一致,但应用语义不同。MapReduce 的普通 Container 执行 Map/Reduce Task,并额外包含 InputSplit、Shuffle、Counter、JobHistory 等作业语义;Distributed-Shell 的普通 Container 只执行 shell 命令。YARN 看到的不只是 Application 和 Container,还包括 application attempt、resource request 和 container lifecycle;框架语义由 AM 自己管理。

1.2 参数背后对应的是 YARN 对象

Distributed-Shell 的命令行参数看起来很多,但可以按对象归类:

参数 对应对象 含义
-appname Application 应用名称
-queue Scheduler 提交队列
-priority Scheduler 应用优先级
-master_memory AM Container AM 内存
-master_vcores AM Container AM vCore
-container_memory 普通 Container shell Container 内存
-container_vcores 普通 Container shell Container vCore
-num_containers AM 逻辑 要启动多少普通 Container
-shell_command ContainerLaunchContext Container 执行命令
-shell_script LocalResource 上传并执行脚本
-localize_files LocalResource 本地化额外文件

这样看,Distributed-Shell 不是命令手册,而是一组 YARN 对象的入口。每个参数最终都会落到 ApplicationSubmissionContext、ResourceRequest、ContainerLaunchContext 或 LocalResource 里。

这也解释了为什么一个看似简单的 shell 命令,进入 YARN 后会变成多个组件协作。Client 要提交应用,RM 要调度 AM,AM 要申请普通 Container,NM 要完成本地化并启动脚本,日志聚合要收集输出。

1.3 shell_command 和 shell_script 的差别

shell_command 适合短命令,例如:

echo YARN_DISTRIBUTED_SHELL_20250305; hostname; date; sleep 8

它最终会进入 ContainerLaunchContext,再由 NodeManager 写入 Container 启动脚本并执行。短命令的优点是直观,缺点是复杂逻辑容易被 shell 引号、特殊字符、重定向和环境变量影响。如果命令来自用户输入或平台拼接,还要特别注意 shell injection 风险。

shell_script 更适合复杂逻辑。Client 会把脚本作为资源上传,AM 再把它作为 LocalResource 提供给普通 Container。这样做的好处是脚本内容更可控,也更容易版本化。生产环境里,如果命令超过一两行,我更倾向于使用脚本,而不是把所有逻辑塞进 shell_command。

这次测试里我使用短命令,是为了让日志输出更直接。日志聚合中能看到每个普通 Container 都执行了标记输出:

Container: container_1741162032445_0001_01_000002
YARN_DISTRIBUTED_SHELL_20250305

Container: container_1741162032445_0001_01_000003
YARN_DISTRIBUTED_SHELL_20250305

Container: container_1741162032445_0001_01_000004
YARN_DISTRIBUTED_SHELL_20250305

这说明 AM 成功申请并启动了 3 个普通 Container,每个 Container 内都由 NodeManager 启动进程执行了同一条 shell 命令。

1.4 num_containers 决定并行度

num_containers=3 的意思不是启动 3 个 ApplicationMaster,而是一个 AM 管理 3 个普通 Container。AM Container 由 YARN 为应用控制面自动创建,不计入用户指定的 num_containers。ResourceManager 页面里的 Attempt 表只显示一个 ApplicationMaster attempt,这是正常的。

这次应用的 Container 关系可以写成:

application_1741162032445_0001
  appattempt_1741162032445_0001_000001
    container_1741162032445_0001_01_000001  ApplicationMaster
    container_1741162032445_0001_01_000002  shell command
    container_1741162032445_0001_01_000003  shell command
    container_1741162032445_0001_01_000004  shell command

这个结构很重要。YARN 应用的并行度通常由普通 Container 数量表达,而不是 ApplicationMaster 数量。AM 是控制面,普通 Container 是执行面。这里的并行度也不等于任务切分,Distributed-Shell 只是启动多个执行单元,并不会自动把输入数据拆成多份。

如果 num_containers 很大,AM 会持续申请资源,调度器会按队列容量、节点可用资源、用户限制和 AM 资源比例逐步分配。并不是用户写了 100,集群就一定同时启动 100 个 Container。资源不足时,AM 只能等待、处理部分分配,或者按自身策略做 retry/backoff。

1.5 master_memory 和 container_memory 不要混淆

Distributed-Shell 很适合说明 AM 资源和普通 Container 资源的区别。

master_memory=512
container_memory=256

master_memory 控制 AM Container。AM 需要运行 Java 进程、维护状态、和 RM/NM 通信,所以它有自己的资源请求。container_memory 控制普通 shell Container。它们的生命周期和职责不同,不能混在一起调优。这里的内存是 NodeManager 管理的资源边界,不等于进程或 JVM 实际只会使用这么多内存。

常见误判是:普通任务 OOM,就只调 master_memory;应用停在 ACCEPTED,却只调 container_memory。正确做法是先看卡在哪一层:

现象 优先检查
应用停在 ACCEPTED AM 资源、队列 AM 限制、节点可用资源
AM 启动后普通 Container 不分配 普通 Container 资源、队列容量、用户限制
普通 Container 被杀 container_memory、命令内存、NodeManager 日志
AM 自己失败 master_memory、AM 日志、LocalResource

Distributed-Shell 虽然简单,但这些边界和复杂框架完全一致。

1.6 队列决定应用进入哪套资源规则

这次应用提交到 adhoc 队列,RM 页面显示完整队列是 root.adhoc。这说明短队列名最终会映射到调度器里的完整队列路径。

队列不是分类标签或显示字段,而是资源策略集合。应用进入 root.adhoc 后,要受这个队列的容量、最大容量、用户限制、ACL、应用数量限制和 AM 资源限制影响。Distributed-Shell 只是一个轻量应用,但它同样受调度器约束。Capacity Scheduler 更强调容量和层级队列,Fair Scheduler 更强调公平共享,不同调度器会影响等待、分配和抢占行为。

这对平台封装很重要。很多调度平台会把队列参数藏在页面选项里,用户只知道选择“临时查询”或“ETL”。但最终提交到 YARN 时,仍然要变成一个明确的队列路径。排查队列问题时,不要只看平台页面,要看 RM 应用详情里的实际 Queue。

1.7 日志聚合证明 Container 真正执行过

ResourceManager 页面能证明应用成功,但不能证明每个 Container 执行了什么。要确认 shell 命令真正跑过,需要看日志聚合:

yarn logs -applicationId application_1741162032445_0001

这次日志显示 3 个普通 Container 都输出了 YARN_DISTRIBUTED_SHELL_20250305。这比只看 SUCCEEDED 更可靠,因为它证明执行发生在普通 Container 里,而不是只证明 AM 汇报了成功。

日志里还可以看到命令被包装成:

exec /bin/bash -c "echo YARN_DISTRIBUTED_SHELL_20250305; hostname; date; sleep 8 ..."

这说明 NodeManager 最终执行的是生成后的启动脚本。用户提交的是 shell 命令,AM 构造 ContainerLaunchContext,NM 完成本地化、生成 launch_container.sh,最后 exec 用户命令。这个链路一旦理解,很多日志路径、stdout/stderr/syslog、Container 工作目录的问题就好解释了。

日志也要分层看:stdout 是用户命令的标准输出,stderr 更容易包含脚本错误和异常栈,syslog 则通常包含启动脚本、环境准备和 NodeManager 侧的执行信息。

2. 验证价值和使用边界

2.1 Distributed-Shell 适合做验证工具

Distributed-Shell 不适合当生产计算框架,但很适合做 YARN 验证工具。它能快速验证:

验证点 为什么有用
Client 能提交应用 配置和 RM 地址可用
AM 能启动 队列和 AM 资源可用
普通 Container 能启动 NM、资源、本地化可用
shell 输出能聚合 日志聚合可用
多 Container 能完成 AM 申请和监控链路可用

相比 MapReduce Pi,Distributed-Shell 更接近“框架无关”的 YARN 验证。它不依赖输入数据,不需要 Shuffle,也不需要 MapReduce JobHistory。只要 YARN 基础链路可用,它就能跑起来。

因此,在搭建或调整 YARN 集群后,我会把 Distributed-Shell 当作补充验证:MapReduce Pi 验证 MapReduce on YARN,Distributed-Shell 验证普通 YARN 应用模型。两者结合,能覆盖更多边界。

2.2 它也暴露了自定义应用的成本

Distributed-Shell 看起来简单,但它也说明自定义 YARN 应用并不轻。哪怕只是跑 shell 命令,也要处理:

  1. Client 参数解析。
  2. Application 提交。
  3. AM jar 上传和本地化。
  4. AM 注册和注销。
  5. 资源申请。
  6. ContainerLaunchContext 构造。
  7. NodeManager 启动协议。
  8. Container 状态监控。
  9. 日志收集和失败处理。

这就是为什么很多团队不会直接写原生 YARN 应用,而是使用 MapReduce、Spark、Flink、Tez 等更高层框架。原生 YARN API 给了最大控制权,也带来了更多工程负担。

如果确实要写自定义 YARN 应用,Distributed-Shell 是很好的起点。它展示了最小可运行结构,也暴露了哪些地方必须自己处理。把它改造成生产应用时,至少要补齐参数校验、资源清理、失败重试、日志可观测性、安全凭据和幂等提交。

2.3 Distributed-Shell 和调度平台的关系

很多数据平台都有“Shell 任务”类型。表面看,它们和 Distributed-Shell 很像:用户填写一段命令,平台提交到集群执行,最后收集日志和状态。但两者关注点不同。

Distributed-Shell 是 YARN 示例应用,重点是演示 YARN Client、AM 和 Container 如何配合。调度平台的 Shell 任务则通常还要处理依赖关系、周期调度、补数、失败告警、参数替换、权限控制和结果归档。

如果平台底层使用 YARN,那么它最终仍然要回答 Distributed-Shell 暴露出来的这些问题:

问题 平台也必须处理
命令在哪个 Container 里执行 需要记录 Container ID
提交到哪个队列 需要和租户/项目绑定
AM 和普通 Container 资源是多少 需要防止用户随意抢资源
stdout/stderr 在哪里 需要能从平台跳转日志
失败退出码是什么 需要区分平台失败和命令失败
命令依赖哪些文件 需要上传或分发资源

所以 Distributed-Shell 不只是一个示例,它也可以作为平台设计的参照。一个平台如果连 Application ID、队列、Container 日志都不展示,用户排查 Shell 任务时会非常困难。

2.4 shell 任务的退出码和应用状态

YARN 应用的最终状态和 shell 命令的退出码之间有映射关系,但不是同一个层次。shell 命令在普通 Container 里执行,退出码先被 NodeManager 记录,再由 AM 接收 Container 完成状态,最后 AM 决定向 RM 汇报成功还是失败。

这条链路可以写成:

shell exit code
  -> Container exit status
  -> AM completed container handling
  -> FinalApplicationStatus
  -> RM application final state

如果 shell 命令返回非 0,普通 Container 会失败。AM 可以选择重试,也可以直接让应用失败。Distributed-Shell 的策略比较直接,复杂框架则可能有更丰富的重试策略。

这对使用者很重要。看到 RM 上应用 FAILED 时,不要只看 RM 诊断信息,还要进入 Container stderr/stdout。RM 只能告诉你应用最终失败,真正的命令错误通常在 Container 日志里。反过来,如果应用 SUCCEEDED,也不代表业务输出一定正确,只能说明 AM 向 RM 汇报的 FinalApplicationStatus 是成功。

生产平台最好同时展示:

  1. YARN Application 状态。
  2. AM 最终汇报状态。
  3. 失败 Container ID。
  4. shell 退出码。
  5. exit code、diagnostics 和 stdout/stderr 里的唯一日志标记。

这几层合起来,才能说清楚一次 shell 任务到底发生了什么。

2.5 多 Container 并行不等于分布式计算框架

Distributed-Shell 可以启动多个 Container,但这不等于它就是完整的分布式计算框架。它只是把同一条命令分发到多个 Container 执行。Container 之间默认没有数据交换、任务切分、结果合并、容错协议或全局调度语义。

这点很容易被误解。num_containers=3 只是让三个 Container 都执行命令。它不会自动把输入数据切成三份,也不会自动合并输出。如果命令本身没有处理并行数据的逻辑,多个 Container 只是重复执行。

要把它变成真正的分布式应用,还需要补充:

能力 Distributed-Shell 默认是否提供
输入切分
任务分配 很弱,只按数量启动
结果汇总
任务重试语义 有基础能力,但不面向复杂作业
数据本地性 不像 MapReduce 那样内建 node-local/rack-local 语义
作业级 Counter
Shuffle / merge

因此,Distributed-Shell 更适合验证和轻量任务,不适合替代 MapReduce、Spark 或 Flink。它能展示 YARN 的资源运行模型,但不会替你实现计算框架语义。

2.6 用 Distributed-Shell 验证集群时的推荐命令

我更推荐准备两类命令。

第一类是最小验证命令,用来证明应用能提交、AM 能启动、日志能聚合:

hadoop jar $DS_JAR \
  -jar $DS_JAR \
  -appname YarnSmokeTest \
  -queue default \
  -master_memory 512 \
  -container_memory 256 \
  -num_containers 1 \
  -shell_command 'echo YARN_OK; hostname; date'

第二类是多 Container 验证命令,用来证明 AM 能申请多个 Container:

hadoop jar $DS_JAR \
  -jar $DS_JAR \
  -appname YarnMultiContainerTest \
  -queue adhoc \
  -master_memory 512 \
  -container_memory 256 \
  -num_containers 3 \
  -shell_command 'echo YARN_MULTI; hostname; sleep 10'

执行后至少检查三件事:

yarn application -status <application_id>
yarn logs -applicationId <application_id>

以及 RM 页面里的队列、状态和 AM attempt。yarn application -status、yarn logs 和 RM UI 三处的 Application ID、队列、状态、日志标记能对上,才说明 YARN 的基础提交、调度、执行和日志链路是通的。

2.7 Distributed-Shell 的局限也要写清楚

用 Distributed-Shell 做文章或验证时,要避免把它说得过大。它不是生产级编排系统,也不是通用任务调度器。它的局限包括:

  1. 对复杂参数和 shell 引号敏感。
  2. 不负责业务级输入输出管理。
  3. 不提供强作业语义。
  4. 不适合长时间服务治理。
  5. 失败处理能力有限。
  6. 安全和权限仍依赖 YARN/HDFS 配置。
  7. 缺少计算模型抽象,不能替代 Spark、MapReduce 或 Flink。

这些局限不影响它作为学习工具的价值。相反,正因为它功能少,才容易看清 YARN 本身的边界。MapReduce 把任务模型、Shuffle、Counter 都封装起来;Distributed-Shell 把这些都拿掉,只留下 YARN 应用骨架。

我更愿意把它定位为三个用途:

用途 说明
学习 YARN 应用结构 看清 Client、AM、Container
验证集群基础链路 检查提交、调度、日志聚合
自定义应用起点 参考最小 AM 实现

超过这三个用途,就要谨慎评估是否应该使用更高层框架。

2.8 它适合验证哪些 YARN 配置

Distributed-Shell 可以覆盖不少基础配置,但也有边界。适合用它验证的配置包括:

配置方向 验证方式
RM 地址 Client 能否提交应用
队列配置 应用是否进入指定队列
AM 资源 应用是否能从 ACCEPTED 进入 RUNNING
NM 可用性 普通 Container 是否能启动
日志聚合 yarn logs 是否能看到 stdout
HDFS staging AM jar 和资源是否能上传
用户权限 队列 ACL 和日志访问是否正常

不适合用它验证的内容也要分清。比如 MapReduce Shuffle、InputSplit、Reduce 合并、Spark executor 行为、Flink checkpoint,这些都不是 Distributed-Shell 的职责。它能证明 YARN 基础链路可用,不能证明所有计算框架都可用。

因此,集群验收时可以组合使用:

  1. Distributed-Shell 验证普通 YARN 应用链路。
  2. MapReduce Pi 验证 MapReduce on YARN。
  3. 业务框架自带示例验证具体框架。

这样比只跑一个 Pi 更完整。Pi 成功说明 MapReduce 链路好,Distributed-Shell 成功说明 YARN 原生应用链路好,两者互补。

2.9 它也能帮助解释 Container 数量

初学 YARN 时,经常有人问:为什么我设置了 num_containers=3,日志里却看到 4 个 Container?答案是 AM 自己也是 Container。

这次应用就是:

application_1741162032445_0001
  appattempt_1741162032445_0001_000001
    1 个 AM Container
    3 个普通 shell Container

ResourceManager 应用详情页的 Attempt 表展示的是 AM attempt,不会直接把所有普通 Container 列成作业任务列表。普通 Container 需要从日志聚合、NM 记录或 RM/NM 相关接口里看。

这个现象对理解 Spark、Flink 也有帮助。很多框架也会有 driver、AM、executor、taskmanager 等不同角色,它们和 Container 的对应关系并不总是一对一。先用 Distributed-Shell 看清“AM 也是 Container”,后面理解复杂框架会容易很多。

3. 命令组织和环境差异

3.1 命令越复杂,越应该脚本化

Distributed-Shell 支持直接传 shell_command,但这只适合简单命令。命令复杂以后,建议使用脚本文件。原因很实际:

直接命令的问题 脚本化的好处
引号容易错 脚本可以本地测试
多行难维护 脚本结构清晰
参数不可控 可以统一解析参数
日志难读 可以加明确输出
失败定位困难 可以分步骤返回错误

如果脚本要随应用分发,就把它作为 LocalResource 上传。Container 启动后在本地化目录执行脚本。这样既不依赖每台节点预装脚本,也能保证版本和任务一致。安全模式下还要确认 HDFS 路径权限、delegation token 和 localization 权限都可用。

对于平台来说,这也意味着“Shell 任务”最好不要只保存一段命令文本。更好的做法是保存脚本版本、提交参数、资源配置和 Application ID。否则任务失败后,很难还原当时到底执行了什么。

3.2 用它校验队列和用户边界

Distributed-Shell 很适合做队列和权限验证。原因是它的业务逻辑足够简单,成功或失败通常不会被计算框架内部逻辑干扰。把同一条命令分别提交到 default、etl、adhoc 队列,可以很快确认队列是否存在、是否允许提交、资源是否能分配、日志是否能查看。

如果提交到错误队列,RM Application 页面会直接显示队列归属。这个字段比命令行参数更可信,因为它是 RM 接收后的事实。平台封装队列参数时,也应该以 RM 返回结果为准,而不是只相信用户传入的值。

权限验证同样如此。一个用户能否提交到某个队列,能否查看日志,能否 kill 应用,都应该用真实应用验证。配置文件里写了 ACL,不代表实际链路一定正确;代理用户、HDFS 权限、日志目录权限都会影响结果。

建议把验证拆成几组:

验证项 观察点
队列提交 Application 是否进入目标队列
资源分配 AM 是否从 ACCEPTED 进入 RUNNING
普通 Container stdout 是否包含预期标记
日志读取 yarn logs 是否返回完整输出
终止权限 非管理员用户是否能 kill 应用

这些验证不需要复杂数据,也不需要准备输入目录。一个简单命令就能覆盖 YARN 的核心控制面。

3.3 它能暴露 shell 环境差异

很多在登录 shell 中能执行的命令,放进 YARN Container 后会失败。Distributed-Shell 正好可以暴露这种差异。Container 不是用户 SSH 登录后的终端,而是 NodeManager 启动的隔离运行环境,通常也不是 login shell。它的工作目录、环境变量、PATH、文件可见性都由 NodeManager 和启动上下文决定。

因此,用它做验证时,不要只执行 echo ok。更有价值的命令应该打印环境信息:

hostname
pwd
id
umask
printenv | sort | head -30
ls -la

这些输出能回答几个实际问题:Container 在哪台节点运行,运行用户是谁,当前目录是什么,LocalResource 是否已经进入目录,PATH 是否包含必要命令,HADOOP 相关变量是否存在。

如果一个平台要支持“提交 shell 脚本到 YARN”,这些信息尤其重要。用户经常以为脚本会继承自己登录机器上的环境,实际并不会。平台应该在文档和错误信息里明确说明:脚本运行在 YARN Container 中,依赖文件要通过资源分发或共享存储提供,环境变量要显式传入,不能默认继承提交机的 PATH 和 shell 配置。

4. 平台验收和示例价值

4.1 它不是只给开发者看的示例

Distributed-Shell 常被当成示例应用,但它对运维也有价值。集群出现问题时,复杂框架失败不一定能立即定位原因。Spark 失败可能是 executor 参数问题,MapReduce 失败可能是输入目录或 Shuffle 问题,Flink 失败可能是 checkpoint 或作业图问题。Distributed-Shell 失败则更接近 YARN 基础链路问题。

例如:

现象 优先怀疑
Client 无法提交 RM 地址、认证、网络
一直 ACCEPTED 队列资源、调度器、AM 资源
AM 启动失败 LocalResource、启动命令、HDFS 权限
普通 Container 不启动 NM 状态、资源分配、队列限制
日志不可读 日志聚合、HDFS 权限、History 服务

这套判断可以作为集群巡检脚本的一部分。每天提交一个短命令,检查它能否成功、耗时是否异常、日志是否可读。相比只看进程存活,这种端到端验证更接近用户真实体验。

4.2 和 MapReduce 示例的区别

MapReduce Pi 也能验证 YARN,但它验证的是 MapReduce on YARN。它会启动 MRAppMaster,创建 map/reduce task,涉及 JobHistoryServer、Counter、Shuffle 等组件。Distributed-Shell 则不关心这些,它只验证 YARN 应用骨架。

两者的差异可以这样理解:

对象 MapReduce Pi Distributed-Shell
应用类型 MAPREDUCE YARN
AM 行为 管理作业和 task 管理 shell Container
业务语义 map/reduce/counter shell 命令退出码
Shuffle 需要 不需要
日志 任务日志和作业历史 AM 与 shell Container 日志

因此,不能用一个替代另一个。MapReduce Pi 成功,说明 MapReduce 链路可用;Distributed-Shell 成功,说明 YARN 原生应用链路可用。如果两个都失败,优先查 YARN 基础设施;如果只有 MapReduce 失败,再查 MapReduce 配置和 Shuffle。

4.3 如何把它用于平台验收

如果要把 YARN 接入一个内部平台,Distributed-Shell 可以作为最小验收任务。平台只需要暴露应用名、队列、Container 数量、内存和 shell 命令几个参数,就能完成一次端到端提交。

验收结果至少要保存:

字段 说明
application_id 底层应用编号
app_name 平台任务名
queue 实际进入的队列
state/finalStatus YARN 最终状态
container_count 期望和实际 Container 数
log_marker stdout 中的唯一标记
elapsed_time 从提交到完成的耗时

这里的 log_marker 很实用。每次提交都生成一个包含 UUID 和 timestamp 的唯一字符串,让 shell 命令打印出来,再通过 yarn logs 查找。这样可以证明日志来自本次任务,而不是历史输出或误打开的页面。

平台验收还要关注失败路径。比如传入不存在的脚本、非法队列、过大的内存、无权限用户,平台应该给出明确错误,而不是只显示 YARN 的原始异常。Distributed-Shell 足够轻量,适合用来覆盖这些负向测试。

4.4 不要忽略返回码语义

Distributed-Shell 最终是否成功,很大程度取决于普通 Container 中命令的退出码。echo ok 会成功,exit 1 会失败,多条命令串联时如果没有正确处理 set -e 或管道返回码,就可能出现业务步骤失败但 Container 仍然成功的情况。

这点对平台封装很重要。平台如果允许用户提交 shell,应该要求脚本明确返回码,并把关键错误写入 stderr。否则 YARN 只能看到进程退出状态,看不到业务语义。一个脚本内部吞掉错误、最后返回 0,RM 页面会显示应用成功,但业务结果可能已经错误。

更稳妥的脚本习惯包括:

set -euo pipefail
echo "start job"
run_step_one
run_step_two
echo "finish job"

同时要避免把所有命令写成一行。命令越长,越难判断哪个步骤失败。YARN 能负责启动和收集日志,但不能替脚本设计错误处理。Distributed-Shell 把这个边界暴露得很直接:Container 成功不等于业务一定正确,业务正确性仍然要由脚本或上层平台负责表达。

5. 小结

Distributed-Shell 的价值不在于执行 shell,而在于把 YARN 应用模型简化到最小。它可以看作 canonical YARN execution model reference:RM 是 scheduler,AM 是 coordinator,NM 是 executor,Container 是 runtime unit,Task 或 shell 进程才是 user logic。日志聚合负责留下证据。

通过 application_1741162032445_0001 可以看到,一条 shell 命令进入 YARN 后,会变成一个完整的分布式应用生命周期。理解这个过程后,再看 MapReduce、Spark 或其它框架,就不会只盯着框架自己的概念,而能看见底层共同的 YARN 运行模型。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录