Paimon 1.3.1 报 Proxy user is not supported:Kyuubi + Kerberos 集成排查与源码级定位
副标题:一次 Spark 4.x 新版本栈的生产验证记录
关键词:Paimon Proxy user is not supported、Kyuubi Kerberos、Paimon 1.3.1、HadoopSecuredFileSystem、KerberosLoginProvider、Spark Catalog Kerberos、UnsupportedOperationException、Paimon 集成、impersonation、Spark 4.0 升级
目录
- 一、问题现象
- 二、先看清楚:这是哪套栈的问题
- [三、第一反应:是不是 keytab 没配?](#三、第一反应:是不是 keytab 没配? "#%E4%B8%89%E7%AC%AC%E4%B8%80%E5%8F%8D%E5%BA%94%E6%98%AF%E4%B8%8D%E6%98%AF-keytab-%E6%B2%A1%E9%85%8D")
- [四、核心变化:1.3.1 多了一层「安全加固」](#四、核心变化:1.3.1 多了一层「安全加固」 "#%E5%9B%9B%E6%A0%B8%E5%BF%83%E5%8F%98%E5%8C%96131-%E5%A4%9A%E4%BA%86%E4%B8%80%E5%B1%82%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA")
- [五、源码:KerberosLoginProvider 的三条分支](#五、源码:KerberosLoginProvider 的三条分支 "#%E4%BA%94%E6%BA%90%E7%A0%81kerberosloginprovider-%E7%9A%84%E4%B8%89%E6%9D%A1%E5%88%86%E6%94%AF")
- 六、为什么必然走到第三条分支
- [七、一个隐蔽的坑:isLegal() 永远返回 true](#七、一个隐蔽的坑:isLegal() 永远返回 true "#%E4%B8%83%E4%B8%80%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91islegal-%E6%B0%B8%E8%BF%9C%E8%BF%94%E5%9B%9E-true")
- [八、解决方案:补上 principal 与 keytab](#八、解决方案:补上 principal 与 keytab "#%E5%85%AB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E8%A1%A5%E4%B8%8A-principal-%E4%B8%8E-keytab")
- 九、为什么有两套配置键名
- 十、验证方法
- 十一、升级视角:新版本栈还要检查什么
- 十二、总结
一、问题现象
1.1 报错原文
在 Kyuubi + Spark 环境里使用 Paimon 时,任何 SQL 操作都会失败:
css
java.lang.UnsupportedOperationException: Proxy user is not supported
at org.apache.paimon.security.KerberosLoginProvider.throwProxyUserNotSupported(KerberosLoginProvider.java:101)
at org.apache.paimon.security.KerberosLoginProvider.isLoginPossible(KerberosLoginProvider.java:76)
at org.apache.paimon.security.HadoopModule.install(HadoopModule.java:55)
at org.apache.paimon.fs.hadoop.HadoopSecuredFileSystem.trySecureFileSystem(HadoopSecuredFileSystem.java:205)
at org.apache.paimon.fs.hadoop.HadoopFileIO.createFileSystem(HadoopFileIO.java:207)
at org.apache.paimon.fs.hadoop.HadoopFileIO.getFileSystem(HadoopFileIO.java:175)
at org.apache.paimon.fs.hadoop.HadoopFileIO.getFileStatus(HadoopFileIO.java:109)
at org.apache.paimon.catalog.CatalogFactory.createUnwrappedCatalog(CatalogFactory.java:98)
at org.apache.paimon.catalog.CatalogFactory.createCatalog(CatalogFactory.java:71)
at org.apache.paimon.spark.SparkCatalog.initialize(SparkCatalog.java:137)
at org.apache.spark.sql.connector.catalog.CatalogManager.loadCatalog(Catalogs.scala:65)
1.2 三个关键特征
- 错误发生在 Paimon Catalog 初始化阶段,不是后续的表读写
- 精确指向
KerberosLoginProvider.throwProxyUserNotSupported()第 101 行 - 调用链清晰:
createFileSystem→trySecureFileSystem→HadoopModule.install→isLoginPossible
二、先看清楚:这是哪套栈的问题
这一点必须先讲清楚,否则很容易误判成"现网出故障了"。
2.1 两套栈对照
| 现网生产栈 | 本次验证栈 | |
|---|---|---|
| Kyuubi | 1.10.2 | 1.11.x(新版本) |
| Spark | 3.4.1 / 3.5.5 | 4.0.x |
| Paimon | 旧版本 | 1.3.1 |
| Hudi / Ranger 插件 | 有 | 有 |
| 状态 | 稳定运行 | 生产验证中,暴露此问题 |
2.2 所以这不是"线上事故"
现网跑得好好的,问题只出现在新版本栈的验证环境里。
这件事的价值反而更大:
如果你正在规划 Kyuubi / Spark 的版本升级,并且依赖 Paimon、Hudi、Ranger 这些插件,这个坑你大概率会踩到。
2.3 为什么升级后会冒出来
因为升级不是一件事,是四件事同时发生:
Kyuubi 升版本 → 认证与 impersonation 实现变化
Spark 升 4.x → Catalog 插件模型、依赖体系变化
Paimon 升 1.3.1 → 新增安全加固层(本文主角)
插件同步升级 → Hudi / Ranger 各自的行为也在变
任何一个环节的安全性"变严",都可能和另一个环节的"代理身份"撞上。
本文记录的,就是其中撞得最直接的一次。
三、第一反应:是不是 keytab 没配?
看到 Kerberos 相关报错,第一反应通常是去查 keytab。
但有个很关键的对照事实:
同样的代码,在 Paimon 1.0.0 上跑得好好的。现网那套栈也没这个问题。
如果 keytab 有问题,旧版本也该失败。所以问题不在"认证材料缺失",而在版本行为变了。
对比 HadoopFileIO.createFileSystem() 的实现:
Paimon 1.0.0:
arduino
protected FileSystem createFileSystem(Path path) throws IOException {
return path.getFileSystem(hadoopConf.get()); // 直接取,不做任何安全包装
}
Paimon 1.3.1:
ini
protected FileSystem createFileSystem(Path path) throws IOException {
Configuration conf = hadoopConf.get();
FileSystem fileSystem = path.getFileSystem(conf);
// ★ 新增:安全加固层
fileSystem = HadoopSecuredFileSystem.trySecureFileSystem(fileSystem, options, conf);
return fileSystem;
}
就是这一行 trySecureFileSystem,改变了整个行为。
四、核心变化:1.3.1 多了一层「安全加固」
trySecureFileSystem 的逻辑:
arduino
public static FileSystem trySecureFileSystem(
FileSystem fileSystem, Options options, Configuration configuration) throws IOException {
SecurityConfiguration config = new SecurityConfiguration(options);
if (config.isLegal()) {
LOG.info("Hadoop security configuration is legal, use the secured FileSystem.");
HadoopModule module = new HadoopModule(config, configuration);
module.install(); // ← 这里会走到 KerberosLoginProvider
UserGroupInformation ugi = UserGroupInformation.getLoginUser();
return new HadoopSecuredFileSystem(fileSystem, ugi);
} else {
LOG.info("Hadoop security configuration is illegal, use the original FileSystem.");
return fileSystem;
}
}
调用顺序:
markdown
1. 从 options 读取 principal / keytab → SecurityConfiguration
2. 判断配置是否"合法" → isLegal()
3. 合法 → HadoopModule.install()
4. HadoopModule 创建 KerberosLoginProvider
5. isLoginPossible() 检查当前 UGI 的认证方式
第 5 步就是报错的地方。
五、源码:KerberosLoginProvider 的三条分支
java
public boolean isLoginPossible() throws IOException {
if (!UserGroupInformation.isSecurityEnabled()) {
return false; // 未开安全认证,直接跳过
}
UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
if (principal != null) { // 分支 1
return true; // 有 principal → 允许 keytab 登录 ✅
} else if (!isProxyUser(currentUser)) { // 分支 2
if (useTicketCache && currentUser.hasKerberosCredentials()) {
return true; // 非 PROXY → 允许 ticket cache ✅
}
} else { // 分支 3
throwProxyUserNotSupported(); // PROXY + 无 principal → ❌ 抛异常
}
return false;
}
画成决策树:
是否有 principal?
├─ 有 → 走 keytab 登录 ✅
└─ 没有
├─ 当前 UGI 是 PROXY 用户? → 是 → 抛异常 ❌
└─ 不是 → 尝试 ticket cache ✅
结论很明确:只要「没有 principal」且「当前是 PROXY 用户」,就一定抛异常。
六、为什么必然走到第三条分支
6.1 Kyuubi 天然产生 PROXY 用户
Kyuubi 做多租户的核心机制就是 impersonation ------服务进程以 hive/kyuubi 身份登录,然后代理真实客户端用户去访问 HDFS。
这种代理状态下,当前线程的 UserGroupInformation 的认证方式被标记为:
UserGroupInformation.AuthenticationMethod.PROXY
这就是 isProxyUser(currentUser) 返回 true 的原因。
6.2 没有 principal → 必走分支 3
如果 spark.sql.catalog.paimon.security.kerberos.login.principal 没配,principal 就是 null。
于是:
ini
principal == null
+
currentUser 是 PROXY
↓
分支 3:throwProxyUserNotSupported()
6.3 完整因果链
vbnet
升级到 Paimon 1.3.1(新增安全加固层)
↓
trySecureFileSystem → HadoopModule.install
↓
KerberosLoginProvider.isLoginPossible()
↓
principal 未配置 + UGI 是 PROXY
↓
UnsupportedOperationException: Proxy user is not supported
注意:这不是 Paimon 的 bug,也不是 Kyuubi 的 bug。
这是两个"更安全"的设计撞在了一起:
- Kyuubi 的 impersonation 让多租户成为可能 → 产生 PROXY 身份
- Paimon 1.3.1 的安全加固拒绝"无凭证的代理身份" → 直接抛异常
单独看,两边都没做错;放在一起,就都不工作了。
七、一个隐蔽的坑:isLegal() 永远返回 true
这是最容易让人困惑的地方。
既然 principal/keytab 都没配,isLegal() 为什么还返回 true?
看源码:
arduino
public boolean isLegal() {
// 1. 检查 keytab 和 principal 是否成对存在
if (StringUtils.isNullOrWhitespaceOnly(keytab)
!= StringUtils.isNullOrWhitespaceOnly(principal)) {
return false; // 只有一个非空 → 不合法
}
// 2. 检查 keytab 文件是否存在
if (!StringUtils.isNullOrWhitespaceOnly(keytab)) {
File keytabFile = new File(keytab);
return keytabFile.exists() && keytabFile.isFile() && keytabFile.canRead();
}
return true; // ★ 两个都为空,仍然返回 true
}
逻辑是「成对检查」,不是「必须存在」。
两个都是 null 时,第一个判断 null != null 为 false,不返回;第二个判断跳过;最后 return true。
所以:
scss
两个都没配 → isLegal() = true → 走安全通道 → 然后炸在 isLoginPossible()
如果没有这一行 return true,没配的时候会直接跳过安全层,回到 1.0.0 的行为,就不会报错。
这是一个典型的「防御性代码只防了一半」:它假设「不配 = 不启用」,但没考虑「不配 + 代理身份」这种组合。
这一点对升级评估特别重要:如果你以为"不配就是走老路径",就会低估升级风险。
八、解决方案:补上 principal 与 keytab
既然分支 1 是「有 principal 就允许」,那就把 principal/keytab 显式提供给 Paimon Catalog。
8.1 完整配置
ini
################################### Kyuubi 自身认证 ###################################
kyuubi.authentication=KERBEROS
kyuubi.kinit.principal=kyuubi/node01.example.com@EXAMPLE.COM
kyuubi.kinit.keytab=/opt/kyuubi/kyuubi.keytab
################################### Spark 会话扩展 ###################################
spark.sql.extensions=org.apache.paimon.spark.PaimonSparkSessionExtensions
################################### Paimon Catalog ###################################
spark.sql.catalog.paimon=org.apache.paimon.spark.SparkCatalog
spark.sql.catalog.paimon.warehouse=hdfs://nameservice1/tmp/paimon
################################### ★ 关键:Paimon 的 Kerberos 配置 ###################################
spark.sql.catalog.paimon.security.kerberos.login.principal=kyuubi/node01.example.com@EXAMPLE.COM
spark.sql.catalog.paimon.security.kerberos.login.keytab=/opt/kyuubi/kyuubi.keytab
8.2 三个必须注意的点
| 注意点 | 说明 |
|---|---|
| principal 必须与 keytab 内一致 | 用 klist -kt <keytab> 核对,大小写、FQDN 都要一致 |
| keytab 路径是「当前机器真实路径」 | 不是容器内路径,Kyuubi 进程必须有读权限 |
| 改完必须重启 Kyuubi | kyuubi-defaults.conf 是启动时读取的 |
8.3 核验 keytab
bash
# 1. 确认 keytab 里的 principal 列表
klist -kt /opt/kyuubi/kyuubi.keytab
# 2. 确认文件可读
ls -l /opt/kyuubi/kyuubi.keytab
# 3. 确认 FQDN
hostname -f
# 4. 用 keytab 试登录
kinit -kt /opt/kyuubi/kyuubi.keytab kyuubi/node01.example.com@EXAMPLE.COM
klist
九、为什么有两套配置键名
配置里你会看到两组键:
ini
# 主键(优先级 1)
spark.sql.catalog.paimon.security.kerberos.login.principal=...
spark.sql.catalog.paimon.security.kerberos.login.keytab=...
# 回退键(优先级 2)
spark.sql.catalog.paimon.hadoop.security.principal=...
spark.sql.catalog.paimon.hadoop.security.keytab=...
原因在 SecurityConfiguration 的 ConfigOption 定义:
arduino
public static final ConfigOption<String> KERBEROS_LOGIN_KEYTAB =
key("security.kerberos.login.keytab") // 优先级 1
.noDefaultValue()
.withFallbackKeys("security.keytab"); // 优先级 2
public static final ConfigOption<String> KERBEROS_LOGIN_PRINCIPAL =
key("security.kerberos.login.principal") // 优先级 1
.noDefaultValue()
.withFallbackKeys("security.principal"); // 优先级 2
注意:这里定义的是相对键 (security.kerberos.login.keytab),前面会被加上 Catalog 前缀 spark.sql.catalog.paimon.。
也就是说,实际生效的是:
spark.sql.catalog.paimon.security.kerberos.login.keytab ← 真正生效
spark.sql.catalog.paimon.security.kerberos.login.principal ← 真正生效
而 spark.sql.catalog.paimon.hadoop.security.* 这组,在没有自定义 Options 覆盖的情况下,不一定会被读到。
这是一个实践中常见的误区:网上很多示例写的是
hadoop.security.*,照抄过来不生效。
保险做法:两组都配,主键优先。多写两行不会有副作用。
十、验证方法
10.1 看日志
启动后正常应该出现:
arduino
Hadoop security configuration is legal, use the secured FileSystem.
如果出现这两类,说明配置没被识别:
csharp
Proxy user is not supported ← principal 未生效
Keytab file not found / not readable ← 路径或权限问题
10.2 用 SET -v 确认配置真的加载了
ini
SET -v;
应该能看到:
bash
spark.sql.catalog.paimon org.apache.paimon.spark.SparkCatalog
spark.sql.catalog.paimon.security.kerberos.login.principal kyuubi/node01.example.com@EXAMPLE.COM
spark.sql.catalog.paimon.security.kerberos.login.keytab /opt/kyuubi/kyuubi.keytab
这一条很关键:很多人改完配置重启了,但配置没进到 engine 里,靠这个能一眼看出来。
10.3 实际建表验证
sql
CREATE DATABASE IF NOT EXISTS paimon_test;
CREATE TABLE paimon_test.t1 (id INT, name STRING);
INSERT INTO paimon_test.t1 VALUES (1, 'a');
SELECT * FROM paimon_test.t1;
十一、升级视角:新版本栈还要检查什么
这次验证的意义不止于修好一个配置。
如果你也在规划 Kyuubi / Spark 大版本升级 + 插件同步升级,建议按下面这张表逐项过:
| 检查项 | 为什么 | 怎么验证 |
|---|---|---|
| Catalog 级 Kerberos 配置 | 新版插件可能新增安全层,要求显式提供 principal | 各 Catalog 的 security.* 键是否配全 |
| impersonation 是否仍被所有插件支持 | 有的插件开始拒绝 PROXY 身份 | 用代理用户跑一遍建表/写入 |
| 配置键名前缀变化 | 新版本可能改变相对键与前缀拼接规则 | SET -v 核对实际生效值 |
| Hudi / Ranger 插件的等价变化 | 同一批升级,行为可能同步收紧 | 各自的 Catalog 与权限路径逐一验证 |
| keytab 分发与权限 | 新组件可能以不同用户启动 | 检查文件属主与读权限 |
| 回退方案 | 新栈验证不通过时能否快速切回 | 保留旧引擎池 / 双栈并行 |
一个判断原则
升级不是"换个 jar",是"换一套默认行为"。
旧版本能跑,往往不是因为配置对,而是因为你恰好没触发那条新路径。
十二、总结
12.1 五个判断
① 「认证失败」不等于「认证材料缺失」
这次的根因不是 keytab 没配,而是 Paimon 主动放弃了代理身份这条路径。
看到 Kerberos 报错,先分清是"凭证问题"还是"路径问题"。
② 两个「更安全」的设计,撞在一起就都不工作
Kyuubi 的 impersonation 是安全特性,Paimon 1.3.1 的安全加固也是安全特性。
但前者产生 PROXY 身份,后者拒绝 PROXY 身份。
③ 防御代码写了一半,比不写更难查
isLegal() 在「两个都没配」时返回 true,于是所有未配置的环境都会进入安全通道,然后炸在下一步。
如果它返回 false,就会退回旧行为,问题根本不会出现。
④ 遇到奇怪的报错,去看那一行的上下文
throwProxyUserNotSupported() 只有三行业务逻辑,但它背后是一个完整的三分支决策树。
报错信息往往只说了「否」,没说「为什么否」------为什么否,要自己从源码里读出来。
⑤ 新栈验证的价值,在于"提前暴露"
现网那套 Kyuubi 1.10.2 + Spark 3.4.1/3.5.5 是稳定的。
但这个稳定,有一部分是"版本恰好没触发"带来的。
升级前把这些坑跑一遍,比升级后被业务追着问要划算得多。
12.2 排查 Checklist
| 检查项 | 方法 |
|---|---|
| Paimon 版本 | SELECT version() / 查 jar 版本 |
| 当前 UGI 认证方式 | 日志中搜 AuthenticationMethod |
principal 是否配置 |
SET -v |
| keytab 路径是否真实存在 | ls -l <keytab> |
| keytab 内 principal 是否一致 | klist -kt <keytab> |
| 能否用 keytab 登录 | kinit -kt ... |
| 配置是否生效 | 日志中搜 security configuration is legal |
| Catalog 前缀是否正确 | 必须是 spark.sql.catalog.<name>.security.kerberos.login.* |
参考
- Apache Paimon:
HadoopSecuredFileSystem、KerberosLoginProvider、SecurityConfiguration(1.3.1) - Apache Paimon:
org.apache.paimon.options.ConfigOption#withFallbackKeys - Hadoop:
UserGroupInformation.AuthenticationMethod.PROXY、impersonation 机制 - Apache Kyuubi:Kerberos 认证与 impersonation 配置