问题背景
我们有两套Hive集群,其中一套配置了Kerberos,另一套没有配置。我们在OpenLookeng中创建了两个catalog分别对应这两个Hive集群,hive_unsecurity和hive_security。然后通过JDBC的方式访问OpenLookeng。hive_security的配置如下:
hive.hdfs.authentication.type=KERBEROS
hive.hdfs.presto.principal=******
hive.hdfs.presto.keytab=/etc/security/keytabs/hdfs.headless.keytab
hive.metastore.service.principal=******
hive.metastore.authentication.type=KERBEROS
hive.metastore.uri=thrift://ip:9083,thrift://ip:9083
hive.metastore.krb5.conf.path=/etc/krb5.conf
hive.metastore.client.keytab=/etc/security/keytabs/hdfs.headless.keytab
hive.metastore.client.principal=******
hive.config.resources=/etc/hadoop/conf/core-site.xml,/etc/hadoop/conf/hdfs-site.xml,/etc/hadoop/conf/mapred-site.xml,/etc/hadoop/conf/yarn-site.xml,
hive.allow-rename-table=true
hive.allow-drop-table=true
hive.parquet.use-column-names=true
除此之外,还需要做一些配置,可以参考OpenLookeng官网中的FAQ:[openLooKeng documentation](https://docs.openlookeng.io/zh/docs/docs/faq/faq.html),主要是这两条:
-
openLooKeng如何连接两个配置了kerberos的hive数据源?
-
首先保证两个配置了kerberos的hive数据源域名不相同;
-
然后合并两个数据源的krb5.conf文件,将其中一个的realm信息加入另一个krb5.conf文件即可
-
将合并后的krb5.conf文件放在jvm.config中配置的路径下。举例如下
-
-
openLooKeng如何同时连接一个配置了kerberos的hive和一个没有配置kerberos的hive数据源?
-
在没有配置kerberos的hive数据源的core-site.xml文件中增加如下配置即可:
<property> <name>ipc.client.fallback-to-simple-auth-allowed</name> <value>true</value> </property>
-
安装上面说的配置完成后,可以正常访问两个Hive数据源了。
出现的问题
通过OpenLookeng做跨源分析,我们再也不用来回的迁移数据了,但是快乐的时光总是短暂的,风雨总在阳光后。使用了几天之后突然出现了一个查询异常,在查询hive_unsecurity中的hudi表时抛出了一个异常:Can't get Master Kerberos principal for user renewer导致查询失败。这个异常如果是在查询hive_security数据时出现的倒好解释一点,但是现在访问的是未配置kerberos的Hive数据,为什么会有kerberos相关的流程呢?
首先看下报错的堆栈:
正常来讲,我们访问hive_unsecurity是不需要获取Token的,那我们先看看代码是根据什么来判断需不需要获取Token的吧。
//org.apache.hadoop.mapreduce.security.TokenCache
public static void obtainTokensForNamenodes(Credentials credentials, Path[] ps, Configuration conf) throws IOException {
if (UserGroupInformation.isSecurityEnabled()) {
obtainTokensForNamenodesInternal(credentials, ps, conf);
}
}
继续看:
//org.apache.hadoop.security.UserGroupInformation
public static boolean isSecurityEnabled() {
return !isAuthenticationMethodEnabled(UserGroupInformation.AuthenticationMethod.SIMPLE);
}
接着看:
//org.apache.hadoop.security.UserGroupInformation
private static boolean isAuthenticationMethodEnabled(UserGroupInformation.AuthenticationMethod method) {
ensureInitialized();
return authenticationMethod == method;
}
UserGroupInformation.isSecurityEnabled()返回值是根据authenticationMethod == method来判断的,method传递的是UserGroupInformation.AuthenticationMethod.SIMPLE,authenticationMethod是UserGroupInformation的类属性,是在初始化的时候设置的:
//org.apache.hadoop.security.UserGroupInformation
private static synchronized void initialize(Configuration conf, boolean overrideNameRules) {
authenticationMethod = SecurityUtil.getAuthenticationMethod(conf);
...
}
//org.apache.hadoop.security.UserGroupInformation
public static AuthenticationMethod getAuthenticationMethod(Configuration conf) {
String value = conf.get("hadoop.security.authentication", "simple");
try {
return (AuthenticationMethod)Enum.valueOf(AuthenticationMethod.class, StringUtils.toUpperCase(value));
} catch (IllegalArgumentException var3) {
throw new IllegalArgumentException("Invalid attribute value for hadoop.security.authentication of " + value);
}
}
分析到这里,需不需要获取Token的流程应该是清楚了,应该是根据conf中的配置hadoop.security.authentication来判断的,如果配置的是simple,则不需要获取,否则的话就需要获取了。那先检查一下我们的hive_unsecurity对应的配置吧,确实是simple。那是程序中某个地方给我们搞混了?打印一下日志看看吧。于是在obtainTokensForNamenodes方法中加上一些日志,打印一下conf中的hadoop.security.authentication值和UserGroupInformation.isSecurityEnabled()的返回值。结果傻眼了,hadoop.security.authentication值为’simple‘,UserGroupInformation.isSecurityEnabled()返回值为true.驴唇对马尾呀这是。
再看看这个方法:
//org.apache.hadoop.mapreduce.security.TokenCache
public static void obtainTokensForNamenodes(Credentials credentials, Path[] ps, Configuration conf) throws IOException {
if (UserGroupInformation.isSecurityEnabled()) {
obtainTokensForNamenodesInternal(credentials, ps, conf);
}
}
实质就是这段代码:
return authenticationMethod == method;
method没有问题,是直接传递进来的参数。authenticationMethod是初始化的时候根据传递的conf赋的值,conf刚才我们也通过日志确认了,没有问题呀。这怎么能行了,都没有问题,那就是我的问题了,再研究研究。此时,灵光一现,让你们看下:
//org.apache.hadoop.security.UserGroupInformation
private static UserGroupInformation.AuthenticationMethod authenticationMethod;
想到啥了吗?static!!! 静态属性在JVM中只有一份呀,感觉要破案了,可能的情况应该是这样的,有两个查询请求先后到达coordinator,分别是访问hive_unsecurity和hive_security的,访问hive_unsecurity的查询执行到obtainTokensForNamenodes的时候,hive_security的查询进程修改了authenticationMethod的值,因此导致异常发生。
验证
猜想了上面的可能情况,我们接下来验证一下吧。首先找到coordinator进程id,然后看下这个JVM中的UserGroupInformation实例信息:
然后看下authenticationMethod:
JVM中有两个authenticationMethod值,这两个是通过不同的classLoader加载进来的,但是我们在查询hive_unsecurity和hive_security的时候,只有375ff309这个值会发生变化,而且跟我们猜想的一样,查询hive_unsecurity的时候,authenticationMethod值就变成了simple,查询hive_security的时候authenticationMethod值又变成了kerberos。说明跟我们猜想的一样,确实是因为并发访问authenticationMethod这个静态属性时不安全导致的异常。
修改方案
这个异常只有在查询hudi表的时候才会遇到,所以解决的地方应该在处理hudi的地方,但是为了简单,我们修改了obtainTokensForNamenodes的判断条件,通过conf来判断要不要获取Token。
总结
问题比较简单,只有一个知识点,类的静态属性在JVM中只有一份,所以要小心呢。