kerberos下HBase訪問Zookeeper的ACL權限訪問列表問題解決過程記錄

轉自:http://www.aboutyun.com/thread-14977-1-1.html
最近公司HBase(CDH-4.6.0)遇到了一個麻煩問題,以爲有必要記錄下整個解決的過程。html

一、問題原由

用戶在跑mapreduce任務,從hdfs讀取文件想寫入到hbase table的時候失敗了(這是hbase提供的一種mapred能力)。這個問題發如今A環境(一個測試環境),自從啓用了kerberos以後。運行了用戶給的程序和本身寫的sample以後,發現程序最後掛在NullPointerException上。這個NPE指示的是服務端的一個叫currentKey的變量爲null。java

org.apache.hadoop.hbase.ipc.ExecRPCInvoker$1@58e395e8,java.io.IOException: java.io.IOException: java.lang.NullPointerException
at  org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.createPassword(AuthenticationTokenSecretManager.java:129) 
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.createPassword(AuthenticationTokenSecretManager.java:57)
at org.apache.hadoop.security.token.Token.<init>(Token.java:70)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.generateToken(AuthenticationTokenSecretManager.java:162)
at org.apache.hadoop.hbase.security.token.TokenProvider.getAuthenticationToken(TokenProvider.java:91) 
at sun.reflect.GeneratedMethodAccessor56.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.hadoop.hbase.regionserver.HRegion.exec(HRegion.java:5610)
at org.apache.hadoop.hbase.regionserver.HRegionServer.execCoprocessor(HRegionServer.java:3918)
at sun.reflect.GeneratedMethodAccessor39.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.hadoop.hbase.ipc.SecureRpcEngine$Server.call(SecureRpcEngine.java:311)

AuthenticationTokenSecretManagernode

@Override
 protected byte[] createPassword(AuthenticationTokenIdentifier identifier) {
   long now = EnvironmentEdgeManager.currentTimeMillis();
   AuthenticationKey secretKey = currentKey;  //currentKey賦給secretKey
   identifier.setKeyId(secretKey.getKeyId()); //NPE在這裏拋出的,也就是currentKey爲null
   identifier.setIssueDate(now);
   identifier.setExpirationDate(now + tokenMaxLifetime);
   identifier.setSequenceNumber(tokenSeq.getAndIncrement());
   return createPassword(WritableUtils.toByteArray(identifier),
       secretKey.getKey());
 }

二、問題定位

既然currentKey爲null,那咱們就去找它在哪裏賦值的。閱讀源碼以後,瞭解到整個過程是這樣的:
1.在開啓kerberos以後,每一個RegionServer都會有一個AuthenticationTokenSecretManager用來管理token。
2.這些manager中,只有一個leader,只有它能生產token,而後放到zookeeper裏。其它manager經過感知zookeeper的變化來同步leader生產的token。leader經過競爭產生,誰先在ZK上建立 /hbase/tokenauth/keymaster 節點,誰就是leader。
AuthenticationTokenSecretManager$LeaderElector:apache

public void run() {
    zkLeader.start();
    zkLeader.waitToBecomeLeader();  //沒有成爲leader的人會一直阻塞在這裏,直到感知到當前leader掛掉纔會開始新一輪競爭
    isMaster = true;
    while (!stopped) {
      long now = EnvironmentEdgeManager.currentTimeMillis();
      // clear any expired
      removeExpiredKeys(); //清除過時的token,同時也把它從ZK上移除
      if (lastKeyUpdate + keyUpdateInterval < now) {  //默認的週期是1天
        // roll a new master key
        rollCurrentKey();  //就是這個函數產生新的token,替換currenKey
      }
      try {
        Thread.sleep(5000);
      } catch (InterruptedException ie) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Interrupted waiting for next update", ie);
        }
      }
    }
  }

AuthenticationTokenSecretManager:app

