写点什么

被这个参数三杀了

作者:hncscwc
  • 2022 年 6 月 14 日
  • 本文字数:5533 字

    阅读完需:约 18 分钟

最近接连排查了几个问题,居然都是同一个参数引起的,本文就通过实际案例讲述下该参数如何引发问题的,以及问题最终又是如何解决的~

【First Blood】

在我们的环境中,RM 是基于 HA 的方式部署的并且 RM 是基于容器的方式运行的,即两个 RM 运行在各自的容器中;同时,我们还开启了 kerberos 认证,因此两个 RM 的 hostname 配置的是域名。主要的配置信息如下所示:

<property>    <name>yarn.resource.ha.enabled</name>    <value>true</value></property><property>    <name>yarn.resourcemanager.ha.automatic-failover.enable</name>    <value>true</value></property><property>    <name>yarn.resourcemanager.ha.rm-ids</name>    <value>rm1,rm2</value></property><property>    <name>yarn.resourcemanager.hostname.rm1</name>    <value>rm-0.svc.cluster.local</value></property><property>    <name>yarn.resourcemanager.hostname.rm2</name>    <value>rm-1.svc.cluster.local</value></property>
复制代码

在一次测试过程中,RM 的其中一个(容器所在)节点异常宕机了,此后向 RM 提交了一个任务,但该任务的 AM 启动后就失败了,报错信息为:

java.lang.IllegalArgumentException: java.net.UnknownHostException: hadoop-resourcemanager-0.hadoop-resourcemanager.hncscwc-198.svc.cluster.local1        at org.apache.hadoop.security.SecurityUtil.buildTokenService(SecurityUtil.java:418)        at org.apache.hadoop.yarn.client.ClientRMProxy.getTokenService(ClientRMProxy.java:153)        at org.apache.hadoop.yarn.client.ClientRMProxy.getAMRMTokenService(ClientRMProxy.java:138)        at org.apache.hadoop.yarn.client.ClientRMProxy.setAMRMTokenService(ClientRMProxy.java:80)        at org.apache.hadoop.yarn.client.ClientRMProxy.getRMAddress(ClientRMProxy.java:99)        at org.apache.hadoop.yarn.client.ConfiguredRMFailoverProxyProvider.getProxyInternal(ConfiguredRMFailoverProxyProvider.java:76)        at org.apache.hadoop.yarn.client.ConfiguredRMFailoverProxyProvider.getProxy(ConfiguredRMFailoverProxyProvider.java:90)        at org.apache.hadoop.io.retry.RetryInvocationHandler$ProxyDescriptor.<init>(RetryInvocationHandler.java:197)        at org.apache.hadoop.io.retry.RetryInvocationHandler.<init>(RetryInvocationHandler.java:317)        at org.apache.hadoop.io.retry.RetryInvocationHandler.<init>(RetryInvocationHandler.java:311)        at org.apache.hadoop.io.retry.RetryProxy.create(RetryProxy.java:59)        at org.apache.hadoop.yarn.client.RMProxy.createRMProxy(RMProxy.java:120)        at org.apache.hadoop.yarn.client.RMProxy.createRMProxy(RMProxy.java:93)        at org.apache.hadoop.yarn.client.ClientRMProxy.createRMProxy(ClientRMProxy.java:72)        at org.apache.hadoop.mapreduce.v2.app.rm.RMCommunicator.createSchedulerProxy(RMCommunicator.java:311)        at org.apache.hadoop.mapreduce.v2.app.rm.RMCommunicator.serviceStart(RMCommunicator.java:117)        at org.apache.hadoop.mapreduce.v2.app.rm.RMContainerAllocator.serviceStart(RMContainerAllocator.java:263)
复制代码

RM 配置了高可用,其中一个宕机,另外一个 RM 也确实提升为 Active 了,任务也能正确提交和调度,但为什么运行就报错了呢?

顺着报错的堆栈信息,走读相关的代码,我们发现了问题的所在。

在《YARN 任务运行中的 token》中提到了 yarn 任务的 AM 在启动后,会从指定的文件中加载 AMRMToken,而 rm 的客户端在初始化时需要给 token 设置服务端的地址,也就是 rm 的地址。

关键代码如下所示:

获取token服务


获取token服务对应的ip地址

