症状:History Server 一片空白,日志去哪了?
上周五下午,我们组的告警突然炸了——用户反馈 Spark History Server 上看不到任何历史任务。明明 spark-submit 跑完了,S3 bucket 里也看到了 spark-events 目录,但点进去就是空的。
更诡异的是,我手动 kubectl logs 看 driver pod,日志是有的。executor pod 也能看到 stdout/stderr。但 S3 里就是没有 application_xxx.inprogress 文件。
我第一反应:“日志采集管道断了”。
根因分析:三个常见的"我以为"
翻了一下午代码和社区帖子,发现这个问题在 Spark on Kubernetes 场景下极其普遍。Reddit 上有人吐槽:“I am looking for an option to add sparkApplicationId to driver and executor pod names."——其实这背后暴露的是日志路径映射的混乱。
核心问题有三个:
spark.eventLog.dir配置不对。很多人设了s3a://bucket/spark-events,但 Spark 在 K8s 上默认不走 Hadoop S3A connector,你得显式加上spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem。Driver 和 Executor 的日志路径不统一。默认情况下,Spark 的 event log 只由 driver 写入,executor 的 stdout/stderr 根本不会进 S3。你需要额外配置
spark.kubernetes.driver.log.persist和spark.kubernetes.executor.log.persist。History Server 读取权限问题。就算日志写进去了,History Server 的 service account 可能没有 S3 bucket 的
s3:GetObject权限。这个坑我们踩了两天才发现。
修复步骤:从零到能看 History Server
1. 确认 S3 路径和 Hadoop 配置
首先,检查你的 spark-submit 命令或配置文件中是否包含以下关键项:
--conf spark.eventLog.enabled=true
--conf spark.eventLog.dir=s3a://my-bucket/spark-events/
--conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
--conf spark.hadoop.fs.s3a.access.key=YOUR_ACCESS_KEY
--conf spark.hadoop.fs.s3a.secret.key=YOUR_SECRET_KEY
--conf spark.hadoop.fs.s3a.endpoint=https://s3.ap-southeast-1.amazonaws.com
注意:如果你的 S3 兼容存储(比如 MinIO)用了自签名证书,还得加上 --conf spark.hadoop.fs.s3a.connection.ssl.enabled=false(别问我怎么知道的)。
2. 开启 Driver 和 Executor 日志持久化
这是大部分人漏掉的一步。默认 Spark 只把 event log 写入 S3,但 driver 和 executor 的 stdout/stderr 不会自动上传。你需要显式开启:
--conf spark.kubernetes.driver.log.persist=true
--conf spark.kubernetes.executor.log.persist=true
--conf spark.kubernetes.driver.log.persist.dir=s3a://my-bucket/driver-logs/
--conf spark.kubernetes.executor.log.persist.dir=s3a://my-bucket/executor-logs/
注意:spark.kubernetes.driver.log.persist 在 Spark 3.1.1 之后才稳定,如果你用老版本,可能需要升级。
3. 给 History Server 配好 IAM 权限
History Server 本身是一个 Spark 应用,它需要读取 S3 里的 event log。如果你用 IRSA(IAM Roles for Service Accounts),需要确保 service account 绑定了正确的 policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
如果你用 access key/secret key 的方式,直接在 History Server 的 Deployment 环境变量里设置 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY。
4. 验证日志写入
跑一个简单的 Spark Pi 作业测试:
spark-submit \
--master k8s://https://<api-server> \
--deploy-mode cluster \
--conf spark.kubernetes.container.image=apache/spark:3.5.0 \
--conf spark.eventLog.enabled=true \
--conf spark.eventLog.dir=s3a://my-bucket/spark-events/ \
--conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem \
--conf spark.hadoop.fs.s3a.access.key=xxx \
--conf spark.hadoop.fs.s3a.secret.key=xxx \
local:///opt/spark/examples/jars/spark-examples.jar 100
作业跑完后,检查 S3 bucket:
aws s3 ls s3://my-bucket/spark-events/
你应该能看到类似 app-20260624020000-0000.inprogress 的文件。如果看不到,先看 driver pod 的日志:
kubectl logs <driver-pod> | grep -i "eventlog"
常见错误是 java.io.IOException: No FileSystem for scheme: s3a,说明 Hadoop S3A 依赖没加载。
5. 重启 History Server 并验证
如果日志已经写入 S3,但 History Server 还是空白,检查 History Server 的启动参数。正确的配置应该是:
# history-server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spark-history-server
spec:
replicas: 1
selector:
matchLabels:
app: spark-history-server
template:
metadata:
labels:
app: spark-history-server
spec:
containers:
- name: history-server
image: apache/spark:3.5.0
args:
- "/opt/spark/bin/spark-class"
- "org.apache.spark.deploy.history.HistoryServer"
env:
- name: SPARK_HISTORY_OPTS
value: "-Dspark.history.fs.logDirectory=s3a://my-bucket/spark-events/ -Dspark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem -Dspark.hadoop.fs.s3a.access.key=xxx -Dspark.hadoop.fs.s3a.secret.key=xxx"
ports:
- containerPort: 18080
注意:SPARK_HISTORY_OPTS 环境变量是 History Server 读取配置的关键。很多人直接在 spark-defaults.conf 里配,但 History Server 启动时不一定加载那个文件。
配置对比表
| 配置项 | 作用范围 | 常见错误 | 正确值 |
|---|---|---|---|
spark.eventLog.dir | Driver | 使用 s3:// 而非 s3a:// | s3a://bucket/spark-events/ |
spark.hadoop.fs.s3a.impl | Driver/Executor | 缺失 | org.apache.hadoop.fs.s3a.S3AFileSystem |
spark.kubernetes.driver.log.persist | Driver | 未设置 | true |
spark.kubernetes.executor.log.persist | Executor | 未设置 | true |
spark.history.fs.logDirectory | History Server | 与 eventLog.dir 不一致 | 必须指向同一个 S3 路径 |
| IAM Policy | History Server | 缺少 s3:GetObject | 必须包含 s3:GetObject 和 s3:ListBucket |
FAQ
Q1: 为什么我的 History Server 显示 “No completed applications found” 但 S3 里有文件?
A: 最常见的原因是 History Server 的 spark.history.fs.logDirectory 配置与 driver 的 spark.eventLog.dir 不匹配。检查两点:
- 路径必须完全一致(包括 bucket 名和前缀)。
- History Server 必须能访问 S3——如果是 IRSA,检查 service account 的 annotation 是否正确;如果是 access key,检查环境变量是否传递。
Q2: 开启 spark.kubernetes.driver.log.persist 后,日志文件在哪里?
A: 默认路径是 s3a://<eventLog.dir>/<app-id>/driver/。你可以通过 spark.kubernetes.driver.log.persist.dir 自定义。注意:这个功能只在 Spark 3.1.1+ 可用,并且需要 Hadoop S3A 依赖。
Q3: 我的 Spark 版本是 3.0.0,能支持日志持久化到 S3 吗?
A: 勉强可以,但不推荐。Spark 3.0.0 的 spark.kubernetes.driver.log.persist 功能是实验性的,且没有 executor 支持。建议升级到 3.1.1 或更高版本。如果必须用 3.0.0,可以考虑用 sidecar 容器手动将日志上传到 S3。
Q4: S3 兼容存储(如 MinIO)需要额外配置吗?
A: 需要。除了 fs.s3a.impl,还需要:
spark.hadoop.fs.s3a.endpoint:指向 MinIO 的 API 地址。spark.hadoop.fs.s3a.path.style.access=true:MinIO 强制要求 path-style 访问。- 如果 MinIO 没有 HTTPS,加上
spark.hadoop.fs.s3a.connection.ssl.enabled=false。
Q5: 日志写入 S3 后,History Server 加载很慢怎么办?
A: 考虑启用 spark.history.fs.updateInterval 来降低轮询频率(默认 10 秒)。另外,如果日志文件很大,可以设置 spark.history.fs.maxLogEntries 限制每个应用的最大日志条目数。对于生产环境,建议将 S3 bucket 的生命周期策略设置为归档旧日志到 Glacier,减少 History Server 的扫描范围。
社区灵感与参考 (References & Community Insights)
本文探讨的架构演进与技术实现方案,深度提炼自 Hacker News、Reddit 等极客社区的真实工程师讨论、线上事故复盘(Post-mortems)以及一线技术博客的实战经验分享。