synchronized void rollCurrentKey() {
    if (!leaderElector.isMaster()) {
      LOG.info("Skipping rollCurrentKey() because not running as master.");
      return;
    }
    long now = EnvironmentEdgeManager.currentTimeMillis();
    AuthenticationKey prev = currentKey;
    AuthenticationKey newKey = new AuthenticationKey(++idSeq,
        Long.MAX_VALUE, // don't allow to expire until it's replaced by a new key
        generateSecret());
    allKeys.put(newKey.getKeyId(), newKey);
    currentKey = newKey;           //滾動currentKey,置爲newKey
    zkWatcher.addKeyToZK(newKey);  //把新的token放到zookeeper
    lastKeyUpdate = now;
    if (prev != null) {
      // make sure previous key is still stored
      prev.setExpiration(now + tokenMaxLifetime); //prev是原來的newKey,是不會過時的,當有新的newKey替代它後,它的期限默認設置是7天
      allKeys.put(prev.getKeyId(), prev);
      zkWatcher.updateKeyInZK(prev);
    }
  }

3.既然token是由leader生產的,除非沒有leader,纔會沒人生產。驗證這個想法,我在zookeeper和一些region server啓動當天的日誌裏找到了證據:
a) zk中的 /hbase/tokenauth/keymaster 節點用來存放leader的信息,而後進入zookeeper-client查看了下,根本沒這個節點。
b) 一些尚有保留集羣啓動當天日誌的region server上找到了以下異常:ide

org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager: Zookeeper initialization failed
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keys
at org.apache.zookeeper.KeeperException.create(KeeperException.java:113)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.createNonSequential(RecoverableZooKeeper.java:421)
at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.create(RecoverableZooKeeper.java:403)
at org.apache.hadoop.hbase.zookeeper.ZKUtil.createWithParents(ZKUtil.java:1164)
at org.apache.hadoop.hbase.zookeeper.ZKUtil.createWithParents(ZKUtil.java:1142)
at org.apache.hadoop.hbase.security.token.ZKSecretWatcher.start(ZKSecretWatcher.java:58)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.start(AuthenticationTokenSecretManager.java:105)
at org.apache.hadoop.hbase.ipc.SecureRpcEngine$Server.startThreads(SecureRpcEngine.java:275)
at org.apache.hadoop.hbase.ipc.HBaseServer.start(HBaseServer.java:1650)
at org.apache.hadoop.hbase.regionserver.HRegionServer.startServiceThreads(HRegionServer.java:1728)
at org.apache.hadoop.hbase.regionserver.HRegionServer.handleReportForDutyResponse(HRegionServer.java:1105)
at org.apache.hadoop.hbase.regionserver.HRegionServer.run(HRegionServer.java:753)
at java.lang.Thread.run(Thread.java:662)

這是AuthenticationTokenSecretManager啓動時候失敗了,啓動的時候會先在ZK上建立/hbase/tokenauth/keys這個目錄(即使這個目錄已經存在也會執行這個操做,這是一種保證),這個目錄用來存放leader生成的token。結果你們都沒有/hbase/tokenauth的權限,因此都失敗了(NoAuth for /hbase/tokenauth/keys,這裏的提示有點瑕疵,實際上/hbase/tokenauth沒有權限致使的)。然而發生這樣的嚴重錯誤,server的啓動並無被終止,而是繼續運行下去,留下了隱患。
AuthenticationTokenSecretManager:函數

public void start() {
   try {
     // populate any existing keys
     this.zkWatcher.start(); //這裏拋出的KeeperException 
     // try to become leader
     this.leaderElector.start(); //這裏競爭leader,可是由於異常這裏不會被執行,因此沒有人去競爭leader
   } catch (KeeperException ke) {
     LOG.error("Zookeeper initialization failed", ke); //發生異常,僅僅是打印一條error信息,而沒有abort。在Hbase的不少地方,發生這樣的錯誤都是會abort server的。
   }
 }

4.錯誤緣由就是/hbase/tokenauth權限問題,在zookeeper-client裏查看了下它的權限是這樣的:oop

