namespace设计解读
namespace是Kubernetes进行多租户资源隔离的主要手段,那么它在系统中的表现形式是什么样的?实现原理和使用方法又是怎样的呢?
什么是namespace
namespace是一个将Kubernetes的资源对象进行细分的类似于DNS子域名的概念。namespace能够帮助不同的租户共享一个Kubernetes集群。Kubernetes引人namespace的目的包括以下几点:
- 建立一种简单易用,能够在逻辑上对Kubernetes资源对象进行隔离的机制。
- 将资源对象与实际的物理节点解耦,用户只需关注namespace而非工作节点上的资源情况。
- 随着Kubernetes访问控制代码开发的深人,与Kubernetes认证和授权机制相结合。
- 通过namespace对Kubernete,资源进行归类,使得APIserver能够建立一套有效的过滤Kubernetes资源请求的机制。
Kubernetes用户认证机制
Kubernetes用以认证用户请求的方式主要有5种,下面将逐一进行简单说明。
基于客户端证书的认证机制
在APIServer启动过程中,通过传入--client-ca-file=SOMEFILE参数可以启用客户端证书认证。上面引用的文件必须包含一个或多个证书颁发机构(CA)用于检验用户传给APIServer的证书的合法性。如果客户端证书提交给APIServer且被认证通过,则SSL证书主题中的共用名(Common Name)将作为客户端发起API请求的用户名。
基于token的认证机制
当客户端向Kubernetes安全端口发起API请求时,Kubernetes还支持使用token来认证用户的合法性。token预先存储在一个token文件中,在APIServer启动过程中传入--token-auth-file=SOMEFILE参数便可启用Kubernetes认证模块。token文件是一个CSV格式的文件(token文件格式目前由package tokenfite实现,具体代码见:plugin/pkg/auth/authenticator/token/token-file/...),至少有3列数据,分别是token, user name和user id,其后可以有选择地加上groupnames。
基于OpenID Connect ID Token的认证机制
OpenID ConnectID Token是一种特殊的认证方式。与其为每个希望访问的服务器都单独构建一个新的账户,不如使用一个通用的OpenID,而该OpenID的签发方来向各个服务器来提供认证服务。
Basic认证机制
Basic认证通过--basic-auth-file参数简单地传入用户名、用户id和密码进行认证。当某个HTTP客户端使用这种认证方式向APIServer发起请求时,应该在Authorization头部附上一个Basic BASE64ENCODED(USER:PASSWORD)的值。事实上,该认证方式在安全方面并不十分可靠,通常只用作其他几种认证方式的补充。
Keystone认证机制
当启用这种认证方式时,用户会从Keystone API中获得一个token,并且将该token作为该用户的密码注入到kubeconfig文件中。当用户向APIServer发送请求时,APIServer会向Keystone校验该用户及其token是否合法,验证通过时则对该请求进行过应答。
Kubernetes用户授权机制
在Kubernetes中,认证和授权是分开的,而且授权发生在认证完成之后,认证过程是检验发起API请求的用户是不是他所声称的那个人,而授权过程则判断此用户是否有执行该API请求的权限,因此授权是以认证的结果作为基础的。Kubernetes授权模块应用于对APIServer安全端口的HTTPS访问请求。
Kubernetes授权模块检查每个HTTP请求并提取请求上下文中的所需属性(例如user,resource, namespace)与访问控制规则进行比较。任何一个API请求在被处理前都需要通过一个或多个访问控制规则的验证。
目前Kubernetes支持并实现了以下的授权模式(authorization mode ),这些授权模式可以通过在APIServer启动时传入参数进行选择:
--authorization_mode=AlwaysDeny
--authorization_mode=AlwaysAllow
--authorization_mode=ABAC
--authorization_mode=Webhook
--authorization_mode=RBAC
--authorization_mode=Node
复制
Kubernetes多维资源管理机制admission control
除了上一节介绍的3种相对传统的授权模式之外,Kubernetes还提供了一种多维度可扩展的资源管理机制admission control,方便用户实现资源配额等功能。
与Kubernetes用户认证与授权模块一样,对admission control的调用也是由APIServer来完成的。APIServer启动过程中就进行了3个初始化操作,创建了3个对象:认证、授权和admission control。APIServer会在用户的请求通过认证授权之后调用admission control,对用户请求进行进一步的审核。所以即使用户请求通过了认证授权,也有可能因为申请的资源超过资源配额而被admission control驳回。
需要说明的是,admission control只负责管理API对资源的请求量,一旦pod或容器实际在某台机器上运行后,并不控制它们的行为。因此admission control实际上是一个静态的、运行前的概念,而不是运行时的概念。
admission control是一种多维度可扩展的资源管理机制,每个维度通过一个admission control插件实现。这种插件被称为admission controller或者admission control plugino Kubernetes APIServer接受以下可选参数来启用admission control。
- admission-control。以逗号作为分割符的admission control插件的列表,在新建、修改和删除Kubernetes对象之前会调用这些插件来检查该操作是否合法。默认是AlwaysAdmit,即永远允许。
- iadmission-control-config-file。启动admission control插件的配置文件。
下面将会以Kubernetes系统自带的几个admission control插件为例,介绍如何向Kubernetes集群加入用户自定义的admission control插件,以更好地实现对集群资源的定制化管理控制。
admission control插件是以下接口的一个实现:
admission_controller结构体.png
Attributes接口被admission control用来获取一个API请求中能够用于帮助进行决策的信息,这些信息包括API请求所处上下文的namespae, API操作的Kubernetes对象类型、API操作类型和实际运行时的Kubernetes对象。
admission_Interface结构体.png
Interface接口是一个抽象的、可插拔的、用于进行资源管理控制决策的admission control接口。Interface接口的Admit则根据一个API请求的实际信息进行实际的管理决策,负责主要逻辑部分。Handles用于指明该admission controller否支持CREATE, UPDATE, DELETE或CONNECT操作,返回值为bool型。
任意一个admission control插件都是admission.Interface,接口的一个实现用户也可以非常容易地实现自定义的插件。在撰写本书时,共有9个推荐使用的系统admission control插件,分别是AlwaysDeny, AlwaysAdmit, AlwaysPullImages, DenyEscalatingExec, ServiceAccount, SecurityContextDeny, ResourceQuota, LimitRanger和NamespaceLifecycle。
NamespaceLifecycle插件
该插件拒绝在终止状态的namespace(即用户发起删除一个namespace的请求后,此时该namespace的.status.phase为Terminating状态,待该namespace下的所有资源全部被删除后,它才会退出)中创建新资源的请求,以及在尚未存在的namespace下创建资源的请求。
NamespaceLifecycle的Admit函数非常直观地展示了该admission controller的策略,它接受CREATE、UPDATE和DELETE操作。在每次接收到API请求之后,执行如下检查:
- 当接收到的API请求试图删除namespace时,如果该namespace为default,拒绝该请求,否则接收该请求,并且从本地cache中将该namespace的记录删除。
- 当接收到的API请求为资源相关的请求时,查找其对应的namespace,如果该namespace不存在,拒绝该请求。
- 当接收到的API请求为创建资源请求时,如果该namespace为Terminating状态,拒绝该请求。
- 当上述情况均未违反相应规则时,则通过该请求。
LimitRanger插件
LimitRanger插件的作用是判断客户端API请求中的资源需求是否符合系统管理员预设值,包括上限、下限、默认值以及默认比率。为了配合LimitRanger插件的使用,Kubernetes引入了LimitRange对象,用于对特定namespace中的Kubernetes对象设置资源使用的预设值。当前Kubernetes支持以namespace为单位,在pod和容器两个层次对资源使用进行管理。
先来看一下LimitRange的数据结构定义:
limitRanger结构体.png
其中,ObjectMeta表示LimitRange对象的元数据(所有Kubernetes资源对象都有元数据),而LimitRangeSpec本质上是一个包含LimitRangeItem类型数组的结构体。LimitRangeItem是对具体Kubernetes对象类型应用其能够使用的资源列表的设定值,其中资源列表是键为资源名值为整型或字符串类型的map结构。
最后,看一个具体的例子:limit-range.yaml。该Kubernetes资源文件描述了一个LimitRange对象(由kind字段指定)。
limitRanger对象yaml样例.png
与定义Kubernetes其他对象(pod等)的资源文件一样,该yaml文件的kind字段表明该资源文件定义的是LimitRange对象。spec.limits.type字段指定应用限制的对象,这里分别是pod和containero spec.limits.max和spec.limits.min字段分别表示资源列表上限与下限,该文件定义的资源列表包含两个资源类型:内存和CPU。该文件表明,应用到namespace中每个pod的资源限制均为:最大内存1 Gi,最小内存6Mi,最大CPU计算资源2,最小CPU计算资源200m(即0.2 );spec.limits.default和spec.limits.defaultRequest字段分别表示资源列表的默认请求上限和默认请求下限,同样包含了CPU和内存两种资源类型。如果用户在创建pod时未指定spec.container[].resources.limits和spec.container[].resources.requests,这两个值将会被启用。此处应用到namespace中每个容器的资源限制为,默认请求下限:CPU200m,内存100Mi ;默认请求上限:CPU300m,内存200Mi。
接下来我们将从代码的角度,简要分析下LimitRanger插件的实现原理:
- 首先,向admission control注册一个名为LimitRanger的插件。
- 然后,实现Admit方法。该方法首先获取发起API请求所在的namespace中的LimitRange对象列表。遍历该LimitRange对象列表,执行如下检查:
- 对于.spec.container.resources.limits和.spec.container.resources.requests字段为空的manifest,填入LimitRange设定的默认值。
- 对于pod中的每个container,检查其.container.resources.limits和.container.resources.requests是否满足LimitRange的.spec.limits.max, .spec.limits.min和.spec.limits.maxLimitRequestRatio的设定。如果不满足,则拒绝该请求。
LimitRanger插件只适用于Kubernetes对象创建(CREATE)和更新(UPDATE)操作,并不受理删除操作。需要注意的是,一个namespace中可能存在不止一个LimitRange对象,因此,任何一个针对Kubernetes对象的创建和更新操作都要接受该namespace中所有LimitRange对象的限制。
SecurityContextDeny插件
pod manifest中的security context定义了应用于容器的安全设置(如uid. gid, SELinux角色等),包括pod级别和容器级别的限制。SecurityContextDeny插件会拒绝任何定义了其中某些字段的pod。
SecurityContex取在pod创建(CREATE)和更新(UPDATE)时发生作用。它的Admit函数逻辑如下:
- 检查REST资源类型,如果不是pod,则接受该请求。
- 检查pod级别的SecurityContext。如果.supplementalGroups, .seLinuxOptions, .runAsUser或.fsGroup不为空,拒绝该API请求。
- 逐一检查容器级别的SecurityContext。如果.seLinuxOptions, .runAsUser不为空,则拒绝该请求。
ServiceAccount插件
service account为pod中的进程提供id。实际上,当用户试图与APIServer打交道时,往往通过user account来进行身份的认证及授权;类似地,集群中的pod也可能希望与APIServer通信,系统并不为其创建user account,而作为代替的就是service account。
如果用户启用了service account自动生成的功能,controller就会接管这项工作,主要涉及了3个层面,分别对应service account admission controller, token controller和service account controllero这里我们主要关注第一个。
service account插件在pod创建(CREATE)时发挥作用,其Admit函数包括如下检查:
- 如果创建的pod是mirror pod,且其.spec.serviceAccountName或volumes.VolumeSource.secret不为空,则拒绝该请求。
- 如果创建的pod的.spec.serviceAccountName为空,则将该字段设为同一namespace下的default service account。
- 检查pod引用的service account是存在的,如不存在拒绝该请求。
- 查找service account对应的secret token,如该pod中未存在该volume,则为其挂载。
ResourceQuota插件
ResourceQuota插件的作用是为特定namespace应用资源使用的配额。与LimitRanger插件类似,为了配合ResourceQuota插件的使用,Kubernetes引人了ResourceQuota对象,用于对特定namespace中的Kubernetes对象设置资源配额。
这里先梳理一下Kubernetes资源的概念。直观地,CPU、内存等通用计算机资源也属于Kubernetes资源类型,在Kubernetes的代码中可以找到以下定义:
kubernetes_resource结构体.png
而Kubernetes系统定义的对象类型,譬如pod, service, replication controller, resourcequota
等,也属于Kubernetes资源类型,同样可以在Kubernetes的代码中可以找到以下定义:
kubernetes_resource结构体2.png
每种Kubernetes资源对象都对应一组元数据,包括Name, Namespace和ResourceVersion,分别表示该资源对象的名字,所在Namespace和资源版本号,其中资源版本号用于标识该资源的新旧程度,方便进行版本和数据一致性控制。
Resource0uota的数据结构定义如下所示:
ResourceQuota结构体.png
其中,unversioned.TypeMeta表示ResourceQuota对象的元数据。此外,Resource0uota包含两个数据类型:ResourceQuotaSpec和ResourceQuotaStatus,分别表示特定namespace中预设的资源配额和资源配额的实际使用情况,存储在etcd中的ResourceQuota.Status字段的数据会由controller manager的资源配额管理器根据系统实际的资源使用量而动态更新。
结束了对ResourceQuota对象的讨论后,再来看具体的例子resource-quota.yaml,该资源文件描述了一个ResourceQuota对象(见kind字段)。
resource_quota_yaml样例.png
如上所示,spec:hard即资源配额列表,其各字段的含义和单位如表所示。
ResourceQuota资源对象配置各字段含义和单位.png
ResourceQuota插件只检验创建和更新资源的API请求,其一般过程如下所示:
- 首先检查该API操作对象是否在ResourceQuota资源列表内,及该操作是否会影响资源对象的数量,如不满足,则接受该API请求。
- 检查该资源对象所在的namespace是否有ResourceQuota对象,如没有,则接受该API请求。
- 遍历所有与该资源对象相关的ResourceQuota,根据操作资源对象的不同,检查其.spec定义是否符合要求。以pod为例,如果在ResourceQuota对象中对于cpu进行了定量限制,那么则要求所有在该namespace中的pod必须定义.spec.containers.resource下的cpu值。
- 根据API请求的资源需求量,检查资源请求量总和是否超过.status.hard的限定,如果超过,则拒绝该请求。对于不同的API请求操作,计算资源请求量总和的方法各不相同。
- 如果请求的类型是CREATE,资源请求量总和为请求的资源需求量与当前.status.used中对应的资源使用量求和。
- 如果请求的类型是UPDATE,资源请求量总和为请求的资源更新值(请求值减去原先值)与当前.status.used中对应的资源的使用量求和。
- 对于每个ResourceQuota,更新其对应的资源对象数目及.status.used,并在etcd中持久化该更新。