問題:
最近CDH集羣增加了kerberos認證,發現了JavaWeb應用啓動後,超過24小時後,kerberos憑證過期導致查詢Hbase失敗的問題。
Spark程序連接CDH時,通過principal和keytab配置方式,內部會將憑證到hdfs上,供Executor和Driver使用。當憑證Ticket快要失效時,會通過Keytab重新生成憑證。
spark2-submit \
--master yarn \
--deploy-mode cluster \
--num-executors 3 \
--driver-memory 4g \
--executor-memory 4g \
--executor-cores 2 \
--conf spark.default.parallelism=10 \
--conf spark.shuffle.file.buffe=1024k \
--conf spark.executor.memoryOverhead=4096 \
--class com.hypers.streaming.PanoramaMain \
--conf "spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails" \
--principal xxxx \
--keytab xxxx.keytab \
xxxx.jar
但我們自己開發的JavaWeb服務沒有該機制,導致Ticket在程序運行時過期失效,最終查詢Hbase時提示沒有有效憑證,查詢失敗。
源碼調試:
本地調試源碼驗證後,發現確實是因爲Ticket的到期時間每次啓動時候都會指定成一個固定的時間,後續也沒有進行更新,最終過期導致應用查詢Hbase失敗。
重新編譯了源碼,增加了Ticket憑證相關的日誌,最終確認了問題就是Ticket沒有更新到期有效時間,導致認證過期。打印的日誌如下:
checkTGTAndReloginFromKeytab! shouldRenewImmediatelyForTests:false, Time.now():1606492834061, getRefreshTime(tgt):1606526141000, tgt:Ticket (hex) =
0000: 61 82 01 40 30 82 01 3C A0 03 02 01 05 A1 0D 1B a..@0..<........
0010: 0B 45 53 53 45 4E 43 45 2E 43 4F 4D A2 20 30 1E .ESSENCE.COM. 0.
0020: A0 03 02 01 02 A1 17 30 15 1B 06 6B 72 62 74 67 .......0...krbtg
0030: 74 1B 0B 45 53 53 45 4E 43 45 2E 43 4F 4D A3 82 t..ESSENCE.COM..
0040: 01 02 30 81 FF A0 03 02 01 12 A1 03 02 01 01 A2 ..0.............
0050: 81 F2 04 81 EF B4 0C DB 10 F7 5F 24 41 A8 91 70 .........._$A..p
0060: A0 76 13 DE 32 C2 29 82 D3 2D D0 50 34 E3 A9 B1 .v..2.)..-.P4...
0070: 4B B0 CF 55 CF 2E 83 8C CB AE 05 2E 77 C5 14 6B K..U........w..k
0080: 56 B0 63 DD 32 A7 C9 BA DE 51 9B FF 06 C5 01 6F V.c.2....Q.....o
0090: 16 01 AD E2 BB C4 6E C5 B3 BC 35 FE 0D AF 20 49 ......n...5... I
00A0: CF F7 4C 90 C7 7F A0 F5 A1 19 A2 FF 3D FF AB 67 ..L.........=..g
00B0: EF 91 FA C4 ED 59 C1 53 38 7A BD B2 FF FD FC 84 .....Y.S8z......
00C0: E5 16 DE 9F 08 62 49 65 57 86 57 6D 03 A2 96 83 .....bIeW.Wm....
00D0: F9 80 1E E6 F7 E4 4B 4F 9C 00 55 B9 4A A2 DA E2 ......KO..U.J...
00E0: F2 01 86 11 1C B5 B1 01 9A F6 29 75 C2 D1 80 8A ..........)u....
00F0: 90 7F 35 C2 D0 A2 65 C3 9A 8B 9C 00 5E 20 EB 6C ..5...e.....^ .l
0100: CF 1A 04 FC 20 8C 7B 4B 98 0A 0F 08 36 EC 94 7E .... ..K....6...
0110: AF 71 4D A1 E1 DA 95 4A 50 5A A8 1B 39 4A F0 B6 .qM....JPZ..9J..
0120: 99 60 71 C7 E4 05 1A 54 0C 05 50 C5 42 B0 97 08 .`q....T..P.B...
0130: 29 5A 48 7E 8F A7 C9 BD A6 9B 19 E4 A7 2A ED 48 )ZH..........*.H
0140: 8F 5F 1A D2 ._..
Client Principal = @xx.COM
Server Principal = xxxx/[email protected]
Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)=
0000: A2 ED 15 B4 25 87 D1 B8 70 23 96 41 48 4B B6 79 ....%...p#.AHK.y
0010: 96 60 1A 1D EF D7 50 C0 31 C6 F5 63 8A 65 56 9E .`....P.1..c.eV.
Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Fri Nov 27 14:03:41 CST 2020
Start Time = Fri Nov 27 14:03:41 CST 2020
End Time = Sat Nov 28 14:03:41 CST 2020
Renew Till = null
Client Addresses Null
主要是End Time、Renew Till字段,源碼裏的解釋:
/**
* Returns the start time for this ticket's validity period.
*
* @return the start time for this ticket's validity period
* or null if not set.
*/
public final java.util.Date getStartTime() {
return (startTime == null) ? null : (Date)startTime.clone();
}
/**
* Returns the expiration time for this ticket's validity period.
*
* @return the expiration time for this ticket's validity period.
*/
public final java.util.Date getEndTime() {
return (endTime == null) ? null : (Date) endTime.clone();
}
/**
* Returns the latest expiration time for this ticket, including all
* renewals. This will return a null value for non-renewable tickets.
*
* @return the latest expiration time for this ticket.
*/
public final java.util.Date getRenewTill() {
return (renewTill == null) ? null: (Date)renewTill.clone();
}
解決思路:
最後找到的解決方案是需要我們自己去定期刷新(重新登錄)憑證來使憑證永久生效,或者指定一個較長的 ticket_lifetime 憑證有效期,來讓應用盡可能推遲到期時間。
參考:https://stackoverflow.com/questions/33211134/hbase-kerberos-connection-renewal-strategy/33243360
最終解決方案:
在連接Hbase,觸發了首次登錄Kerberos的時候,啓動一個定時任務,每次定時調用checkTGTAndReloginFromKeytab()方法來更新憑證,來達到定時更新的效果。
conf.addResource("hbase-site-test.xml");
conf.addResource("core-site-test.xml");
conf.addResource("hdfs-site-test.xml");
try {
System.setProperty("java.security.krb5.conf", Constants.KRB5_CONF_PATH);
UserGroupInformation.setConfiguration(conf);
UserGroupInformation.loginUserFromKeytab(Constants.KEYTAB_USER, Constants.KEYTAB_PATH);
startCheckKeytabTgtAndReloginJob(); // 定時更新憑證任務
} catch (IOException e) {
logger.error("Login failure", e);
}
/**
* * 定時更新憑證
*/
private static void startCheckKeytabTgtAndReloginJob() {
//10分鐘循環 達到距離到期時間一定範圍就會更新憑證
ThreadPool.updateConfigThread.scheduleWithFixedDelay(() -> {
try {
UserGroupInformation.getLoginUser().checkTGTAndReloginFromKeytab();
logger.warn("get tgt:{}", UserGroupInformation.getLoginUser().getTGT());
logger.warn("Check Kerberos Tgt And Relogin From Keytab Finish.");
} catch (IOException e) {
logger.error("Check Kerberos Tgt And Relogin From Keytab Error", e);
}
}, 0, 10, TimeUnit.MINUTES);
logger.warn("Start Check Keytab TGT And Relogin Job Success.");
}
注:也可通過reloginFromTicketCache()方法進行更新,但依賴於服務器上的認證緩存。或者直接調用kinit -R來更新(參考Hadoop源碼中的續期方式)。
// 參考Hadoop源碼中的續期方式
try {
String cmd = "kinit";
Shell.execCommand(cmd, "-R");
log.warn("exec kinit -R!");
} catch (IOException e) {
log.warn("relogin error.",e);
}
try {
UserGroupInformation.getLoginUser().reloginFromTicketCache();
} catch (IOException e) {
log.warn("relogin error.",e);
}
觀察結論:
Ticket的有效期已經更新,目前運行了幾天,一起正常。後續繼續觀察。