[zk: localhost:2181(CONNECTED) 0] getAcl /hbase/tokenauth
'sasl,'hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM
: cdrwa

但很奇怪的是無論我切換什麼帳戶也沒法訪問這個節點,想經過setAcl設置它的權限爲anyone也是失敗的。緣由很顯然,由於我不是「hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM」,我沒任何權限操做。測試

Authentication is not valid : /hbase/tokenauth

可爲何4048這臺機子也沒能成爲leader呢(問題[1])?ui

三、第一次解決(day 0)

嘗試各類辦法也沒法得到/hbase/tokenauth的控制權,咱們只好暫時經過在zookeeper配置文件zoo.cfg添加參數skipACL=yes,重啓zookeeper,這樣不會驗證ACL。
重啓hbase,觸發AuthenticationTokenSecretManager.start,你們開始競爭成爲leader,因而有了leader,leader是4048這臺機子。
而後再經過zookeeper-client的setAcl命令把這個點的權限改爲anyone,再關閉skipACL,重啓zookeeper。
這些是我同事操做的,操做完以後集羣一切正常,mapreduce也能夠跑了。不過還有一個隱患,我注意到了/hbase/tokenauth/keys的權限也是4048專屬,若是4048掛掉了,別人也沒法順利成爲leader,可是想一想它掛掉的機率比較低,等它掛掉再說吧,因而就沒去理會了。

四、問題2 (day 1)

今天中午的時候,集羣忽然奔潰了,全部region server都掛掉了。 我上去查了一下日誌,結果居然和我昨天考慮到的隱患同樣,4048掛掉了,而後其餘人競爭leader的時候沒有權限也掛掉了。4048爲何會掛掉(問題[2])? 當時我沒怎麼看4048的日誌,不知道它爲何掛掉,只以爲很巧。
這是從4050這臺機子的region server上截取的兩條日誌,它先是成爲了leader,而後由於沒有權限維護/hbase/tokenauth/keys,天然想訪問裏面的key也是失敗的。其餘機子掛掉的緣由也同樣。

2015-08-25 14:35:08,273 DEBUG org.apache.hadoop.hbase.zookeeper.ZKLeaderManager: Claimed the leader znode as 'SVR4050HW2285.hadoop.xxx.com,60020,1440397852179'
2015-08-25 14:35:08,288 FATAL org.apache.hadoop.hbase.regionserver.HRegionServer: ABORTING region server SVR4050HW2285.hadoop.xxx.com,60020,1440397852179: Unable to synchronize secretkey 3 in zookeeper
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keys/3
at org.apache.zookeeper.KeeperException.create(KeeperException.java:113)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.setData(ZooKeeper.java:1266)
at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.setData(RecoverableZooKeeper.java:349)
at org.apache.hadoop.hbase.zookeeper.ZKUtil.updateExistingNodeData(ZKUtil.java:814)
at org.apache.hadoop.hbase.security.token.ZKSecretWatcher.updateKeyInZK(ZKSecretWatcher.java:197)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.rollCurrentKey(AuthenticationTokenSecretManager.java:257)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager$LeaderElector.run(AuthenticationTokenSecretManager.java:317)

五、第二次解決(day 1)

添加skipACL後重啓ZK,重啓HBase。就這樣暫時保持skipACL開啓,保證hbase正常運行。

六、思考(day 2)

咱們總不能這樣開着skipACL,這對資源隔離不是很友好。我查看了下HBase的ZKUtil.java的代碼。
這是建立ZNode時候,建立ACL的函數。它對一些特定節點使用CREATOR_ALL_AND_WORLD_READABLE權限,其他使用CREATOR_ALL_ACL權限。前者是建立者有全部權限,其他人有隻讀權限。後者是建立者有全部權限。

