被這個引數三殺了

語言: CN / TW / HK

最近接連排查了幾個問題,居然都是同一個引數引起的,本文就通過實際案例講述下該引數如何引發問題的,以及問題最終又是如何解決的~

【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的地址。

關鍵程式碼如下所示:

在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 more
Caused 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/[email protected]</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:

對於QualifiedHostResolver:

也就是說,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。

那麼這裡為什麼會是127.0.0.1,是誰指定的127.0.0.1?

繼續通過strace來分析,發現socket建立後,主動進行了一次bind源地址的操作,並且繫結的源端地址就是127.0.0.1

進一步調整日誌為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/9000
22/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/[email protected]"

  • 從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"有了較深入的理解,同時也深刻領會原始碼是不會騙人的

好了,這就是本文的全部內容,如果覺得本文對您有幫助,不要吝嗇點贊在看轉發,也歡迎加我微信交流~

本文分享自微信公眾號 - hncscwc(gh_383bc7486c1a)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。