在 buildTokenService 中,判断如果必须使用 IP(userIpForTokenService),则会对 rm 的域名进行解析,如果无法解析出具体的 ip 地址,则抛出异常;异常会逐层往上抛,最终导致程序退出。

结合实际情况来分析,由于其中一个 rm 出现了宕机,其域名确实无法解析出对应的 ip 来,因此这也就是导致任务失败的根本原因。

至于 useIpForTokenService 的值,是由配置项 hadoop.security.token.service.use_ip 来决定的,默认为 true,即 tokenService 需要使用 ip,而不是域名。

最终,将该配置参数设置为 false 后,再次测试验证,在同样的场景下,任务可以正确提交和运行。


【Double Kill】

在上面问题解决后的第二天,重新部署环境时,发现 jobHistoryServer 由于无法正确进行 kerberos 认证,导致启动失败,具体报错信息为:

org.apache.hadoop.yarn.exceptions.YarnRuntimeException: History Server Failed to login        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.serviceInit(JobHistoryServer.java:130)        at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163)        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.launchJobHistoryServer(JobHistoryServer.java:231)        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.main(JobHistoryServer.java:241)              Caused by: java.io.IOException: Login failure for hadoop/172.16.20.18@BIGDATA.COM from keytab /home/hncscwc/hadoop/etc/hadoop/hdfs.keytab: javax.security.auth.login.LoginException: Unable to obtain password from user
at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1144) at org.apache.hadoop.security.SecurityUtil.login(SecurityUtil.java:286) at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.doSecureLogin(JobHistoryServer.java:183) at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.serviceInit(JobHistoryServer.java:128) ... 3 moreCaused by: javax.security.auth.login.LoginException: Unable to obtain password from user
at com.sun.security.auth.module.Krb5LoginModule.promptForPass(Krb5LoginModule.java:901) at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:764) at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:618) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) at javax.security.auth.login.LoginContext.login(LoginContext.java:587) at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:522) at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1135) ... 6 more
复制代码

问题出现后,先对 jobHistoryServer 的配置文件进行了确认,发现与之前是一样的,并没有什么不对的地方,关键配置项如下所示:

<property>    <name>mapreduce.jobhistory.address</name>    <value>172.16.20.18</value></property><property>    <name>mapreduce.jobhistory.principal</name>    <value>hadoop/_HOST@BIGDATA.COM</value></property>
复制代码

jobHistoryServer 启动后进行 kerberos 登陆时,会对 principal 中的_HOST 进行替换,替换内容为 mapreduce.jobhistory.address 的值,即最终以 ip 形式进行了替换,导致认证失败。

正常来说,向 kdc 登陆认证的 principal 应该是包含服务的主机名,而不是 ip 地址,并且之前在这种配置下也都没有任何问题,怎么突然就不正常了?

还是结合源码进行分析,找到了问题所在:

配置项 hadoop.security.token.service.use_ip 配置为 true 或 false 时,内部会产生不同的主机地址解析对象:

  • 设置为 true 时为 StandardHostResolver

  • 设置为 false 时为 QualifiedHostResolver

两者对于 host 地址为 ipv4 的解析有所不同

对于 StandardHostResolver:

StandardHost解析方式

对于 QualifiedHostResolver:

QualifiedHost解析方式

也就是说,StandardHostResolver 可以通过 getByName 正确解析出 ip 对应的主机名,而后者直接将 ip 返回。

之前 hadoop.security.token.service.use_ip 配置为 true,因此配置项 mapreduce.jobhistory.address 即便配置为 ip,也能正确解析出对应的主机名,然后在 principal 替换_HOST 时,也是正确的。

修改上面的问题后,将配置的值改为了 false,就导致了该问题的出现。

最后,我们通过将配置项 mapreduce.jobhistory.address 的值修改为主机名解决了该问题。


【Triple Kill】

没有问题的日志维持了两三天,再次遇到问题,这次的现象是在 sparkHistory 节点上向 hdfs 上传文件失败

首先,我们先在其它节点上进行了同样的操作,发现向 hdfs 上传文件是没有任何问题的,同时查看 nn/dn 的进程情况,结合对应日志确认 nn/dn 都是正常的

接着,我们重新回到该节点上,检查了 hdfs-site.xml 中的相关配置项,没有发现异常的地方,然后向 nn/dn 的节点执行了 ping 操作,确认网络也没有问题,但发现 hdfs 相关的所有操作都失败。