private static ArrayList<ACL> createACL(ZooKeeperWatcher zkw, String node) {
   if (isSecureZooKeeper(zkw.getConfiguration())) {
     // Certain znodes are accessed directly by the client,
     // so they must be readable by non-authenticated clients
     if ((node.equals(zkw.baseZNode) == true) ||
         (node.equals(zkw.rootServerZNode) == true) ||
         (node.equals(zkw.masterAddressZNode) == true) ||
         (node.equals(zkw.clusterIdZNode) == true) ||
         (node.equals(zkw.rsZNode) == true) ||
         (node.equals(zkw.backupMasterAddressesZNode) == true) ||
         (node.startsWith(zkw.assignmentZNode) == true) ||
         (node.startsWith(zkw.masterTableZNode) == true) ||
         (node.startsWith(zkw.masterTableZNode92) == true)) {
       return ZooKeeperWatcher.CREATOR_ALL_AND_WORLD_READABLE;
     }
     return Ids.CREATOR_ALL_ACL;
   } else {
     return Ids.OPEN_ACL_UNSAFE;
   }
 }

/hbase/tokenauth及其子節點顯然使用的是CREATOR_ALL_ACL權限。那4048建立了key,而後又掛掉的話,那其它機子顯然不可能成爲leader。這種權限設定彷佛有點不科學。
由於B環境權限都很正常的,沒出什麼問題,我又對比了下A和B的權限和配置。
B leader生產的token的權限:

[zk: localhost:2181(CONNECTED) 4] getAcl /hbase/tokenauth/keys/67
'sasl,'hbase
: cdrwa

A leader生產的token的權限:

[zk: localhost:2181(CONNECTED) 1] getAcl /hbase/tokenauth/keys/2
'sasl,'hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM
: cdrwa

前者很是統一的使用hbase這個principal,後者則帶上了hostname。
問題一定出在這裏!

我又對比了hbase的zk-jaas.conf,沒區別。這個配置文件裏配置了訪問zk的principal,它們都是帶hostname的。

Client {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
useTicketCache=false
keyTab="/etc/hbase.keytab"
principal="hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM";
};

可爲何B最後的principal卻沒帶hostname,我又對比了zookeeper的配置文件zoo.cfg。
B的有下面兩行設置:

kerberos.removeHostFromPrincipal=true
kerberos.removeRealmFromPrincipal=true

而A呢?竟然也有。。。
和同事討論了下,他告訴我A這兩行配置不是一開始就有的,是後來加上去的,當時A最先上kerberos還出了不少問題。我瞬間就懂了,一切疑惑都解開了。
問題[1]:爲何4048這臺機子也沒能成爲leader呢?
由於當初集羣最先上kerberos啓動的時候沒加那兩行remove配置,因此/hbase/tokenauth和/hbase/tokenauth/keys的權限都是歸4048專屬。後來由於出了問題,這兩行配置被加上去,hbase重啓。此時你們的principal都變成了hbase(包括4048),沒有人能訪問這個4048專屬的目錄。因而包括4048在內,沒人成爲leader。
問題[2]:4048爲何會掛掉?
這個是由於咱們第一次解決的時候,只修復了/hbase/tokenauth而沒有修復/hbase/tokenauth/keys,它的權限依然是4048全部。

[zk: localhost:2181(CONNECTED) 0] getAcl /hbase/tokenauth/keys
'sasl,'hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM
: cdrwa

當時重啓hbase的時候仍是開着skipACL的,因此leader順利的在/hbase/tokenauth/keys下面建立了token,集羣正常啓動,一切正常。
而後咱們關閉了skipACL,彷佛也沒有問題,可爲何剛好次日就奔潰了?
由於leader去更新token的默認週期剛好是一天,次日它想更新的時候由於沒有/hbase/tokenauth/keys的權限而掛掉。
由於咱們加了那兩行remove配置,即便這個leader是4048,它也沒法訪問,道理同問題[1]。

這個證據也很好找。
這是第一次解決時,新寫入的token,它的建立時間是24號下午2點半。

