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 的调度流程大致分三步:
- 过滤(Predicates):选出满足 Pod 资源需求的节点。
- 打分(Priorities):对候选节点打分,比如资源利用率、亲和性等。
- 绑定(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 的资源调度模型差异:
3.5 如何选择?
没有的答案。我根据实际经验给几个建议:
- 如果你在维护 Hadoop 集群,YARN 仍然是成熟稳定的选择。尤其是大内存作业(单个 executor 超过 32GB),YARN 的进程级隔离开销更小。
- 如果你在建设云原生数据湖,K8s 是趋势。容器化带来的弹性伸缩、多租户隔离、混合部署优势很明显。
- 如果你两者都要,可以考虑 YARN on K8s 的方案(比如 YuniKorn),但复杂度会上升。
个人经验:我去年帮一个客户做数据湖迁移,他们从 CDH 集群迁到 K8s。最痛苦的不是调度模型差异,而是网络模型。YARN 下 Spark 用 host network,性能很好;K8s 下用 CNI 插件,网络开销增加了 10%-15%。后来我们换成 Macvlan 才解决。