介绍
Apache Ranger 提供一个集中式安全管理框架来解决授权和审计问题,它可以对 Hadoop 生态的组件如 HDFS、Yarn、Hive、Hbase 等进行细粒度的数据访问控制,通过 Ranger 控制台,管理员可以轻松的配置策略来控制用户访问权限。
和 HDFS 配合使用时,Ranger 以插件 plugin 的方式集成到 HDFS 中,由 Ranger Admin 配置访问策略,Ranger plugin 定期轮询 Admin(默认轮询间隔30s),将策略更新到本地,并在本地根据策略信息进行权限判断。
注意只要 Ranger Admin 的策略有变化,plugin 就需要从 Amdin 处进行全量更新,plugin 拿到的所有策略都保存在一个 json 文件中,plugin 会同时将该文件保存在磁盘上(位置可配),一个典型的、包含6K个规则的 json 文件,大小大概在6M左右。
Ranger 集成 HDFS 的架构图如下所示,其中,管理界面 Ranger Web、插件 Ranger Plugin 和 RangerAdmin 之间都是基于 HTTP 的RESTful架构。
Ranger 对 HDFS 访问控制的实现原理
HDFS 本身是有默认的鉴权机制的(即 rwx + ACL),该权限检查的实现代码是 INodeAttributeProvider 抽象类中的接口 AccessControlEnforcer 的 checkPermission 方法,原生 HDFS 中,接口实现类是 FSPermissionChecker,基于类 Unix 的 User、Group、Others 分组和 rwx 权限来做检查。
启用 Ranger 后,会将 NameNode 的 Inode attribute 提供类强制指定为 Ranger 自己的类,该类实际上继承了 INodeAttributeProvider 抽象类,提供了一个 Ranger 自己的 AccessControlEnforce 实现类,需要在 hdfs-site.xml 文件中修改如下配置项:
<name>dfs.namenode.inode.attributes.provider.class</name>
<value>org.apache.ranger.authorization.hadoop.RangerHdfsAuthorizer</value>
需要注意的是,Apache Ranger为 HDFS 提供了一种联合授权模式。 Ranger Plugin 用于 HDFS 检查 Ranger 策略,如果策略存在,则授予用户访问权限。 如果在 Ranger 中不存在策略,则 Ranger 将默认为 HDFS(POSIX 或 HDFS ACL)中的本机权限模型,联合模式适用于 Ranger 中的 HDFS 和 Yarn 服务。
HDFS 引入 Ranger
概述
Ranger 引入之后,可能会存在两个问题:
- RangerHDFSPlugin 会在 NameNode 的 JVM 内存中增加比较多的 ranger 规则,这可能造成内存占用的膨胀。
- RangerHDFSPlugin 使用自己优化过的数据结构来管理所有规则,但在规则数量特别大时(如十万级别),可能会造成鉴权性能低下。
对这两个问题,都需要进行测试,暂时以 30W 条规则为基准进行测试。
内存测试
一个问题
一开始测试时,发现增加 5K 个 Ranger 规则后,NameNode 进程所占的物理内存变化非常大,如下:
项目 | 增加规则前(KB) | 增加规则后(KB) |
---|---|---|
RES(物理内存) | 516 764 | 1 146 768 |
可以看到,在增加了5K 条规则后,NameNode 进程占用的内存增加了 615M,这个数据明显超出预期,进一步使用 jmap 生成增加规则前后,NameNode 进程的 java 堆 dump 文件,并使用 Eclipse Memory Analyzer(MAT)分析,发现在增加规则后,java 堆中的大部分空间都被 java.lang.ref.Finalizer 对象占据。
这里面根本的原因是,HDFS Ranger Plugin 中的 RangerPolicyEngineImpl 类覆写了 java.lang.Object 的 finalize()方法,导致这个类的对象其 GC 过程和其它正常对象不同,比较慢,在我们测试过程中,由于 NameNode 更新规则特别频繁,而且每次都是全量更新,即:先废弃老的 RangerPolicyEngineImpl 对象,再生成新的 RangerPolicyEngineImpl 对象并全量加载新的规则,而之前老的规则会在 GC 老的 RangerPolicyEngineImpl 对象时,在它的 finalize() 方法中清除,这样做的后果是会导致非常多的老的 RangerPolicyEngineImpl 对象在排队执行它们的 finalize() 方法,它们所占的内存无法及时释放,从而造成前面那样的内存剧烈膨胀,但实际上只有时间足够,这部分内存空间最终将被正常回收掉。
关于覆写 finalize() 方法导致的堆空间膨胀问题,详细请参见:
https://blog.csdn.net/okjxp/article/details/78426446
https://plumbr.io/blog/garbage-collection/debugging-to-understand-finalizer
创建5K条规则后,NameNode 的 java 堆如下所示,图中蓝色的空间即是 java.lang.ref.Finalizer 及相关的 Ranger 对象占用的空间:
排除了 Finalizer 对象的影响后,可以算出真正的堆空间增加是10M左右(只计算堆中的 live object 大小),远远小于之前的膨胀数据。
在实际使用环境中,为了防止这个问题,可以将 NameNode 同步的间隔调大(现网环境中可从默认的30s调整至30min),以避免这个问题。更进一步,可以修改 ranger 代码,不再复写 finalize 方法,而是使用标准的 try...catch...final 语句,来完成 finalize 所做的所有事情。
测试环境
内存测试使用现网机器,各项指标如下:
项目 | 数值 |
---|---|
CPU | 24核 Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz |
内存 | 64G |
测试方案和结果
为了规避上面 Finalize 对象的影响,采用下面的方案测试:
- 向 RangerAdmin 增加30W条规则。
- 启动 NameNode,对比不启用 Ranger HFS Plugin 和 启用 Ranger HDFS Plugin 这两种情况下,NameNode 进程所占用的物理内存、Java堆内存的变化。
测试结果如下:
- 物理内存变化
项目 | 加载规则前(MB) | 加载规则后(MB) | 增加内存(MB) |
---|---|---|---|
RSS(物理内存) | 662 | 1957 | 1295 |
- Java 堆内存变化
项目 | 加载规则前(MB) | 加载规则后(MB) | 增加内存(MB) |
---|---|---|---|
Java堆 | 211 | 1414 | 1203 |
Java堆(仅live) | 157 | 1228 | 1071 |
- 结论
- 物理内存和 Java 堆的变化比较一致,但是统计 live 的 Java 堆更具说服力,因为非 live 的对象虽然目前仍占有内存,但终将被 GC 掉,因此可以忽略。
- 因此可以得出结论:增加 30W 条规则后,内存占用增加 1G。
性能测试
Ranger 规则的应用过程和权限校验都在 Ranger Plugin 这一侧进行,和 HDFS 集成时,Ranger Plugin 运行在 NameNode 进程中,因此,如果规则数量太大的话,可能会延长鉴权过程,增大 NameNode 的响应时间。
测试环境
性能测试使用现网机器,各项指标如下:
项目 | 数值 |
---|---|
CPU | 24核 Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz |
内存 | 64G |
测试方案
经过分析,可以使用 NNBench + Ranger 的方法测试 NameNode 的 RPC 时延,具体方案如下:
在 NameNode 侧配置 Ranger Plugin:如果找不到 Ranger 规则,则 fallback 到 HDFS 默认方法进行鉴权。
以用户 root 启动 NameNode。
以用户 root,使用 NNBench 跑一下 create_write 测试(目标文件数量30W),生成所有必须的文件,特别是相关的 data 文件,hadoop client 默认配置的堆内存只有512M,为了防止 GC 影响,可以调大一点:export HADOOP_CLIENT_OPTS="-Xmx4G -Xms4G"。
以用户 root,将 HDFS 的根目录 / 的权限递归改为777,保证在后面 Ranger 匹配不到规则的时候,HDFS 默认的鉴权也能通过,相关操作也能正常进行。
以用户 root,ls -R 得到 NBench 生成的文件列表,然后停掉 NameNode,使用 curl+脚本为 NNBench 生成的每一个文件都配置一条 Ranger 规则(注意:规则的 user 一定配置成 root 之外的其它用户,例如 caoxudong),最后启动 NameNode,等待 NameNode 同步完成所有规则。需要注意的是,这一步耗时非常长,向 RangerAdmin 灌入30W 条规则大概需要40-50个小时,瓶颈在 MySQL,规则越多增加新规则越慢。
export HADOOP_USER_NAME=caoxudong,以用户 caoxuodng 访问 HDFS, export HADOOP_CLIENT_OPTS="-Xmx4G -Xms4G",启动 NNBench 开始测试 open_read,看看在启用和不启用 Ranger 的情况下的耗时差别,注意这一步一定不能以 root 进行,因为 root 根本不用进行鉴权。
-
这样一套测试下来,最终的测试结果是可信赖的,因为:
- 可以确保对每一个 NNBench 的 data 文件,都有对应的 Ranger 规则,因此访问 data 文件肯定走的是 Ranger 的检查(Eclipse 远程调试也验证了这一点),而 NNBench 也只统计对 data 文件的操作时间。
- open_read 测试不需要写 EditLog,最大程度排除了本地磁盘 IO 的影响。
- 在最终生成的结果里,只需要关注 Avg Lat (ms): Open: 这一项的结果,这个统计的是 org.apache.hadoop.hdfs.DistributedFileSystem.open(Path, int) 的平均耗时,它的核心过程是一次对 NameNode 的 RPC 调用:org.apache.hadoop.hdfs.protocol.ClientProtocol.getBlockLocations(String, long, long)
-
NNBench 使用的两条测试命令:
- create_write
bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.7.2-tests.jar nnbench -operation create_write -maps 1 -reduces 1 -blockSize 1048576 -bytesToWrite 0 -numberOfFiles 300000 -baseDir /benchmarks/NNBench-`hostname -s`
- open_read
bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.7.2-tests.jar nnbench -operation open_read -maps 1 -reduces 1 -numberOfFiles 300000 -readFileAfterOpen false -baseDir /benchmarks/NNBench-`hostname -s`
测试结果
启用 Ranger HDFS Plugin 前后,NNBench 的结果如下:
综上所述,30W个文件,每一个文件都有一条 Ranger 规则,因此对30W个文件的访问,就产生了30W次规则匹配过程,最终的影响是:
项目 | 时间(ms) |
---|---|
总耗时增加 | 10350 |
平均每次 RPC 耗时增加 | 0.035 |
可以认为,在30W条规则的数量级上,Ranger HDFS Plugin 对 NameNode 的 RPC 时延没有影响。