[zk: localhost:2181(CONNECTED) 3] stat /hbase/tokenauth/keys/3 
cZxid = 0x1900000097
ctime = Mon Aug 24 14:30:48 CST 2015
mZxid = 0x1c000000e8
mtime = Tue Aug 25 15:35:36 CST 2015
pZxid = 0x1900000097
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 42
numChildren = 0

這是leader掛掉的日誌,時間是次日下午2點半,集羣奔潰也是在2點半左右,恰好間隔24小時左右。

2015-08-25 14:33:01,515 FATAL org.apache.hadoop.hbase.security.token.ZKSecretWatcher: Unable to synchronize master key 4 to znode /hbase/tokenauth/keys/4
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keys/4
at org.apache.zookeeper.KeeperException.create(KeeperException.java:113)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.createNonSequential(RecoverableZooKeeper.java:421)
at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.create(RecoverableZooKeeper.java:403)
at org.apache.hadoop.hbase.zookeeper.ZKUtil.createWithParents(ZKUtil.java:1164)
at org.apache.hadoop.hbase.zookeeper.ZKUtil.createSetData(ZKUtil.java:868)
at org.apache.hadoop.hbase.security.token.ZKSecretWatcher.addKeyToZK(ZKSecretWatcher.java:180)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager.rollCurrentKey(AuthenticationTokenSecretManager.java:250)
at org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager$LeaderElector.run(AuthenticationTokenSecretManager.java:317)
2015-08-25 14:33:01,516 FATAL org.apache.hadoop.hbase.regionserver.HRegionServer: ABORTING region server SVR4048HW2285.hadoop.xxx.com
       ,60020,1440397852099: Unable to synchronize secret key 4 in zookeeper

日誌顯示它想寫入新的token 4失敗而終止,昨天寫入的是3。由於新的token的id比舊的大一,因此正好掛在想寫入4的時候。

AuthenticationTokenSecretManager:

synchronized void rollCurrentKey() {
   if (!leaderElector.isMaster()) {
     LOG.info("Skipping rollCurrentKey() because not running as master.");
     return;
   }
   long now = EnvironmentEdgeManager.currentTimeMillis();
   AuthenticationKey prev = currentKey;
   AuthenticationKey newKey = new AuthenticationKey(++idSeq,  //新token的id比上一次大一
       Long.MAX_VALUE, // don't allow to expire until it's replaced by a new key
       generateSecret());
   allKeys.put(newKey.getKeyId(), newKey);
   currentKey = newKey;
   zkWatcher.addKeyToZK(newKey); //試圖向zk寫入新token
   lastKeyUpdate = now;
   if (prev != null) {
     // make sure previous key is still stored
     prev.setExpiration(now + tokenMaxLifetime);
     allKeys.put(prev.getKeyId(), prev);
     zkWatcher.updateKeyInZK(prev);
   }
 }

七、第三次解決(day 2)

修復zk上全部權限有問題的節點(設置權限爲anyone),刪除過時的token(這些token由於沒有權限,沒被人刪除),關閉skipACL,重啓zk。
由於已經添加了remove配置,如今不一樣region server訪問zookeeper的principal都是同樣的,不會再出現權限問題。
後記
爲了保證不一樣region server訪問zookeeper的principal同樣,咱們必須在zoo.cfg裏添加remove配置,這種作法彷佛不是特別科學。
由於做爲hbase,你不能保證zookeeper裏會有remove配置。假如zookeeper是另外一個團隊維護,他們以爲添加了這樣的配置對其它app有影響呢?
事實上hbase做爲client,zookeeper做爲server,咱們彷佛能夠給hbase配置統一的client身份?
zk-jaas.conf 相似這樣:

Client {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=true
  keyTab="/path/to/zkcli.keytab"
  storeKey=true
  useTicketCache=false
  principal="zkcli@<YOUR-REALM>";
};

而不是這樣:

Client {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
useTicketCache=false
keyTab="/etc/hbase.keytab"
principal="hbase/svr4048hw2285.hadoop.xxx.com@DC.SH.XXX.COM";
};
相關文章
相關標籤/搜索