目录

弹性计算平台-Spark访问Kerberized-HDFS

概述

本文是笔者在前司建设的基于Kubernetes构建的弹性计算平台集成Spark的相关使用文档,我们是国内最早基于Kubernetes运行Spark计算任务的团队之一

Spark 计算目前读写都是在 non-Kerberized 集群上的,因为 HDFS 加固的需求,所以需要读写 Kerberized HDFS。之前通过弹性计算平台提交的 Spark 计算带上了 Keytab 相关几个 conf,还是会出现权限验证不通过的情况。

1
2
3
spark.kubernetes.kerberos.keytab=/var/spark-data/spark-files/test.hdfs.keytab
spark.kubernetes.kerberos.principal=test@IEGBACKUP.COM
spark.kubernetes.kerberos.enabled=true

之前出现错误的信息如下:

/%E5%BC%B9%E6%80%A7%E8%AE%A1%E7%AE%97%E5%B9%B3%E5%8F%B0-spark%E8%AE%BF%E9%97%AEkerberized-hdfs/img.png /%E5%BC%B9%E6%80%A7%E8%AE%A1%E7%AE%97%E5%B9%B3%E5%8F%B0-spark%E8%AE%BF%E9%97%AEkerberized-hdfs/img_1.png

错误的原因是因为 kinit 的问题,以及 Keytab 和 Principal 的理解。

以下是一个正常部署 KDC 的一个流程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 初始化 KDC 的数据库文件
/usr/sbin/kdb5_util -P changeme create -s

# addprinc 是 add principal 的意思
# -randkey 是随机密码
# ktadd -k 可以生成 keytab 文件
/usr/sbin/kadmin.local -q "addprinc  -randkey ifilonenko"
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/ifilonenko.keytab ifilonenko"

/usr/sbin/kadmin.local -q "addprinc -randkey HTTP/server.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/server.keytab HTTP/server.default.svc.cluster.local"

/usr/sbin/kadmin.local -q "addprinc -randkey hdfs/nn.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "addprinc -randkey HTTP/nn.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "addprinc -randkey hdfs/dn1.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "addprinc -randkey HTTP/dn1.default.svc.cluster.local"

# 一个 keytab 里可以包含多个 principal
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/hdfs.keytab hdfs/nn.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/hdfs.keytab HTTP/nn.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/hdfs.keytab hdfs/dn1.default.svc.cluster.local"
/usr/sbin/kadmin.local -q "ktadd -k /var/keytabs/hdfs.keytab HTTP/dn1.default.svc.cluster.local"

Proposal

Spark Client

HDFS 采用 Kerberos 进行安全验证了,那么 Spark 用户访问的方式就只有两种选择。

  1. Principal和密码,本地kinit之后TGT会被缓存起来,Spark Driver会去读取,并且透传到Executor
  2. 每次spark-submit都通过conf指定Keytab相关选项

关于 Spark 如何去读取缓存的 TGT,Spark 的 HadoopKerberosKeytabResolverStep 有详细说明。

This step does all the heavy lifting for Delegation Token logic. This step assumes that the job user has either specified a principal and keytab or ran $kinit before running spark-submit. With a TGT stored locally, by running UGI.getCurrentUser you are able to obtain the current user, alternatively you can run UGI.loginUserFromKeytabAndReturnUGI and by running .doAs run as the logged into user instead of the current user. With the Job User principal you then retrieve the delegation token from the NameNode and store values in DelegationToken. Lastly, the class puts the data into a secret. All this is appended to the current HadoopSpec which in turn will append to the current DriverSpec.

简单说一句,这里就是读取缓存 TGT 的代码逻辑,依赖的是 Hadoop Common 里关于 Keytab 的 API。

/%E5%BC%B9%E6%80%A7%E8%AE%A1%E7%AE%97%E5%B9%B3%E5%8F%B0-spark%E8%AE%BF%E9%97%AEkerberized-hdfs/img_2.png

Ticket 更新的问题。Kerberos 里,有两个概念特别容易让人糊涂,

  1. ticket_lifetime
  2. renew_lifetime。

打个比方,前者是2d,后者是7d。

意思是 kinit 了之后,这个缓存的 ticket 可以用2d,7天内 kinit -R 都可以刷新这个 ticket,然后这个 ticket 又获得有效期2d了。而 renew_lifetime 表示 kinit -R 这个操作7d内进行,超过7d,就没法 renew 了。那7d之后怎么办呢?可以再次 kinit 一下,获取新的 ticket。

因为 kinit -kt 是需要 keytab 的,kinit -R 是不需要的,所以才会有这样的区别。要记得,keytab 是永远不会过期的,里面存的是 KDC 数据库里的 principal 和 key。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看 ticket 缓存
# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: hdfs/cdh1@IEGBACKUP.COM

Valid starting     Expires            Service principal
05/31/19 11:45:56  06/01/19 11:45:56  krbtgt/IEGBACKUP.COM@IEGBACKUP.COM
	renew until 06/07/19 11:45:56
	
# Refresh ticket
# kinit -R
kinit: Ticket expired while renewing credentials

# 重新 kinit,获得新的 ticket
# kinit -kt runzhliu-test/hadoop-2.7.3/etc/hadoop/hdfs.keytab hdfs/cdh1@IEGBACKUP.COM
# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: hdfs/cdh1@IEGBACKUP.COM

Valid starting     Expires            Service principal
06/11/19 09:14:37  06/12/19 09:14:37  krbtgt/IEGBACKUP.COM@IEGBACKUP.COM
	renew until 06/18/19 09:14:37

Security

一些关于安全的 concerns:

  1. 每个用户提交任务都需要特定的Keytab,增加了DBA的工作量,而且管理也比较麻烦
  2. 统一账号不利于HDFS的审计
  3. Keytab封装在镜像中,用户也是有可能拿到Keytab,从而造成安全风险

关于 Keytab 的处置,调研到的一些方案

  1. Embedded – have the container refresh the token itself
  2. Externalized – have an external service refresh the token in the container
  3. Sidecar – Refresh the token in a sidecar container, and share with the application container

讨论

在已有的镜像文件中,通过传递 Spark conf 是可以满足当前需求的(访问 Kerberized HDFS)。

/%E5%BC%B9%E6%80%A7%E8%AE%A1%E7%AE%97%E5%B9%B3%E5%8F%B0-spark%E8%AE%BF%E9%97%AEkerberized-hdfs/img_3.png
警告
本文最后更新于 2017年2月1日,文中内容可能已过时,请谨慎参考。