kerberos下HBase訪問Zookeeper的ACL問題

來源:互聯網
上載者:User

標籤:hbase   mapreduce   zookeeper   acl   kerberos   

最近公司HBase(CDH-4.6.0)遇到了一個麻煩問題,覺得有必要記錄下整個解決的過程。

問題起因

使用者在跑mapreduce任務,從hdfs讀取檔案想寫入到hbase table的時候失敗了(這是hbase提供的一種mapred能力)。這個問題發現在A環境(一個測試環境),自從啟用了kerberos之後。運行了使用者給的程式和自己寫的sample之後,發現程式最後掛在NullPointerException上。這個NPE指示的是服務端的一個叫currentKey的變數為null。

[email protected],java.io.IOException: java.io.IOException: java.lang.NullPointerExceptionat  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)at org.apache.hadoop.hbase.ipc.HBaseServer$Handler.run(HBaseServer.java:1428)
AuthenticationTokenSecretManager:
  @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:
    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:
  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上找到了如下異常:

org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager: Zookeeper initialization failedorg.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keysat 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裡查看了下它的許可權是這樣的:

[zk: localhost:2181(CONNECTED) 0] getAcl /hbase/tokenauth‘sasl,‘hbase/[email protected]: cdrwa

但很奇怪的是不管我切換什麼賬戶也無法訪問這個節點,想通過setAcl設定它的許可權為anyone也是失敗的。原因很顯然,因為我不是“hbase/[email protected]”,我沒任何許可權操作。

Authentication is not valid : /hbase/tokenauth

可為什麼4048這台機子也沒能成為leader呢(問題[1])?

第一次解決(day 0)

嘗試各種辦法也無法獲得/hbase/tokenauth的控制權,我們只好暫時通過在zookeeper設定檔zoo.cfg添加參數skipACL=true,重啟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 zookeeperorg.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keys/3at 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/[email protected]: cdrwa

前者非常統一的使用hbase這個principal,後者則帶上了hostname。
問題必定出在這裡!

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

Client {com.sun.security.auth.module.Krb5LoginModule requireduseKeyTab=trueuseTicketCache=falsekeyTab="/etc/hbase.keytab"principal="hbase/[email protected]";};

可為什麼B最後的principal卻沒帶hostname,我又對比了zookeeper的設定檔zoo.cfg。
B的有下面兩行設定:

kerberos.removeHostFromPrincipal=truekerberos.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/[email protected]: 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 = 0x1900000097ctime = Mon Aug 24 14:30:48 CST 2015mZxid = 0x1c000000e8mtime = Tue Aug 25 15:35:36 CST 2015pZxid = 0x1900000097cversion = 0dataVersion = 1aclVersion = 0ephemeralOwner = 0x0dataLength = 42numChildren = 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/4org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hbase/tokenauth/keys/4at 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="[email protected]<YOUR-REALM>";};

而不是這樣:

Client {com.sun.security.auth.module.Krb5LoginModule requireduseKeyTab=trueuseTicketCache=falsekeyTab="/etc/hbase.keytab"principal="hbase/[email protected]";};

這樣就不會帶上hostname了吧?

附錄

Zookeeper Authentication

HBase as a MapReduce Job Data Source and Data Sink

著作權聲明:本文為我原創文章,轉載請註明出處

kerberos下HBase訪問Zookeeper的ACL問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.