OpenStack keystone详解及调优

一、Keystone基本概念介绍

User

User即用户,他们代表可以通过keystone进行访问的人或程序。Users通过认证信息(credentials,如密码、API Keys等)进行验证。

Tenant

Tenant即租户,它是各个服务中的一些可以访问的资源集合。例如,在Nova中一个tenant可以是一些机器,在Swift和Glance中一个tenant可以是一些镜像存储,在Quantum中一个tenant可以是一些网络资源。Users默认的总是绑定到某些tenant上。

Role

Role即角色,Roles代表一组用户可以访问的资源权限,例如Nova中的虚拟机、Glance中的镜像。Users可以被添加到任意一个全局的 或 租户内的角色中。在全局的role中,用户的role权限作用于所有的租户,即可以对所有的租户执行role规定的权限;在租户内的role中,用户仅能在当前租户内执行role规定的权限。

Service

Service即服务,如Nova、Glance、Swift。根据前三个概念(User,Tenant和Role)一个服务可以确认当前用户是否具有访问其资源的权限。但是当一个user尝试着访问其租户内的service时,他必须知道这个service是否存在以及如何访问这个service,这里通常使用一些不同的名称表示不同的服务。在上文中谈到的Role,实际上也是可以绑定到某个service的。例如,当swift需要一个管理员权限的访问进行对象创建时,对于相同的role我们并不一定也需要对nova进行管理员权限的访问。为了实现这个目标,我们应该创建两个独立的管理员role,一个绑定到swift,另一个绑定到nova,从而实现对swift进行管理员权限访问不会影响到Nova或其他服务。

Endpoint

