3. 资源管理基础:YARN 与 Kubernetes 资源调度模型、容器化 vs 进程级隔离

聊数据湖任务调度,绕不开资源管理。说白了,你的 Spark 或 Flink 作业跑在谁上面?谁给它分配 CPU、内存?谁决定它能用多少资源?

我这些年接触过的生产环境,基本就两套体系:YARN 和 Kubernetes。前者是 Hadoop 生态的老牌调度器,后者是云原生时代的宠儿。它们俩的调度模型差异很大,底层隔离方式也不同。今天咱们就把这块掰开揉碎讲清楚。

3.1 YARN 资源调度模型

YARN(Yet Another Resource Negotiator)是 Hadoop 2.x 引入的资源管理器。它的核心思想是:把资源管理和作业调度分开。

YARN 里有三个关键角色:

  • ResourceManager(RM):全局老大,负责资源分配和调度决策。
  • NodeManager(NM):每台机器上的代理,负责管理本机资源、启动容器。
  • ApplicationMaster(AM):每个作业的管家,向 RM 申请资源,协调任务执行。

我刚开始接触 YARN 时,总觉得这架构有点绕。后来亲手调过一个 Spark 作业,才明白它的设计意图——AM 相当于作业的“大脑”,它知道作业需要多少 map、多少 reduce,然后动态跟 RM 要资源。

3.1.1 资源单位:Container

YARN 里最小的资源分配单位是 Container。一个 Container 封装了 CPU、内存等资源。注意,YARN 的 Container 是逻辑概念,不是 Docker 容器。

举个例子,你提交一个 Spark 作业:

spark-submit \
  --master yarn \
  --deploy-mode cluster \
  --executor-memory 4g \
  --executor-cores 2 \
  --num-executors 10 \
  --class com.example.MyJob myjob.jar

YARN 会为每个 executor 分配一个 Container,每个 Container 包含 4GB 内存和 2 个 vcore。AM 也会占用一个 Container。

关键点:YARN 的 Container 是进程级隔离。每个 Container 对应一个 JVM 进程,通过 Linux CGroup 做资源限制。但进程之间共享操作系统内核,隔离性不如容器。

3.1.2 调度策略

YARN 支持三种调度器:

调度器 特点 适用场景
FIFO Scheduler 先来先服务,简单粗暴 单用户、小集群
Capacity Scheduler 按队列分配资源,支持弹性 多租户、生产环境
Fair Scheduler 按权重公平分配,支持抢占 资源竞争激烈、延迟敏感

我个人习惯用 Capacity Scheduler。为什么?因为它能保证每个队列的最低资源,又允许空闲时借用。我曾经在一个 200 节点的集群上,给数据湖的 ETL 作业单独划了一个队列,保证它不会被 ad-hoc 查询挤死。

避坑指南:我曾经遇到过一个问题——YARN 的 Container 内存超卖。明明每个 Container 只申请 4GB,但物理内存被撑爆了。后来发现是 yarn.nodemanager.vmem-pmem-ratio 默认是 2.1,虚拟内存可以超配。建议生产环境把这个值改成 1,或者直接关闭虚拟内存检查。

3.2 Kubernetes 资源调度模型

Kubernetes(K8s)是容器编排的事实标准。它的资源模型和 YARN 完全不同。

K8s 的核心概念是 Pod——一组容器的集合,共享网络和存储。每个 Pod 可以包含一个或多个容器,但通常一个 Pod 只放一个主容器。

3.2.1 资源单位:Pod 与 Container

在 K8s 里,资源分配是容器级别的。你可以在 Pod 定义里为每个容器指定 requests 和 limits:

apiVersion: v1
kind: Pod
metadata:
  name: spark-executor
spec:
  containers:
  - name: executor
    image: spark:3.4
    resources:
      requests:
        memory: "4Gi"
        cpu: "2"
      limits:
        memory: "8Gi"
        cpu: "4"

requests 是调度时保证的资源,limits 是容器能使用的上限。K8s 调度器会根据 requests 做决策,而不是 limits。

嗯,这里要注意:K8s 的 CPU 是可压缩资源,超限会被 throttle;内存是不可压缩资源,超限会被 OOM Kill。这个区别在调优时特别重要。

3.2.2 调度流程

