一、Keystone Token深度概述
Keystone作为OpenStack项目基础认证模块,目前支持的token类型分别是uuid、pkiz、pki、fernet。
首先,简要叙述一下这四种类型的原理及其优缺点。
uuid 比较简单,采用随机生成的序列(128位,以16进制表示)作为id,并构造token内容,需要持久化后端数据库支撑,比如MySQL数据库存储。优点,实现简单;缺点是持久化查询、每次访问都需要keystone相关服务进行认证。
pki(pkiz) 基于cms算法,token格式以PKIZ_开头,pki类的token 长度很长(pkiz只是对pki进行了一定程度的压缩),它将所有token的内容都放在了token中,并且认证不需要与keystone服务交互,直接采用相关的证书进行本地认证,这类似与传统的U盾技术,但是密钥是在keystone处生成好了之后,是不能够进行实时更新的,安全性相对较低,一旦被破解,相关的信息将被暴露,此时,如果对密钥进行更新,将导致前面生成的token不可用。它的缺陷在于,没有密钥的周期性更替机制(安全性大打折扣),同时,负载太大,部分rest接口,不能处理超长请求。在生产环境中,极不推荐这种方式。
fernet 是当前主流推荐的token格式,这种方式相较于前几种类型,是当前比较均衡的一种方式。它不需要后端持久化存储,与pki类似,但是在认证上,仍然需要与keystone进行交互。它的token结构,采用了特定的数理结构设计(包括socoped类型、user信息等等),结合加密技术,重要的优点是它的轮询替换算法。简要叙述一下fernet密钥的原理,默认的轮换长度是3,当以keystone-manage fernet-setup生成密钥时,会看到0、1两个索引表征,这分别是什么意思呢?在此,需要提一下三个概念,primary key(主密钥,用于加密、解密token)、secondary key(次次密钥,用于解密token)、staged key(次密钥,用于解密token),那么上述0 表示的是staged key,1 表示的是primary key,primary key相比较另外两种key,它的索引最高,并且可以加密、也可以解密;staged key 相较于secondary key是它更有机会变为primary key。也就是说,fernet的密钥不是不变的,并且可以周期性轮转,不同认证节点之间的更新频次可以相差1。这安全性大大提高,keystone 进行认证的时候,依赖于这些密钥进行解密,从而进一步实现认证。
在分析了上述四种token格式后,毋庸置疑,fernet是比较靠谱,且性能相对较优的格式。但是,如果仅从这些分析上,不足以保证fernet的优势,说服力还不够。下面两节,将分别从认证原理、及历史经验数据上做探究分析。fernet是当前比较合适的方式,这并不表示fernet就是最优的token方式。在后面的分析上,尽可能提出更优的方案。
二、openstack认证
Openstack项目的认证技术,除了与keystone认证服务相关,也离不开众多的中间件的处理,如典型的keystone/middleware。首先,任何的服务在使用之前都需要获取token,token的生成,按照各种token的格式,uuid是存储在持久化后端数据库中,其他方式采用加密技术,token ID中包含了token数据的信息。有了token后,所有服务在发送请求时,需要将token ID作为请求字段,否则,直接抛出异常。而token ID的认证,主要依赖于中间件组件的处理。在下一小节中,重点说明。
在介绍认证原理之前,必须明确几个概念,cache、revoke、service-token、user-token、expiration,其中cache就是缓存,在keystone中常用的缓存工具是memcache,它在内存中开辟一块空间,用于存放token,优点不用多说,会大大缩短获取token的时间,并且验证时间也缩短了;revoke是回收机制,keystone中的token过期回收机制,会根据token的过期时间作响应或者人为主动发出的token回收请求;service-token在请求头中用X-Service-Token表示,是M版后,keystone认证所改进的技术,它主要是区分普通的用户的请求和服务请求,区分的原因是引入了过期token的使用,P版将会更加强化这一概念,并且过期token的使用,只发生在服务请求中;user-token就是普通用户的请求,在请求头中用X-Auth-Token表示;expiration,即过期时间,在keystone中有两处过期的概念:token的过期时间、cache的过期时间。简要叙述一下,cache的过期时间的设定,一般我们认为,cache存储的时间越久越好,但是cache的容量有限,并且,它占据着内存的空间,因此cache内存的大小实际存在一个理论上的“极值”设定,超过了这个大小,将会影响服务的响应处理,除了这一点,另外就是过期时间,考虑到过期token,即使存储在cache中,它也认证不通过,但是,如果cache中不存在的话,需要keystone服务去获取并且验证,这个开销比存储在cache中的开销要大。虽然如此,考虑一种高压情况,cache的存储,我们希望有效的token存储越多越好,过期的token,我们只存储一个index值,这样不会占据太多的空间,并且认证过程中,很快能够检测出过期token。在下一段的改进性能优化上,进一步说明。
正式进入openstack认证原理的介绍。openstack的项目,依赖于keystone项目进行认证,借助于中间件的处理.Token包括两部分,分别是token ID、token Data,token Data是核心,包括用户的信息、角色信息、服务信息、服务入口等等。Openstack认证,首先去cache中,查询是否存在对应的Token,如果存在,则进一步验证token是否被回收。此流程很简单;如果cache中不存在的话,keystone服务会去获取token的信息,并调用对应的driver去验证token,不同格式的token处理上有些差异,pki(pkiz)直接本地认证即可;其它两种方式都需要和keystone服务交互。如果获取并认证通过了,则需要将token的信息加入到cache中,否则,认证不通过,返回结果。具体流程,如下图所示。
三、结合实际探究分析
回顾历史经验数据,曾经做过一个压力测试,验证token的抗压能力,uuid、fernet、pkiz的模式,得出的结论是fernet相对结果较好。我们的测试用例是nova list,这个结果,现在来看的话,有着它的必然性。能想到的keystone认证的瓶颈,主要是token 过期时间、cache的大小、时长、服务的worker数、数据库的性能。
fernet的数据性能最好,原因是它不需要后端持久化操作,并且token的认证,使用的是密钥进行解密,能够直接得出token Data的信息,从而进行token的过期认证,它的失败原因,只可能是token过期了,或者是token放到了cache中,但是已经被回收了。归根到底,还是token过期了。之前做的压测,设置的work数是8个,所以,当压力达到3千以上的时候,进程本身有一定的负载上限,因此在处理上,会出现多数的等待,我们看到获取token的那条命令,出错概率相对较低,原因是生成token的流程,没有后端数据库的操作,因此,比较流畅。有部分出错了,原因是由于请求等待时间过长,如果一直没有进程在处理,那么将会抛出获取token时的认证异常。其它出错,在认证时候的异常,原因是由于压力增多,导致一条请求的处理流程被拉长,token的过期时间也设置的较短,并且也存在请求长时间没有得到处理而抛出的认证异常。因此,当我们增加服务节点或者提高work数的时候,压力问题会得到有效的缓解。
Pki系列的性能也不太好,原因就是token长度太长。虽然它采用的是自认证的方式,在请求传递过程中的负载,将会消耗很大的时间,并且采用cache的方式,会导致命中出现问题,因为cache有大小上限,因此,很容易出现之前已经存在于cache的token,会被替换出去,从而影响后续的认证流程。它的请求传递开销,是它致命的缺陷。抗压能力较弱(此处说的是只有一台服务器的情况下)。
uuid的实现简单,但是抗压能力,是比较差的,因为它涉及大部分的持久化操作,数据库的性能直接影响压力测试的结果。常规情况下,我们不会单独再用一台数据库节点,因此,数据库性能不会很高,并且当数据库达到一定压力的时候,本身连接数据库就会变得很慢。
在上述分析中,我们从历史数据得出的部分解答。从近期的调研分析来看,openstack的服务在使用之前都需要认证,认证如果变得很快,那么openstack整体的服务性能将大大提升。认证过程中,除了增加服务数的方法、提高服务进程数外,token的cache命中率,也是有效提高认证的方法之一。除此之外,可信认证的充分利用,比如将某些服务发送的请求,去掉部分认证的环节,尤其是过期token的引入。
四、性能提升
为了提升token的认证能力,假设我们的使用场景是抗压能力测试,没有其他操作干扰,还是以nova list作为测试用例,我们以rest接口作为测试方法,没有超时限制,fernet作为token的格式,并且将token的时长调大,理论上只要token过期时长足够大,成功率,应该是能够达到100%。如果针对这个测试用例,非要找出出错的可能性,只可能出现在token过期。当我们引入过期token可再使用时,那么成功率将更加有保障。
在上述分析中,我们得出影响性能的几个关键因素:服务能力数(认证服务节点数量)、服务进程数(worker数量)、token的格式、cache的大小设置,cache策略。
服务能力数,即设置多少台服务节点是合适的,这个问题,需要考虑实际部署的机器的性能,因此,我们只有通过测试,这个值的大小大致是可以确定的。我们可以通过rest请求的响应时间作为参考值,我们需要提前预估一下响应时间的最大时间,即用户能够接受的时长,这个也是分请求的,比如创建虚机的请求,它涉及多个流程,它的响应时长的设置,应该是每个子操作响应时长的总合,因此,我们只需要设计好原子操作的响应时长。当合理的原子请求不能在规定的时间内得到响应,说明服务器服务需要优化,资源需要增加。因此,rest请求的响应能力,是判定服务能力的重要的参考依据。我们可以通过压力测试,找到服务能力的数值。
服务进程数,当服务器性能允许的情况下,这个数值越大,表示服务能力越强。但是,当进程太多,会拖慢机器的性能,这个值,在理论上也存在极值。一般情况下,我们开启32 或者64进程。具体的检测方法同上。也可以通过rest请求的压力测试得出。
token的格式,从上述的几种token的逻辑处理上,毋庸置疑,fernet的优势非常明显。
cache的大小设置,如果条件允许,并且token会被多次利用的概率较大,cache大小设置大一点。另外,在实际的生产环境中,获取了token后,95%的情况下,token会需要被验证,因此,token的改进措施,就是在生成了token后,就可以将token加入到cache中,这样在验证的时候,直接可以通过cache机制认证。这一点需要一定的cache大小空间的支撑,在下面的cache策略中再详述。
cache策略,在当前keystone服务中,token认证通过了,会被加入到cache中。token过期或者达到缓存时长,将会从cache中移除。如果token被回收,存储在cache中token会被标记为Invalid Token。Keystone发展到O版,service-token引入,并且允许过期token的使用,在私有云环境下,尤其是内部使用的项目,在cache策略上,service-token认证的流程,应该可以简化,甚至注释掉。但是,前面的策略只是某些个列情况下,当前比较优化的方案是,当token生成了后,直接将token放入cache中,在正常的业务流程中,该token会被用来发送请求。这样的话,在压力负载较大的情况下,优势会比较明显。
以上仅是个人的观点,如有问题,请及时与我探讨分析。