Endpoint,翻译为“端点”,我们可以理解它是一个服务暴露出来的访问点,如果需要访问一个服务,则必须知道他的endpoint。因此,在keystone中包含一个endpoint模板(endpoint template,在安装keystone的时候我们可以在conf文件夹下看到这个文件),这个模板提供了所有存在的服务endpoints信息。一个endpoint template包含一个URLs列表,列表中的每个URL都对应一个服务实例的访问地址,并且具有public、private和admin这三种权限。public url可以被全局访问(如http://compute.example.com),private url只能被局域网访问(如http://compute.example.local),admin url被从常规的访问中分离。

二、keystone 的访问流程

通俗的讲,token 是用户的一种凭证,需拿正确的用户名/密码向 Keystone 申请才能得到。如果用户每次都采用用户名/密码访问 OpenStack API,容易泄露用户信息,带来安全隐患。所以 OpenStack 要求用户访问其 API 前,必须先获取 token,然后用 token 作为用户凭据访问 OpenStack API。

以创建一个虚拟机(server)为例,结合上图简述下keystone在openstack的访问流程。

* 用户Alice通过自己的户名和密码向keystone申请token,keystone认证用户名和密码后,返回token1

* Alice通过token1发送keystone查询他所拥有的租户,keystone验证token1成功后,返回Alice的所有Tenant

* Alice选择一个租户,通过用户名和密码申请token,keystone认证用户名、密码、tenant后,返回token2。(其实1、2步仅仅是为了查询tenant,如果已经知道tenant,可以忽略1、2步)

* Alice通过token2发送创建server的请求,keystone验证token2(包括该token是否有效,是否有权限创建虚拟机等)成功后,然后再把请求下发到nova,最终创建虚拟机。

1.获取临时Token

我们知道要创建虚拟机,一定是某一租户下的用户来创建的,因此在创建之前用户要获取自己所能访问到的租户 (一个用户可以属于多个租户),要想获取用户能访问的所有租户需要从keystone获取一个不与任何租户相关联的临时Token. 我们可以使用keystone的标准REST API获取某一用户能访问的所有租户,注意这里不要在请求体中设置租户名。

示例请求:

示例响应:

其中,响应中的access/token/id的值就是获取的临时Token,在接下来的请求中,将这个临时Token作为 X-Auth-Token的值(其中X-Auth-Token位于请求头部)。

2.获取用户能访问的所有租户

我们使用上一步获取的临时token(a19bc13b46ba459cb3104fa97e414a27),来获取用户demo所能访问的租户,用户所能访问的租户由用户在租户中是否有角色来决定。使用下面api获取是所能访问的租户时,需要将临时token作为X-Auth-Token的值,如下所示。

3.获取指定租户的Token

上一步中我们获取的用户所能访问到租户,接着用户需要从中选择一个租户,作为自己的工作空间, 用户在某一租户中才能访问其他非keystone服务,也只能使用指定租户的Token才能访问其他非keystone服务。 获取指定租户的Token,所使用的REST API与步骤1相同,仅有的区别:在body体中要指定租户。

示例请求:

示例响应:

除此之外,我们在body体中可以使用步骤1中的临时Token,来获取指定租户(demo)的token。

4.调用目标服务

有了指定租户的Token,我们就可以调用该租户提供的服务,比如demo租户提供类glance服务,那么我们怎么访问租户提供的服务呢?细心的读者可能在步骤3中已经发现,响应中提供每种服务都有的endpoint,这样endpoint是响应服务的REST API前缀。

如glance的endpoint为:http://192.168.56.2:9292

而其REST API为:http://192.168.56.2:9292 + api_uri

例如:http://192.168.56.2:9292/v2/images/{image_id}

我们通过调用服务的REST API调用目标服务,当请求到达响应服务之前,会先通过keystone验证用户的Token是否有效(如Token是否过期,Token对应的用户是否存在),验证通过后,服务进行后续操作。(注意:图中示例了当Token使用UUID的情况,目前大多数情况下PKI)。

5.验证用户是否有权限执行操作

我们指定用户在某一租户中有响应的角色,这些角色决定了用户在该租户中的操作权限,默认情况下有admin和非admin两种角色,当然我们也可以添加角色,若自定义添加角色,则要在相应服务中的policy.json文件中定义相应角色的所能执行的操作。OpenStack的每个组件都有policy.json文件,其基于该文件实现基于角色的访问控制,当经过policy的检测,用户有权执行某一操作,相应服务才会对请求做进一步的处理。

6.服务执行用户请求

这一步由服务来完成用户请求,如图中所示创建虚拟机。

7.给用户响应

将用户的请求结果呈现给用户,注意立即呈现的响应结果不一定是最终的响应结果,如创建虚拟机时,首先返回给用户的是虚拟机正在building。

三、Token详解

D 版本中,仅有 UUID 类型的 Token,UUID token 简单易用,却容易给 Keystone 带来性能问题,从图一的步骤 4 可看出,每当 OpenStack API 收到用户请求,都需要向 Keystone 验证该 token 是否有效。随着集群规模的扩大,Keystone 需处理大量验证 token 的请求,在高并发下容易出现性能问题。

于是 PKI( Public Key Infrastructrue ) token 在 G 版本运用而生,和 UUID 相比,PKI token 携带更多用户信息的同时还附上了数字签名,以支持本地认证,从而避免了步骤 4。因为 PKI token 携带了更多的信息,这些信息就包括 service catalog,随着 OpenStack 的 Region 数增多,service catalog 携带的 endpoint 数量越多,PKI token 也相应增大,很容易超出 HTTP Server 允许的最大 HTTP Header(默认为 8 KB),导致 HTTP 请求失败。

顾名思义, PKIZ token 就是 PKI token 的压缩版,但压缩效果有限,无法良好的处理 token size 过大问题。

前三种 token 都会持久性存于数据库,与日俱增积累的大量 token 引起数据库性能下降,所以用户需经常清理数据库的 token。为了避免该问题,社区提出了 Fernet token,它携带了少量的用户信息,大小约为 255 Byte,采用了对称加密,无需存于数据库中。

UUID

UUID token 是长度固定为 32 Byte 的随机字符串,由 uuid.uuid4().hex 生成。

def _get_token_id(self, token_data):

return uuid.uuid4().hex

但是因 UUID token 不携带其它信息,OpenStack API 收到该 token 后,既不能判断该 token 是否有效,更无法得知该 token 携带的用户信息,所以需经图一步骤 4 向 Keystone 校验 token,并获用户相关的信息。其样例如下: 144d8a99a42447379ac37f78bf0ef608 UUID token 简单美观,不携带其它信息,因此 Keystone 必须实现 token 的存储和认证,随着集群的规模增大,Keystone 将成为性能瓶颈。

PKI

在阐述 PKI(Public Key Infrastruction) token 前,让我们简单的回顾 公开密钥加密(public-key cryptography) 和 数字签名 。公开密钥加密,也称为非对称加密(asymmetric cryptography,加密密钥和解密密钥不相同),在这种密码学方法中,需要一对密钥,分别为公钥(Public Key)和私钥(Private Key),公钥是公开的,私钥是非公开的,需用户妥善保管。如果把加密和解密的流程当做函数 C(x) 和 D(x),P 和 S 分别代表公钥和私钥,对明文 A 和密文 B 而言,数学的角度上有以下公式:

B = C(A, S)A = D(B, P) 其中加密函数 C(x), 解密函数 D(x) 以及公钥 P 均是公开的。采用公钥加密的密文只能用私钥解密,采用私钥加密的密文只能用公钥解密。非对称加密广泛运用在安全领域,诸如常见的 HTTPS,SSH 登录等。

数字签名又称为公钥数字签名,首先采用 Hash 函数对消息生成摘要,摘要经私钥加密后称为数字签名。接收方用公钥解密该数字签名,并与接收消息生成的摘要做对比,如果二者一致,便可以确认该消息的完整性和真实性。

PKI 的本质就是基于数字签名,Keystone 用私钥对 token 进行数字签名,各个 API server 用公钥在本地验证该 token。相关代码简化如下:

其中 cms.cms_sign_token 调用 openssl cms –sign 对 token_data 进行签名,token_data 的样式如下:

token_data 经 cms.cms_sign_token 签名生成的 token_id 如下,共 1932 Byte:

PKIZ

PKIZ 在 PKI 的基础上做了压缩处理,但是压缩的效果极其有限,一般情况下,压缩后的大小为 PKI token 的 90 % 左右,所以 PKIZ 不能友好的解决 token size 太大问题。

其中 cms.pkiz_sign() 中的以下代码调用 zlib 对签名后的消息进行压缩级别为 6 的压缩。

compressed = zlib.compress(token_id, compression_level=6)

PKIZ token 样例如下,共 1645 Byte,比 PKI token 减小 14.86 %:

Fernet

用户可能会碰上这么一个问题,当集群运行较长一段时间后,访问其 API 会变得奇慢无比,究其原因在于 Keystone 数据库存储了大量的 token 导致性能太差,解决的办法是经常清理 token。为了避免上述问题,社区提出了 Fernet token ,它采用 cryptography 对称加密库(symmetric cryptography,加密密钥和解密密钥相同) 加密 token,具体由 AES-CBC 加密和散列函数 SHA256 签名。 Fernet 是专为 API token 设计的一种轻量级安全消息格式,不需要存储于数据库,减少了磁盘的 IO,带来了一定的 性能提升 。为了提高安全性,需要采用 Key Rotation 更换密钥。

以上代码表明,token 包含了 user_id,project_id,domain_id,methods,expires_at 等信息,重要的是,它没有 service_catalog,所以 region 的数量并不影响它的大小。self.pack() 最终调用如下代码对上述信息加密:

该 token 的大小一般在 200 多 Byte 左右,本例样式如下,大小为 186 Byte:

gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc

7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQ

XBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8

如何选择 Token

Token 类型的选择涉及多个因素,包括 Keystone server 的负载、region 数量、安全因素、维护成本以及 token 本身的成熟度。region 的数量影响 PKI/PKIZ token 的大小,从安全的角度上看,UUID 无需维护密钥,PKI 需要妥善保管 Keystone server 上的私钥,Fernet 需要周期性的更换密钥,因此从安全、维护成本和成熟度上看,UUID > PKI/PKIZ > Fernet 如果:

* Keystone server 负载低,region 少于 3 个,采用 UUID token。

* Keystone server 负载高,region 少于 3 个,采用 PKI/PKIZ token。

* Keystone server 负载低,region 大与或等于 3 个,采用 UUID token。

* Keystone server 负载高,region 大于或等于 3 个,K 版本及以上可考虑采用 Fernet token。

四、keystone性能优化

openstack采用了token认证的机制,各api的调用都会涉及到token的验证问题,使得keystone成为一个性能的瓶颈。

token的验证环节包括:验证请求中包含的token是否有效、过期,该token对应的用户组和用户id,对应的授权服务访问地址等。

性能瓶颈的解决-1:memcache缓存

由于openstack中的各api都是wsgi服务,并且都用到了keystoneclient提供的一个中间件(wsgi filter)auth_token,对应的文件位于:keystoneclient/middleware/auth_token.py。该中间件采用memcache来缓存token的相关信息到本地,从而减少各服务对keystone的直接访问,不过默认情况下缓存并未启用。为此,添加如下配置到nova.conf、cinder.conf…

auth_token中间件中,token认证的相关代码片段如下:

性能瓶颈的解决-2:keystone并行化

当前的keystone实现中并没有采用并行化的机制,keystone-all运行时分别发起两个进程、绑定到两个socket上,分别处理5000和35357端口上的请求。

大概的修改如下:

* 引入了多线程下共享的socket

* 根据配置选项works的大小,发起多个进程处理api的请求

代码修改之后,还需对keystone的配置做适当更新:

keystone默认的token存储后端为基于内存的k-v,范围在一个进程的空间内。并行化之后,多个进程需共享访问token的存储后端,这里采用memcache。修改keystone.conf,并安装memcache服务。

[token]

# driver = keystone.token.backends.memcache.Token

本文分享自微信公众号 - SDNLAB(SDNLAB)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-09-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云计算D1net

如何避免陷入意外的“云锁定”窘境

鉴于云仍然处于其发展的初期阶段,云服务供应商之间的竞争依然十分激烈。因此,云服务成本在不断地下降,而其功能与性能则在不断得到提升。其结果就是,众多云项目规划者都...

31270
来自专栏张戈的专栏

SendCloud邮件队列状态和已使用额度的Python监控脚本

公司最近用上了 SendCloud 的邮件代发服务,于是就有了各种监控需求。比如每天发信额度是不是要超标了或是邮件是否堵塞了等等。最近经常接触 python,所...

42590
来自专栏张戈的专栏

分享几个可用的二维码API,以及给博客添加文章二维码图片的方法

最新补充:博客已分享性能最好的 js 生成二维码方案==>传送门 今天发现之前用的二维码 API 不怎么稳定了,老是出现图裂无法加载的情况。用的是 api.qr...

69940
来自专栏张戈的专栏

Linux系统date命令无法修改或同步时间的解决办法

今天,在站长交流群里面,又一个站长抱怨服务器每星期都必须手动重启一次,否则 QQ 登陆功能无法使用,原因是服务器时间快了 5 分钟以上,腾讯服务器拒绝提供 AP...

47540
来自专栏Golang语言社区

nwui —— 又一个go语言图形界面解决方案

Github: https://github.com/go-nwui/nwui 最近开的一个大坑,具体实现就是自动生成htm+css+js然后调用nw.js来显...

29630
来自专栏Golang语言社区

问题帖子--Concurrent Read/Write Map

DK1.5 引入了 concurrent package, 提供了更多的concurrent 控制方法。 还提供了一个 ConcurrentHashMap 类...

378120
来自专栏云计算D1net

让云API远离黑客攻击

没有合适的安全措施,云API就会成为黑客的一扇门。那么如何确保云API的安全呢? 开发者可以使用云应用编程接口编码,而这个接口具备一项云提供商的服务。但是同时对...

43760
来自专栏张戈的专栏

分享张戈博客自用的php网址在线转换二维码的API源码

最新补充:博客已分享性能最好的 js 生成二维码方案==>传送门 去年张戈博客曾分享过一篇与二维码 API 有关的文章:《分享几个可用的二维码 API,以及给博...

42030
来自专栏Golang语言社区

Go语言开发Windows应用

当第一次看到Go程序在windows平台生成可执行的exe文件,就宣告了windows应用也一定是Go语言的战场。Go不是脚本语言,但却有着脚本语言的轻便简单的...

64660
来自专栏张戈的专栏

WordPress发布文章自动同步到新浪微博(带特色图片)

WordPress 发博客后自动同步到新浪微博,这是我从无主题博客看到的方法,一直沿用至今。感觉对博客宣传和提升“逼格”都有显著的作用: ? 一、老版代码 先来...

57870

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励