背景
hadoop delegation token的问题相对比较混乱和复杂,简单说下这东西的出现背景,最早的hadoop的因没有的完善的安全机制(安全机制主要包括:认证 + 鉴权,hadoop这里主要是身份认证机制没有),所以导致操作风险比较大,你可以理解只要获取了一台装有hadoop client的机器,就可以任意操作HDFS系统了,深究原因是因为hadoop身份认证机制太薄弱,所以只要黑了一台机器就可以发起各种危险操作了,所以各方大佬们云集讨论了一个可靠的安全方案,最终在SSL协议和Kerberos协议中,选择了Kerberos,主要有两个原因:
1,性能。因为SSL数据传输中包含非对称加密算法,相对耗时比较大,而Kerberos则完全采用对称加密
2,易于维护管理。Kerberos里面如果撤销一个客户端的访问权,直接在KDC(key distribution center)秘钥分发中心删除掉这个用户即可,而SSL里面,则需要生成一个证书吊销列表然后分发到所有的服务器中,对比而言Kerberos的集中式管理更方便维护
Kerberos介绍
Kerberos一些术语:
- KDC:密钥分发巾心,负责管理发放票据,记录授权。
- Realm: Kerberos管理领域的标识。
- principal:Kerberos 下的用户可以称为 Principal,当每添加一个用户或服务的时候都需要向kdc添加一条principal, principal的形式为:主名称/实例名@领域名。
- 主名称:主名称可以是用户名或服务名,表示是用于提供各种网络服务(如hdfs、yam,、hive) 的主体。
- 实例名:实例名简单理解为主机名。
- keytab文件:存储了用户的加密密码。常用这种方式认证。
使用Kerberos协议后,一次客户端访问服务端的认证流程如下:
Kerberos的架构和工作原理
- AS_REQ 是在初始化一个用户(kinit)的时候发出的用户认证请求,这个请求是发给KDC中的Authentication Server (AS);
- AS_REP 是 AS回复给client的信息,其中包括TGT (用TGS secret key加密过的) and the session key (用发请求的用户的secret key加密过的);
- TGS_REQ 是client为了一个service ticket向Ticket Granting Server (TGS)的信息. 其中包括上一条信息中获得的TGT (用TGS secret key加密过的) ,一个客户端产生的一个authenticator(用session key加密过的).
- TGS_REP 是TGS回复给TGS_REQ的信息. 其中包括service ticket(用appservice的secret key加密过),和 一个TGS产生的service sessinon key(之前AS产生的一条session key给加密过的)
- AP_REQ 是一条客户端发给appserver的访问服务的请求,其中包括service ticket和一个authenticator(使用TGS产生的service session key 加密过的)
- AP_REP 是appserver回复给client的一条信息,证明这个appserver确实是客户端希望访问的server。不过这条信息也不一定总是要回复的。比如当客户端和appserver需要相互认证的时候,客户端向appserver发出请求,这个时候就不需要回复信息。
Delegation Token介绍
Delegation Token(委托令牌,缩写DT),前面我们知道了Kerberos的工作原理,那么Hadoop的 Delegation Token又是怎么来的?
虽然理论上可以单独使用 Kerberos 进行身份验证,但在 Hadoop 等分布式系统中使用时,它有一些弊端。想象一下,对于每个 MapReduce 作业,如果所有YARN的NodeManager节点或者Spark的Executor节点,都必须使用委托的 TGT(Ticket Granting Ticket)通过 Kerberos 进行身份验证,那么 Kerberos 密钥分发中心 (KDC) 将很快成为瓶颈。一次大的Job作业可能有数千个节点到节点的通信,这样就会导致放大对KDC中心的请求服务。事实上,它会无意中在非常大的集群中对 KDC 执行分布式拒绝服务攻击。
因此,Hadoop Delegation Token委托令牌作为一种轻量级身份验证方法被引入,以补充 Kerberos 身份验证。 Kerberos 是一个三方协议;相反,Delegation Token 身份验证是一种两方身份验证协议。
DT的工作原理如下:
1,客户端最初使用kinit或者keytab文件,完成与 Kerberos 的身份验证,并拿到TGT,然后通过TGT到TGS获取到HDFS服务的票证之后,再拿着Ticket从Name Node服务中获取委托令牌
2,客户端使用DT委派令牌与YARN进行通信和提交任务进行后续身份验证,整个过程不再使用 Kerberos
更详细的图示如下:
YARN Long Running应用的问题
看起来一切很美好,但这里面有个最大的问题是KDC颁发的票据是有最大生命周期的默认是7天,然后在7天内,每隔24颁发的票据就会失效,如果票据过期了,可以通过程序renew token来续约,这个时间一般是在过期前就会完成,准确来说是24*0.75=18个小时。
那么当票据的最大时间到了之后,会发生什么情况呢?很简单服务直接挂掉,因为token过期失效了,也就是我们经常遇到的两种异常情况:
1,Token is expired
2,Token can’t be found in cache
其实都是一个意思,只不过根据失效的时间长短抛出来的有所不同
默认的7天,对于普通离线Job基本绰绰有余了,但对于实时应用,如flink,spark流等的程序,可能就会失败,所以这里的问题又改怎么解决呢。
Hadoop YARN的官方文档给出了四种主流策略:
一,使用本地文件系统存储keytab文件
为应用程序在每个节点上的使用提供了一个keytab文件,将其安装在每个集群节点的本地文件系统中。
在配置选项中提供此路径,一般是环境变量注入。应用程序通过 UserGroupInformation.loginUserFromKeytab() 加载凭据。密钥表必须位于安全目录路径中,只有服务(和其他受信任的帐户)可以读取它。这实际上是所有静态 Hadoop 应用程序获取其安全凭证的方式
二,使用HDFS文件系统存储keytab文件
将keytab文件密钥表被上传到 HDFS。启动 AM 时,keytab 被列为本地化到 AM 容器的资源。
Application Master 配置了 keytab 的相对路径,并使用 UserGroupInformation.loginUserFromKeytab() 登录。
当 AM 启动容器时,它会将指向 keytab 的 HDFS 路径列为要本地化的资源。它将 HDFS 委托令牌添加到容器启动上下文中,以便可以本地化 keytab 和其他应用程序文件。
启动的容器必须自己通过 UserGroupInformation.loginUserFromKeytab() 登录。 UGI 处理登录,并安排一个后台线程定期重新登录用户。
令牌创建在 Hadoop IPC 和 REST API 中自动处理,容器在整个持续时间内通过 kerberos 保持登录状态。
这避免了为整个集群中的特定服务安装 keytab 的管理任务。它确实要求客户端有权访问密钥表,并且当它上传到分布式文件系统时,必须通过适当的路径权限/ACL 来保护它。
由于所有容器都可以访问密钥表,因此必须信任在容器中执行的所有代码。恶意代码(或逃避某种形式的沙箱的代码)可以读取密钥表,因此可以访问集群,直到密钥过期或被撤销。
三,使用HDFS存储keytab,通过AM生成token和续约token,并通过RPC分发给所有的Executor节点
1,client把keytab上传到HDFS
2,当启动AM的时候,会把这个keytab文件下载到AM运行的container里面
3,Application Master 配置了 keytab 的相对路径,并使用 UserGroupInformation.loginUserFromKeytab() 登录。 UGI 代码路径仍将通过 $HADOOP_TOKEN_FILE_LOCATION 自动加载文件引用,这是获取 AMRM 令牌的方式
4,当 AM 启动一个容器时,它会获取该容器所需的所有委托令牌,并将它们添加到容器的容器启动上下文中,典型的就是AM提交YARN任务时,会把token添加到上下文中,发送给RM,当RM启动NM时,又会把token下发到各个NM中
5,启动的容器必须从 $HADOOP_TOKEN_FILE_LOCATION 加载委托令牌,并使用它们(包括续订),直到它们无法再续订
6,AM和Executor 必须实现一个 IPC 接口,该接口允许接受请求一组新的委托令牌;
7,在委托令牌到期之前,client也就是driver进程会重新new新的token,然后通过 IPC 通道发送给AM和Execuor,从而确保他们能够长时间运行
这就是 Apache Spark 1.5+ 以后使用的策略,在容器和 AM 之间使用基于自定义的RPC网络的协议(最早是akka,目前已经重写)进行令牌更新
四,使用本地文件系统存储keytab,在机器上定时kinit认证
这种方式比较简单,与应用程序解耦,需要一个crontab定时任务,在token续约到期之前,自动执行kinit即可,唯一需要注意的就是,如果集群扩容什么的,新加的机器要确保也执行了,否则任务运行在这上面就会失败
上面几种策略,我们可以根据实际情况选择合适的一种来操作,从而避免让我们的实时应用只能跑7天
hadoop配置中和token相关的配置参数:
dfs.namenode.delegation.token.max-lifetime = 604800000 ms (7天) dfs.namenode.delegation.key.update-interval = 86400000 ms (1天) dfs.namenode.delegation.token.renew-interval = 86400000 ms (1天)
在测试验证时,可以修改短点来验证,比如10,,3,3
Spark的Token过期问题
从前面的介绍中,我们现在应该大致了解了整个委托token的相关知识,但在实际使用过程中,仍然会遇到各种问题,因为像大数据系统涉及组件多,出一个问题说实话不太容易定位或者容易把排查方向搞偏,这里总结一下排查这种问题的经验,我总结为4个层面:
1,kerberbos层面
这个层面一般不容易遇到问题,但是如果要支持多个开启了Kerberos认证的hadoop集群之间互相访问数据,大概率会遇到访问失败问题,因为这种情况是需要打通两边kerberos的,当然好消息是kerberos是支持的,只需要我们配置正确即可
2,hadoop层面
Hadoop层面是最容易出现问题的,因为现在hadoop有很多个版本,建议新集群大家直接用hadoop 2.8+以上的版本,基本不会在遇到token问题,但在这个版本之前比如2.7和2.6都有各种各样的bug存在,需要我们打patch才能解决,详情可参考HDFS 9276的ISSUE
3,spark层面
spark层面其实还好,不容易出现这种问题,因为Spark使用的人多,社区也比较活跃,所以spark在1.5版本的时候都设计了可以解决实时应用长时间跑在yarn上的问题。当然也不要心存侥幸,有两个地方也会出问题:
spark编译的时候依赖了低于hadoop 2.8的版本,那么长时间运行的应用大概率也会遇到token过期的问题,所以编译时候要注意,另外就算依赖高于hadoop 2.8的版本,spark自身的代码也会存在bug从而引发token问题,我们就遇到了:
第一个是renew schdule的时长是一个long的最大值,这样会导致driver永远不会更新token,解决方法:把hadoop-hdfs-2.8.5的jar拷贝到spark的jars目录就行了,因为缺失一个SPI的描述文件导致的。
第二个是spark3.0.1版本独有的bug,driver生成的token无法传给AM,从而导致在token超过7天之后,AM新生成的Executor都会失败。解决方法:参考SPARK-32905
这里推荐两个稳定版本:spark 2.4.3 和 spark 3.0.2,有条件直接上最新的更好。
4,应用层面
这个主要指的是,如果你的应用是通过shade的fat jar提交运行的,那么很有可能你的shade jar里面包含了一些hadoop或者spark版本不一致的代码,从而可能会引发类冲突或者加载顺序的问题,导致命中某个bug,所以这里建议大家服务端提供了依赖,fat jar就不要再打进去了,减少这种问题发生的几率。
总结
本篇介绍的内容,都是我们在生产环境遇到的一些问题,当然我们现在主要用的是spark,所以介绍的spark的内容会多一点,如果你正在使用的是flink,也不要紧,解决问题的思路都是是相通的,也可以按照文中的思路在遇到token过期问题时来进行排查,大数据组件因为复杂和繁多,当组合到一起时经常会遇到一些问题,这个时候我们也不要着急,多用谷歌搜索+善用组件的官网ISSUE文档,如果实在还没迹可循,那只能看源码细细捋了,可以造一个测试环境,方便排查,这样可以验证我们的各种想法,也会大大提升我们排查问题的效率。
参考文章:
Kerberos认证原理与环境部署 - 大数据老司机 - 博客园
Hadoop Delegation Tokens Explained - Cloudera Blog
https://issues.apache.org/jira/secure/attachment/12428537/security-design.pdf
https://www.slideshare.net/oom65/hadoop-security-architecture
[HADOOP-4487] Security features for Hadoop - ASF JIRA
[SPARK-32905] ApplicationMaster fails to receive UpdateDelegationTokens message - ASF JIRA