首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 汽车 游戏 社会热点 开源推荐 医疗健康 归档 标签 关于

导致 Spark on Kubernetes 发生 OOM 故障的两个配置错误

摘要

引言 在将几个 Spark 批处理管道从本地基础设施迁移到 Azure Kubernetes Service (AKS) 后不久,我们发现其中一个比较大的作业反复出现执行器内存不足 (OOM) 故障。这些故障出现在 shuffle 阶段,起初看起来像是典型的 Spark 内存调优问题。我们尝试了增加执行器内存、调整执行器数量,并多次重启该作业,但这些方法都未奏效。

Kubernetes OOM AKS shuffle Spark
2026-06-09 1 阅读 约10分钟阅读 作者:Pranav Bhasker
分享:
字号:
引言 在将几个 Spark 批处理管道从本地基础设施迁移到 Azure Kubernetes Service (AKS) 后不久,我们发现其中一个比较大的作业反复出现执行器内存不足 (OOM) 故障。这些故障出现在 shuffle 阶段,起初看起来像是典型的 Spark 内存调优问题。我们尝试了增加执行器内存、调整执行器数量,并多次重启该作业,但这些方法都未奏效。令人费解的是,该管道在迁移前已经稳定运行多年。 最终的根本原因根本不是 Spark 配置问题,而是迁移过程中引入的两项基础设施级设置发生了意想不到的交互:基于 RAM 的本地临时目录(spark.kubernetes.local.dirs.tmpfs=true)以及一条严格的强制所有执行器位于同一节点的 podAffinity 规则。这两项设置共同导致了 shuffle 溢写消耗了节点内存而非磁盘空间,从而引发了反复的 OOM 终止。 本文记录了调查过程、根本原因以及用于解决该问题的配置变更。 系统环境与迁移背景 管道环境 我们的数据平台负责管理生产环境的批处理管道,支持美国某大型金融机构对交易数据进行大规模地聚合和转换。相关工作负载每天处理约 3GB 的定宽平面文件。该文件包含多种交错的记录布局,需要进行多次解析和合并操作,这使得该任务的 shuffle 操作强度远高于其 3GB 的输入大小所暗示的水平。在本地环境中,这些管道已经稳定运行了三年多,执行情况稳定,而且未出现这样的内存不足(OOM)模式。 AKS 集群配置 事件发生的环境如下: 迁移背景 迁移至 AKS 是更广泛的云现代化计划的一部分。这些工作负载被视为“平移”的候选对象,只要匹配 CPU 和内存配置,即可在不修改应用程序的情况下保持运行时行为。但这一假设被证明是错误的:迁移过程中引入的两项基础设施设置改变了 Kubernetes 处理执行器部署和本地存储的方式,而这两项设置在审查过程中均未被标记出来。 事件时间线 迁移后第一周 最初似乎运行得很稳定,比较小的任务都顺利完成了。首次内存不足(OOM)故障出现在定宽多布局批处理任务中,具体发生在该任务需要对大量数据进行 shuffle 的合并阶段。在合并结果之前,该任务使用不同的布局解析器多次读取了同一个 3GB 大小的文件。 第二到第三天 OOM 故障最初被视为暂时的。任务被手动重启并暂时恢复。该问题被记录为间歇性故障,并归因于集群层面的潜在资源竞争。 第三到第四天:初步假设与堆配置错误 初步诊断的重点在于执行器堆内存的配置。我们将 spark.executor.memory 从 8 GB 增加到 10 GB。但在高负载下故障依然存在,这排除了将堆内存配置不足作为根本原因的可能性。 第四天:第二种假设与执行器数量 为了进一步分担工作负载,我们增加了执行器的数量,但这并未产生显著的效果;在涉及大量 shuffle 操作的阶段,系统仍会发生故障。 第四至第五天:Kubernetes Pod 放置分析 我们通过 AKS Pod 日志和 Datadog 检查了 Kubernetes Pod 的放置情况。Datadog 的 Kubernetes 节点概览仪表板显示,在重新分配阶段,节点内存使用率飙升至 90% 以上。Kubernetes 事件持续报告: Reason: OOMKilled Exit Code: 137 我们还在 AKS Pod 日志中观察到了明显的执行器更替模式。该作业并未使用最初的四个执行器来完成,而是反复替换因内存不足(OOM)而被终止的执行器,最终在用到了第 50 个执行器时才失败。在每次终止事件发生前的洗牌阶段,Datadog 显示的节点级内存使用量在数秒内从约 42GB 飙升至超过 58GB。这清楚地表明,问题并非出在堆大小配置上,而是节点级内存耗尽导致 Kubernetes 内核 OOM killer 终止了执行器进程。 第五天:根因确认 我们在对 Spark 和 Kubernetes 配置进行审查时发现,spark.kubernetes.local.dirs.tmpfs 在迁移过程中被设置成了 true,这导致所有本地临时目录(包括洗牌溢出路径)都由内存而非磁盘提供支持。此外,临时卷(tmp-volume 和 workdir)的容量大小也仅为 1 GiB,对于该作业生成的洗牌数据而言,这远远不够。在本地环境中,原本是使用本地磁盘空间来存储洗牌溢出数据,而且卷的大小配置合理。然而,在迁移审查过程中,这两处差异均未被记录在案。 第五天:方案部署 通过设置 preferredDuringSchedulingIgnoredDuringExecution,将 podAffinity 托管规则替换为 podAntiAffinity。此外,将 spark.kubernetes.local.dirs.tmpfs 设为 false,并将 tmp-volume 和 workdir 的大小限制从 1 Gibibyte 增加到 10 Gibibyte。OOM 故障随即停止。该修复方案已经稳定运行六个月,未再发生任何故障。 根因分析 在调查过程中,我们发现了导致故障的三个因素。只有在高随机访问负载下,第二和第三个因素相互作用时,这些故障才会出现。 因素1:大型数据任务中由洗牌引发的内存压力 在数据密集型任务中,大规模的洗牌阶段会导致执行器的内存使用量急剧飙升。这是 Spark 中的预期行为;洗牌操作需要在数据溢出到磁盘之前,先将中间数据保存在内存中。单独来看,通过适当的资源配置,这种数据保留是可控的。但当与下文所述的其他因素结合时,情况便变成了灾难性的。 尽管源文件只有大约 3GB,但多布局定宽格式所需的多次解析迭代、针对每种布局生成的中间 Spark DataFrame 实例、将它们合并的并集操作,以及下游的洗牌阶段,会导致内存压力远超原始输入大小所暗示的水平。对于一个拥有 4 个执行器的任务(即一个包含 4 个工作进程的 Spark 集群配置,每个进程被分配固定比例的 CPU 内核和内存,共同负责在分区间并行处理数据)而言,3 GB 的输入文件看似规模不大,但多轮处理将有效工作集的规模放大到了远超原始输入大小的程度。 因素 2:亲和性配置错误导致执行器被迫共置 实际上,执行器放置规则已经成为一项硬性共置约束。配置中存在一条使用 requiredDuringSchedulingIgnoredDuringExecution 的 podAffinity 规则。该规则并未将执行器 Pod 分布到不同的节点上,而是强制将它们放置在同一个节点上。这并非默认的装箱行为;Kubernetes 只是遵循了一条显式的硬性放置规则。 Kubernetes 的调度机制与配置错误的放置规则相结合,导致所有四个执行器 Pod 都被分配到了同一个 64GB 的节点上。这使得洗牌期间的内存和 I/O 压力都集中在了一台机器上。一旦加入基于 tmpfs 的溢出机制,节点内存便告耗尽,内核 OOM killer 开始终止执行器。 在本地环境中,集群调度器曾经配置了明确的放置约束,可以自然地分散执行器。但在迁移过程中,该约束并未被保留。我们通过使用 pr
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