線(xiàn)上任務(wù)調(diào)度工具執(zhí)行 Hadoop Hive 相關(guān)的任務(wù)會(huì)偶發(fā)任務(wù)執(zhí)行失敗的問(wèn)題,日志報(bào)錯(cuò)為 Kerberos 認(rèn)證失敗蝙泼。查看調(diào)度工具的運(yùn)行邏輯:每次執(zhí)行任務(wù)前先 kinit 認(rèn)證探孝,再執(zhí)行任務(wù)辩涝,考慮到最近遷移任務(wù)量大霍狰,且多為單個(gè)租戶(hù)的任務(wù),懷疑是該租戶(hù)并發(fā) kinit 導(dǎo)致砚哗,經(jīng)一系列的測(cè)試與驗(yàn)證婚陪,筆者確認(rèn)了問(wèn)題的原因,使用一種更優(yōu)的方案解決了集群租戶(hù)并發(fā)認(rèn)證的問(wèn)題频祝。
1.問(wèn)題發(fā)現(xiàn)
大數(shù)據(jù)平臺(tái) Hadoop 的安全認(rèn)證是基于 Kerberos 實(shí)現(xiàn)的泌参。 Kerberos 是一個(gè)網(wǎng)絡(luò)身份驗(yàn)證協(xié)議,用戶(hù)只需輸入身份驗(yàn)證信息常空,驗(yàn)證通過(guò)獲取票據(jù)即可訪(fǎng)問(wèn)多個(gè)接入 Kerberos 的服務(wù)沽一。
Kerberos 認(rèn)證方式有兩種:
- 使用密碼認(rèn)證:使用用戶(hù)密碼通過(guò) kinit 認(rèn)證, 獲取到的 TGT 存在本地憑證緩存當(dāng)中, 供后續(xù)訪(fǎng)問(wèn)服務(wù)認(rèn)證使用漓糙。一般在交互式訪(fǎng)問(wèn)中使用铣缠。
- 使用 keytab 認(rèn)證:用戶(hù)通過(guò)導(dǎo)出的 keytab 可以免密碼進(jìn)行用戶(hù)認(rèn)證, 后續(xù)步驟一致昆禽。一般在應(yīng)用程序中配置使用蝗蛙。
目前大數(shù)據(jù)平臺(tái)租戶(hù)執(zhí)行任務(wù),主要采用第一種 kinit 的方式實(shí)現(xiàn)認(rèn)證醉鳖, 為了保證 kerberos 認(rèn)證不過(guò)期捡硅,調(diào)度系統(tǒng)在每次執(zhí)行任務(wù)時(shí)會(huì)先 kinit 認(rèn)證刷新憑證 echo $passwd | kinit
。
這種方式簡(jiǎn)單可用盗棵,但最近總是頻發(fā)認(rèn)證失敗的問(wèn)題壮韭。報(bào)錯(cuò)如下:
GSS initiate failed: No valid credentials provided Failed to find any kerberos tgt
2.問(wèn)題定位
每次執(zhí)行前都會(huì) kinit,怎么會(huì)還找不到 kerberos tgt纹因,為了定位于解決問(wèn)題喷屋,我們先來(lái)看下 kinit 的解釋
kinit 命令用于獲取和緩存 principal 的初始票證授予票證(憑證)。此票證用于 Kerberos 系統(tǒng)進(jìn)行驗(yàn)證瞭恰。只有擁有 Kerberos 主體的用戶(hù)才可以使用 Kerberos 系統(tǒng)屯曹。有關(guān) Kerberos 主體的信息,請(qǐng)參見(jiàn) kerberos(5)。
當(dāng)使用 kinit 而未指定選項(xiàng)時(shí)恶耽,實(shí)用程序?qū)⑻崾灸斎?principal 和 Kerberos 口令僵井,并嘗試使用本地 Kerberos 服務(wù)器驗(yàn)證您的登錄。如果需要驳棱,可以在命令行上指定 principal。
如果登錄嘗試通過(guò)了 Kerberos 的驗(yàn)證农曲,kinit 將檢索您的初始票證授予票證并將其放到票證高速緩存中社搅。缺省情況下,票證存儲(chǔ)在 /tmp/krb5cc_uid 文件中乳规,其中 uid 表示用戶(hù)標(biāo)識(shí)號(hào)形葬。票證將在指定的生命周期后過(guò)期,之后必須再次運(yùn)行 kinit暮的。高速緩存中的任何現(xiàn)有內(nèi)容都將被 kinit 銷(xiāo)毀笙以。[1]
考慮到最近單個(gè)租戶(hù)的并發(fā)任務(wù)量大(大量任務(wù)在調(diào)度系統(tǒng)配置同一時(shí)間,定時(shí)調(diào)度)冻辩,懷疑是并發(fā) kinit 導(dǎo)致 - kinit 操作會(huì)重新覆蓋寫(xiě) krb 文件 - 即并發(fā)場(chǎng)景下有 krb 文件不可讀的情況猖腕。
為了驗(yàn)證猜想,做了如下測(cè)試:
- 刪除用戶(hù)的憑證文件: /tmp/krb5cc_uid恨闪,執(zhí)行 hdfs 命令倘感。報(bào)同樣的認(rèn)證錯(cuò)誤;
- 編寫(xiě)測(cè)試腳本咙咽,并發(fā) kinit 后執(zhí)行 hdfs 任務(wù)老玛,此認(rèn)證報(bào)錯(cuò)問(wèn)題必現(xiàn)。
3.問(wèn)題解決
如何保證租戶(hù)并發(fā)場(chǎng)景下钧敞,認(rèn)證成功且不過(guò)期蜡豹,想到兩種解決思路:
- 認(rèn)證失敗后重試,重新 kinit溉苛,必須封裝平臺(tái)級(jí) API 才行(并發(fā)依然可能出錯(cuò))
- 不用每次執(zhí)行任務(wù)都 kinit镜廉,改為定期按需進(jìn)行 kinit 認(rèn)證更新憑證 , 且保證并發(fā)的場(chǎng)景不出錯(cuò)
根據(jù)當(dāng)前場(chǎng)景,采用第二種方案愚战,簡(jiǎn)單快速桨吊,可實(shí)現(xiàn),這種方案的優(yōu)點(diǎn)如下:
- 按需 kinit凤巨,過(guò)濾絕對(duì)多數(shù)的重復(fù)的 kinit视乐, Kerberos KDC 認(rèn)證服務(wù)器的壓力可以降低 90% 以上。
- 最小化代碼入侵
解決過(guò)程如下:
首先通過(guò) klist
查看 認(rèn)證相關(guān)信息敢茁,如 ticket 的文件路徑和名稱(chēng)佑淀,失效時(shí)間等
[chenyao_yy@CD/WJ2FD-L350-ZYC1Q-colletion-DELLR730-SV013 ~]$ klist
Ticket cache: FILE:/tmp/krb5cc_30022_3zIUZUTNbD
Default principal: chenyao_yy@KDC
Valid starting Expires Service principal
08/14/18 17:02:12 08/16/18 17:02:12 krbtgt/KDC@KDC
renew until 08/21/18 17:02:12
每次執(zhí)行任務(wù)前,klist 查看票據(jù)失效時(shí)間彰檬,當(dāng)快要失效時(shí)(目前定義 2 hours)伸刃,才進(jìn)行 kinit谎砾,至此解決了按需 kinit 的問(wèn)題;
并發(fā)的問(wèn)題借鑒了鎖的概念捧颅,通過(guò)鎖互斥執(zhí)行 kinit景图。保證一次只有一個(gè)進(jìn)程能 kinit 成功。 經(jīng)查詢(xún) linux 有文件鎖的概念碉哑,經(jīng)測(cè)試可用 - flock
[2]
3.1 Linux flock 文件鎖
簡(jiǎn)單說(shuō)下使用:
flock -xn /tmp/${user}_kinit_lock -c "echo $passwd | kinit"
- 參數(shù)說(shuō)明:
-x
:獨(dú)占鎖 挚币;-n
:未獲取到鎖立馬退出,避免阻塞 - 獲取文件 /tmp/${user}_kinit_lock 獨(dú)占鎖扣典,獲取到才能執(zhí)行 kinit妆毕,執(zhí)行結(jié)束釋放鎖(租戶(hù)的文件鎖通過(guò)用戶(hù)名來(lái)區(qū)分)。
- 未獲取到文件鎖直接退出(跳過(guò) -c 后的命令)
這里需要特殊說(shuō)明是:未獲取到鎖或者 -c 命令執(zhí)行失敗都會(huì)返回 1($?=1)
3.2 kinit 腳本實(shí)現(xiàn)
具體實(shí)現(xiàn)腳本如下:
#!/bin/bash
# kinit helper - 解決單用戶(hù)并發(fā) kinit 報(bào)錯(cuò)問(wèn)題
# 只有當(dāng) expiredTime 剩余時(shí)間小于 2 小時(shí)才進(jìn)行 kinit 操作贮尖,且只允許一次 kinit
source /etc/profile
# 輸入?yún)?shù) $1=租戶(hù) $2=租戶(hù)密碼
proxyUser=$1
passwd=$2
# 是否需要 kinit (默認(rèn)需要=0)
needKinit=0
# 過(guò)期剩余時(shí)間閾值 2 小時(shí)
let maxLeftTime=2*60*60
# 檢查是否需要重新 kinit笛粘,存在如下兩種情況:
# 1.krb 文件不存在
# 2.klist 過(guò)期時(shí)間,還剩不到 2 小時(shí)
check_if_need_kinit(){
klist > /dev/null
if [ $? -eq 0 ]; then
# check principal is match
principal=$(klist | awk '{print $3}' | sed -n '2p' | cut -d@ -f1)
echo "default principal: $principal"
if [ "$principal" != "${proxyUser}" ]; then
echo "expect principal: $proxyUser, will re-kinit"
return
fi
# check klist has renew info
klist | grep renew
if [ $? -eq 1 ]; then
klist
echo "klist info exception !"
return
fi
expiredTime=$(klist | sed -n "5,1p" | awk '{print $2,$3}')
expiredTimeFT=$(date -d "${expiredTime}" +"%Y-%m-%d %H:%M:%S")
expiredTimestamp=$(date -d "$expiredTimeFT" +%s)
currentTimestamp=$(date +%s)
if [ $expiredTimestamp -gt $currentTimestamp ]; then
let free=$expiredTimestamp-$currentTimestamp
# 剩余時(shí)間大于所設(shè)閾值湿硝,則不需要 kinit
if [ $free -gt $maxLeftTime ]; then
nowTime=$(date "+%Y-%m-%d %H:%M:%S")
echo "NowTime: $nowTime, ExpiredTime: $expiredTimeFT, no need to kinit!"
needKinit=1
fi
fi
else
echo "krb file does not exist!"
fi
}
# 傳入?yún)?shù)檢查
if [ $# != 2 -o "$proxyUser" = "" -o "$passwd" = "" ]; then
echo "some curtionl params is null ! please check your prams"
exit 1
fi
check_if_need_kinit
if [ $needKinit -eq 0 ]; then
user=$(whoami)
# 搶占獨(dú)占鎖薪前,進(jìn)行 kinit 操作
echo "try to kinit, command: [ echo password | kinit $proxyUser ]"
flock -xn /tmp/${user}_kinit_lock -c "echo '$passwd' | kinit $proxyUser"
if [ $? -eq 0 ]; then
echo "kinit success!"
else
echo "kinit faild: may be kinit by other concurrent process or password is incorret!"
fi
fi
實(shí)際腳本中 考慮了認(rèn)證用戶(hù)錯(cuò)位 - principal 不匹配,認(rèn)證 klist 信息不全認(rèn)證失敗等場(chǎng)景关斜,較大程度提高了認(rèn)證的可用性序六。
參考:
[1] https://docs.oracle.com/cd/E56344_01/html/E54075/kinit-1.html
[2] http://www.jusene.me/2017/02/22/flock/