K8s 的调度流程大致分三步:

  1. 过滤(Predicates):选出满足 Pod 资源需求的节点。
  2. 打分(Priorities):对候选节点打分,比如资源利用率、亲和性等。
  3. 绑定(Bind):把 Pod 绑定到得分最高的节点。

你想想看,这和 YARN 的“AM 主动申请”模式完全不同。K8s 是调度器主动分配,Pod 自己不用去“要资源”。

核心差异:YARN 是应用驱动的资源分配(AM 申请 → RM 分配),K8s 是调度器驱动的资源分配(Scheduler 决策 → 节点执行)。

3.3 容器化 vs 进程级隔离

这是很多人的知识盲区。YARN 的 Container 和 Docker 容器,虽然都叫“容器”,但隔离级别完全不同。

3.3.1 进程级隔离(YARN Container)

YARN 的 Container 本质是一个操作系统进程。NodeManager 通过 CGroup 限制它的 CPU、内存使用。但:

  • 文件系统:共享宿主机的文件系统,没有独立的 rootfs。
  • 网络:共享宿主机的网络栈,没有独立的网络命名空间。
  • 进程:所有 Container 的进程都在宿主机上可见。

说白了,YARN Container 就是加了资源限制的普通进程。隔离性很弱,但启动快——毫秒级。

3.3.2 容器化隔离(Docker/Podman)

K8s 使用的容器技术(Docker、containerd)提供了完整的操作系统级隔离:

  • 文件系统:每个容器有独立的 rootfs,镜像分层管理。
  • 网络:每个 Pod 有独立的网络命名空间,IP 地址隔离。
  • 进程:容器内的进程在宿主机上被 PID 命名空间隔离。
  • 资源:通过 CGroup 做限制,但比 YARN 更精细。

代价呢?启动慢一些(秒级),镜像拉取、网络配置都有开销。

维度 YARN Container(进程级) K8s Pod(容器化)
启动速度 毫秒级 秒级(含镜像拉取)
隔离性 弱(共享内核、文件系统) 强(独立命名空间)
资源限制 CGroup v1 CGroup v2(更精细)
镜像管理 无 镜像分层、版本控制
适用场景 离线批处理、大内存作业 微服务、混合负载、云原生

注意:容器化隔离虽然强,但也不是银弹。我曾经在 K8s 上跑 Spark Shuffle,发现磁盘 IO 竞争严重。因为多个 Pod 共享宿主机的磁盘,又没有 IO 隔离。后来给每个节点挂载了独立的 SSD,并用本地 PV 解决。

3.4 核心逻辑对比图

下面这张图总结了 YARN 和 K8s 的资源调度模型差异:

YARN vs Kubernetes 资源调度模型对比 YARN 调度模型 ResourceManager NodeManager ApplicationMaster Container (进程) Kubernetes 调度模型 Scheduler Kubelet Pod (容器组) Container (Docker) 核心差异: YARN:应用驱动(AM 申请 → RM 分配),进程级隔离,启动快但隔离弱 K8s:调度器驱动(Scheduler 决策 → 节点执行),容器级隔离,启动慢但隔离强 进程级隔离(YARN) • 共享宿主机文件系统 • 共享网络栈 • 进程全局可见 • 资源限制靠 CGroup v1 容器化隔离(K8s) • 独立 rootfs,镜像分层 • 独立网络命名空间 • PID 命名空间隔离 • 资源限制靠 CGroup v2

3.5 如何选择?

没有的答案。我根据实际经验给几个建议:

  • 如果你在维护 Hadoop 集群,YARN 仍然是成熟稳定的选择。尤其是大内存作业(单个 executor 超过 32GB),YARN 的进程级隔离开销更小。
  • 如果你在建设云原生数据湖,K8s 是趋势。容器化带来的弹性伸缩、多租户隔离、混合部署优势很明显。
  • 如果你两者都要,可以考虑 YARN on K8s 的方案(比如 YuniKorn),但复杂度会上升。

个人经验:我去年帮一个客户做数据湖迁移,他们从 CDH 集群迁到 K8s。最痛苦的不是调度模型差异,而是网络模型。YARN 下 Spark 用 host network,性能很好;K8s 下用 CNI 插件,网络开销增加了 10%-15%。后来我们换成 Macvlan 才解决。