最后,通过 tcpdump 进行了抓包分析,发现与 nn 建立连接时,tcp 的源端地址为 127.0.0.1,这导致 syn 发送后,根本得不到 sync ack 应答。而正常情况下,源 ip 应该是该节点自身的 ip。

抓包信息

进一步调整日志为 TRACE 后,发现是在 hdfs 的客户端进行了 bind 的操作

22/04/19 17:10:50 TRACE ipc.ProtobufRpcEngine: 1: Call -> namenode.svc.cluster.local/172.16.22.234:9000: getFileInfo {src: "/"}22/04/19 17:10:50 DEBUG ipc.Client: The ping interval is 60000 ms.22/04/19 17:10:50 DEBUG ipc.Client: Connecting to namenode.svc.cluster.local/172.16.22.234/900022/04/19 17:10:50 TRACE secruity.SecruityUtil: Name lookup for spark-history.svc.cluster.local took 3ms.22/04/19 17:10:50 DEBUG ipc.Client: Binding hadoop/spark-history.svc.cluster.local@BIGDATA.COM to spark-history.svc.cluster.local.localdomain/127.0.0.1
复制代码

既然知道是 hdfs 客户端中对 socket 进行了源 IP 的绑定动作,那么就结合源码梳理下 hdfs 客户端向 nn 建立连接的逻辑:

在开启 kerberos 认证的场景中,客户端向 nn 建立连接的流程包括:创建 socket,然后从 ticket 中解析出 bind 地址并进行 bind 操作,最后进行连接。

从 ticket 中解析出本地 bind 地址的具体步骤又分为:

  • 从 ticket 中获取 principal

  • 从 principal 中获取主机名

  • 如果主机名为空,则不进行 bind 操作

  • 如果主机名非空,对主机名进行解析,如果解析后的地址非空,则进行 bind 操作。

对于主机名解析又分为两种情况

如果配置项 "hadoop.security.token.service.use_ip" 的值为 true,则直接获取主机名对应的 ip,如果为 false,则继续按下面的逻辑解析(其本意是想要获取主机名对应的完全合规域名)

  • 如果主机名为 ipv4,通过 ip 地址获取对应的全域名

  • 如果主机名以 "." 结尾,直接获取主机名对应的全域名

  • 如果主机名包含 ".",先在主机名末尾加上 ".",并继续上一步的逻辑解析,如果解析出的域名为空,则在主机名末尾依次添加 "/etc/resolve.conf" 中的 "search" 指定的域,进行主机名的解析

以实际情况来分析:

  • sparkHistory 进程 kerberos 登陆使用的 principal 为 "hadoop/spark-history.svc.cluster.local@BIGDATA.COM"

  • 从 pincipal 中解析出主机名为 "spark-history.svc.cluster.local"

  • 配置项 "hadoop.security.token.service.use_ip"的值为 false,因此进入全域名的解析流程。主机名不是完全合规(即不是以"."结尾),但又包含了".",因此先在末尾加上".",使其成为完全合规域名,并按照该域名来解析。

  • 而由于 sparkHistory 所在的容器,配置了就绪探针,容器未就绪时,无法解析出任何地址。因此继续在 "spark-history.svc.cluster.local." 后再加上 "/etc/resolve.conf" 中 search 指定的域,便利进行全域名解析,如果其中任意一个能解析出地址,则退出循环

  • 该节点中 "/etc/resolve.conf" 中的 search 中仅有一个 localdomain,因此以 "spark-history.svc.cluster.local.localdomain" 来解析,解析出的 ip 恰好就是 127.0.0.1,导致了问题的出现

该节点的 /etc/resolve.conf 文件中之所以只有 "search localdomain",怀疑是人为进行了修改导致的。最后,修改该文件,问题得以解决。


【总结】

通过这三个问题分析定位解决,对 "hadoop.security.token.service.use_ip" 有了较深入的理解,同时也深刻领会源码是不会骗人的

发布于: 刚刚阅读数: 5
用户头像

hncscwc

关注

与其搜索别人的总结不如搜索自己的总结 2021.06.21 加入

公众号【hncscwc】

评论

发布
暂无评论
被这个参数三杀了_hadoop_hncscwc_InfoQ写作社区