1. Spark on YARN 里那些绕不开的零散概念
很多人第一次把 Spark 任务提交到 YARN 上跑通之后,心里其实是虚的:任务确实跑完了,但 Driver 到底在哪、Executor 是怎么被拉起来的、日志为什么本地看不到、内存该调哪个参数,全靠试。这篇笔记就是把这些零散的点串起来,面向的是已经能跑通任务、但概念还模糊的开发者。
先把几个核心角色说清楚。Spark 应用在 YARN 上运行时,有两个关键进程:Driver 和 Executor。Driver 是"大脑",负责把用户代码解析成 DAG、划分 Stage、调度 Task;Executor 是"手脚",真正在容器里执行 Task 并把结果汇报回去。YARN 则是"物业",负责分配 Container 资源、启动 ApplicationMaster。在 cluster 模式下,Driver 就跑在 YARN 的 ApplicationMaster 容器里;在 client 模式下,Driver 跑在你提交任务的这台机器上。
HDFS 在这里的角色是"仓库"。它和 YARN 是两套独立系统:HDFS 管存储,YARN 管计算资源。你提交的 jar 包、依赖的 zip、读取的输入数据、写出的结果,通常都放在 HDFS(或兼容 HDFS 协议的对象存储)上。理解这一点很关键——很多"找不到文件"的报错,本质是 Driver 或 Executor 在各自节点上访问 HDFS 路径时权限或地址不对。
还有一个容易混的点:Hadoop MapReduce 的 map 阶段会把中间结果写磁盘,而 Spark 尽量在内存里做 DAG 计算,只有 shuffle 和溢写时才落盘。这决定了 Spark 对内存更敏感,也决定了你调参时 driver-memory 和 executor-memory 的侧重点不同。
这篇会交付一份可复制的 Spark 配置清单、YARN 资源验证命令,并演示如何用 TaoToken 统一管理 API Key,把本地调试到集群提交的链路验证串起来。如果你正在被yarn logs看不到日志、local proxy failed这类问题卡住,下面的内容可以直接对照操作。
2. TaoToken 前置准备:统一管理 API Key 与模型接入
在把 Spark 任务提交到集群之前,本地调试阶段经常需要调用大模型 API 来做数据处理、文本清洗或结果校验。如果每个脚本里都硬编码 Key,换环境就得改代码,很容易出错。TaoToken 的作用就是把这些 API Key 和模型接入统一管起来,本地和集群用同一套配置。
先说清楚它是什么:TaoToken 是一个 API 聚合与密钥管理平台,能让你用统一的 Base URL 和 Key 去调用不同模型,适合需要在多个环境(本地、测试、集群)之间切换的开发者。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。
第一步,登录后在控制台创建 API Key。控制台地址:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建时建议按用途命名,比如spark-local-debug、spark-cluster-job,这样后面排查哪个 Key 被滥用时能快速定位。
第二步,拿到 Key 之后,不要直接写进代码。推荐用环境变量的方式注入。本地开发时在~/.bashrc或~/.zshrc里加:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"集群提交时,通过--conf spark.yarn.appMasterEnv.TAOTOKEN_API_KEY=xxx把环境变量传给 Driver 容器,避免把 Key 写死在 jar 里。这一步很多人忽略,结果 Key 泄露在日志里。
第三步,如果你用的是 Claude Code 这类编码工具做辅助开发,可以在配置里指定 Base URL 和 Key。模型对话入口:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。文档里有各语言的调用示例,照着改 Base URL 就行。
这里要强调一个原则:TaoToken 是帮你管理 Key 和统一入口的,不是让你绕过任何合规流程。所有调用都要走正规 API,Key 要妥善保管,不要提交到 Git 仓库。我试过把 Key 放在.env文件里然后忘了加.gitignore,差点出事,后来统一改成环境变量注入才踏实。
对于长期跑编码任务或 Agent 的场景,可以考虑 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它适合需要持续调用、额度稳定的情况,比按次调用更省心。本地调试阶段用普通 Key 就够,等任务稳定了再切过去。
3. 可复制的 Spark 配置清单与 YARN 资源验证
这一节给出一份可以直接抄的配置。先说 Spark 侧的spark-defaults.conf,放在$SPARK_HOME/conf/下:
spark.master yarn spark.submit.deployMode cluster spark.driver.memory 2g spark.driver.cores 1 spark.executor.memory 4g spark.executor.cores 2 spark.executor.instances 4 spark.yarn.queue default spark.yarn.appMasterEnv.PYSPARK_PYTHON /usr/bin/python3 spark.yarn.appMasterEnv.TAOTOKEN_API_KEY ${env:TAOTOKEN_API_KEY} spark.yarn.archive hdfs:///spark-libs/spark-libs.zip几个关键点解释一下。deployMode设成cluster时 Driver 在集群里跑,日志要用yarn logs看;设成client时 Driver 在本地,日志直接打在终端。spark.yarn.appMasterEnv.PYSPARK_PYTHON指定集群节点上的 Python 路径,这个必须和节点实际路径一致,否则 Executor 起不来。spark.yarn.archive把依赖打包上传到 HDFS,避免每个 Executor 重复下载。
Hadoop 侧的配置文件在/usr/local/service/hadoop/etc/hadoop/目录下,重点是三个:
<!-- core-site.xml --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://HDFS80043</value> </property> </configuration> <!-- yarn-site.xml --> <configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>your-rm-host</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration>fs.defaultFS决定了你写hdfs://HDFS80043/path时实际访问哪个集群。如果这里配错,Driver 会报FileNotFoundException或者连不上 NameNode。yarn.resourcemanager.hostname是 ResourceManager 地址,提交任务时 YARN 客户端靠它找到 RM。
验证 YARN 资源是否够用,用这几条命令:
# 查看队列资源 yarn queue -status default # 查看集群节点状态 yarn node -list -all # 查看当前应用 yarn application -list -appStates RUNNING # 查看某个应用的日志(cluster 模式必备) yarn logs -applicationId application_1234567890_0001yarn node -list会显示每个 NodeManager 的可用内存和 vCores。如果spark.executor.instances设了 4,但集群只剩 2 个 Container 的余量,任务会一直卡在 ACCEPTED 状态。这时候要么等资源释放,要么调小 instances。
HDFS 操作前要先确认有 hadoop 用户权限:
# 列出 HDFS 根目录 hdfs dfs -ls / # 上传依赖包 hdfs dfs -put pyspark_env.zip /spark-python/ # 确认文件存在 hdfs dfs -ls hdfs://HDFS80043/spark-python/pyspark_env.zip如果用的是对象存储(比如 COS),路径写法类似,但要注意它对外暴露的是路径而非标准 HDFS 端口,配置里要加对应的 filesystem 实现。这部分各家云厂商文档不一样,按官方说明配就行。
4. 验证请求与成功结果:从本地到集群的链路
配置写完之后,先别急着提交集群,在本地用 client 模式跑一遍,确认逻辑没问题。本地跑的时候 Driver 和 Executor 都在本机,日志直接输出,排查最快。
$SPARK_HOME/bin/spark-submit \ --master local[2] \ --conf spark.driver.memory=1g \ --conf spark.executor.memory=1g \ your_job.py本地跑通后,再切到 YARN cluster 模式:
$SPARK_HOME/bin/spark-submit \ --master yarn \ --deploy-mode cluster \ --conf spark.yarn.appMasterEnv.TAOTOKEN_API_KEY=$TAOTOKEN_API_KEY \ --files config.json \ your_job.py提交后会返回一个application_xxx的 ID。用这个 ID 跟踪状态:
yarn application -status application_1234567890_0001状态从ACCEPTED变成RUNNING,最后变成FINISHED,说明任务成功。如果卡在ACCEPTED,多半是资源不够;如果变成FAILED,用yarn logs看具体报错。
验证 TaoToken 的调用是否生效,可以在任务里加一段简单的请求测试:
import os import requests api_key = os.environ.get("TAOTOKEN_API_KEY") base_url = os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api") resp = requests.post( f"{base_url}/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json={"model": "your-model", "messages": [{"role": "user", "content": "ping"}]}, timeout=30 ) print(resp.status_code, resp.json())在本地跑这段,返回 200 和正常 JSON,说明 Key 和 Base URL 没问题。然后在集群任务里也加一段,通过yarn logs看输出。如果集群里报 401,说明环境变量没传进去;如果报连接超时,说明节点出网策略或 DNS 有问题。
成功的结果长这样:本地终端打印出200和模型返回内容;集群日志里同样能看到这段输出;yarn application -status显示FINISHED;HDFS 上能看到任务写出的结果文件:
hdfs dfs -ls hdfs://HDFS80043/output/your_job_result/到这里,本地调试到集群提交的链路就验证完了。整个过程的核心是:配置一致、环境变量正确注入、日志能定位问题。
5. 本篇常见错误排查:401、local proxy failed 与日志读取
这一节对照几个真实报错,给出排查路径。
报错一:401 Unauthorized
requests.exceptions.HTTPError: 401 Client Error: Unauthorized原因通常是 Key 没传进去或传错了。检查顺序:本地echo $TAOTOKEN_API_KEY是否有值;集群提交时--conf spark.yarn.appMasterEnv.TAOTOKEN_API_KEY是否拼写正确;Driver 容器里os.environ.get能否取到。如果用的是spark-defaults.conf里的${env:TAOTOKEN_API_KEY},要确认提交任务的 shell 里确实 export 了这个变量。
报错二:local proxy failed
ERROR Client: Failed to connect to server: local proxy failed这个多半是网络或代理配置问题。先确认节点能否访问https://taotoken.net/api,用curl -v测一下。如果是集群节点出网受限,需要联系运维开通对应出口,不要自己乱配代理。另外检查spark.yarn.appMasterEnv里有没有误传http_proxy之类的变量,有时候本地环境变量被带进集群反而导致连接失败。
报错三:reading choices 相关解析错误
KeyError: 'choices'这通常不是网络问题,而是返回体结构和你解析的字段不匹配。先打印完整resp.text看实际返回。可能是模型名写错导致返回了错误信息,也可能是请求体格式不对。对照接入文档里的示例,确认model、messages字段拼写正确。
报错四:OAuth 或认证方式不匹配
如果你用的是 Claude Code 或类似工具,配置里可能涉及 OAuth 流程。确认 Base URL 填的是https://taotoken.net/api,Key 填的是控制台生成的 API Key,而不是其他类型的凭证。Claude Code 的配置参考:https://taotoken.net/claudecode?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。
报错五:Executor 起不来,日志显示 Python 路径错误
Error: Python in worker has different version这是PYSPARK_PYTHON没配对。在spark-defaults.conf里显式指定集群节点上真实存在的 Python 路径,比如/usr/bin/python3。如果节点上 Python 在虚拟环境里,要把虚拟环境打包上传或用 conda-pack 分发。
排查通用思路:先看yarn logs -applicationId xxx的 Driver 日志,再看具体失败 Container 的日志。Driver 日志里通常有最直接的异常栈。如果日志太大,用yarn logs -applicationId xxx | grep -A 20 "Exception"过滤。
6. 把 Key 管理和集群提交串成一条稳定链路
最后说几个实操中总结的经验。第一,Key 永远不要硬编码,本地用环境变量,集群用spark.yarn.appMasterEnv注入,这样换 Key 不用改代码。第二,spark-defaults.conf里的配置要跟集群实际资源匹配,别照抄网上的大内存配置,先yarn node -list看余量再定。第三,cluster 模式下养成用yarn logs看日志的习惯,本地终端什么都看不到是正常的。
如果你需要长期跑编码或 Agent 任务,Coding Plan 的额度模式比按次调用更划算,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。API Key 管理在控制台:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,接入细节看文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
把本地调试和集群提交用同一套 Key 管理方式串起来之后,最明显的变化是排查问题快了:以前 Key 散落在各个脚本里,报 401 要一个个翻;现在统一从环境变量取,出问题只看一个地方。集群任务提交前先在本地 client 模式验证逻辑,再切 cluster 模式,能省掉大量在 YARN 上反复试错的时间。