Z.S.K.'s Records

Kubernetes学习(capsule实现多租户模型)

capsule做为kubernetes的多租户模型的另一实现, 与hnc有异曲同工之处,由于Capsule的声明性,以及所有的配置都可存储在Git中,因此Capsule原生具备GitOps特征

架构

在单个集群中,Capsule Controller将多个命名空间聚合在一个名为Tenant的轻量级抽象中,基本上是一组Kubernetes命名空间。在每个租户中,用户可以自由地创建他们的名称空间并共享所有分配的资源。
另一方面,capsule 策略引擎保持不同租户彼此隔离。网络和安全策略、资源配额、限制范围、RBAC以及在租户级别定义的其他策略会自动由租户中的所有命名空间继承。然后,用户可以自由地自主地操作他们的租户,而不需要集群管理员的干预

租户

在Capsule中,租户是一种抽象,用于在集群管理员定义的一组边界内将多个命名空间分组到单个实体中。然后将租户分配给称为租户所有者的用户或用户组
只有集群管理员才有权限操作Tenant对象

1
2
3
4
5
6
7
8
9
10
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
spec:
owners:
- name: alice
kind: User
- name: system:serviceaccount:tenant-system:robot
kind: ServiceAccount

声明了一个oil的租户, 它的租户管理员是User为alice的人或者是sa, 具有相应权限的sa也可以是租户管理员
每个租户都有一个委派的用户或一组用户作为租户管理员。在capsule中这被称为租户所有者。其他用户可以在租户内部使用由租户所有者直接分配的不同级别的权限和授权进行操作。
Capsule不关心集群中使用的身份验证策略,并且支持所有Kubernetes身份验证方法。使用Capsule的唯一要求是将租户用户分配到由--capsule-user-group选项定义的组(该参数很重要,可以传入多个值),默认安装时这个参数的值为capsule.clastix.io。

具体的身份验证策略详见下文

一般在集群管理员创建完租户后, 会将相应的集群认证文件(常见的如kubeconfig文件,相当还可以是token等)发送给owners列表中的用户, 他们便可使用以下命令查看是否有创建namespace的权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 具有创建ns的权限
kubectl auth can-i create namespaces
Warning: resource 'namespaces' is not namespace scoped
yes

# 具有删除ns的权限,只能删除由它创建的ns
kubectl auth can-i delete ns -n oil-production
yes

# 但是没有get ns的权限
kubectl auth can-i get namespaces
no

# 没有get 其它集群级别资源的权限
kubectl auth can-i get nodes
no
kubectl auth can-i get persistentvolumes
no

# 也不能创建及get租户资源
kubectl auth can-i get tenants
no

Owner的用户可以创建/删除ns,但无法获取除ns之个的集群级别的资源权限, 相关的权限都限制在它创建的ns中
默认情况下,租户所有者将通过RoleBinding API被授予两个ClusterRole资源:

  • Kubernetes admin(可以理解为是限制在namespace中的admin而不是真的集群admin),由它授权大多数命名空间范围内的资源
  • 由Capsule创建名为capsule-namespace-deleter的权限,允许删除创建的命名空间,所以Owner有删除ns的权限

要注意的一点是,由于Owner支持serviceaccount,需要确保不能使用groupsystem:serviceaccounts或groupsystem:serviceaccounts:{capsule-namespace}作为Capsule组,否则您将在Capsule控制器中出现短路,因为Capsule本身由serviceaccount控制

属性

Tenant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
spec:
owners:
- name: alice
kind: User
additionalRoleBindings: # 可以为相关的用户指定额外的权限,与rbac进行联动
- clusterRoleName: 'argoproj-provisioner'
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: User
name: joe
namespaceOptions: # 可以创建的最大namespace数量
quota: 3
additionalMetadata: # 给ns添加annotation及label
annotations:
storagelocationtype: s3
labels:
capsule.clastix.io/backup: "true"
resourceQuotas: # ns的resourcequota, 最终转换成ns的resourcequota对象, 也支持自定义的资源quota
scope: Tenant # 可以是tenant级别(该tenant下的所有的ns有效), 也可以是namespace级别(针对ns有限, ns可通过label selector选定)
items:
- hard:
limits.cpu: "8"
limits.memory: 16Gi
requests.cpu: "8"
requests.memory: 16Gi
pods: "10"
limitRanges: # 容器的limitrange,最终转换成ns的limitrange对象
items:
- limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 10Mi
type: Container
priorityClasses: # 限制该tenant下的所有pod的优先级
allowed:
- custom # 自定义的PriorityClass
allowedRegex: "^tier-.*$"
matchLabels:
env: "production"
nodeSelector: # 给tenant下的所有pod指定调度策略,当然也包含亲和及非亲和属性
pool: oil
kubernetes.io/os: linux
# ... 更多属性

tenant支持的属性非常多,这里不一一列出,可以看出,实现这些属性的机制是通过了策略引擎的方式

GlobalTenantResource

GlobalTenantResource被设计用于在选定的tenant对象中共享资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: capsule.clastix.io/v1beta2
kind: GlobalTenantResource
metadata:
name: fossil-pull-secrets
spec:
tenantSelector: # 通过label选择tenant, 只有tenant对象上具有该label才能继承resources中指定的对象
matchLabels:
energy: fossil
resyncPeriod: 60s # 资源是否已复制并同步的时间间隔
resources: # 指定需要共享的资源对象列表,同样,这里支持任一对象,包含CRD
- namespacedItems:
- apiVersion: v1
kind: Secret
namespace: harbor-system # 在harbor-system中带有tenant: fossil label的secret都将被共享
selector:
matchLabels:
tenant: fossil

由于GlobalTenantResource是群集范围内的资源,因此只有群集管理员才能操作该对象, 如果想在某一tenant中的所有ns共享资源,又如何实现呢?

TenantResource

TenantResource则是用于实现上述目的,tenant的Owner用户可以直接操作该对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: solar-db
namespace: solar-system
spec:
resyncPeriod: 60s # 资源是否已复制并同步的时间间隔
resources:
- namespaceSelector: # 选择namespace
matchLabels:
environment: production
rawItems: # 选择资源对象列表
- apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql
spec:
description: PostgreSQL cluster for the Solar project
instances: 3
postgresql:
pg_hba:
- hostssl app all all cert
primaryUpdateStrategy: unsupervised
storage:
size: 1Gi

有了这些属性,Tenant几乎可以实现任何的多租户需求
参考官网

身份验证

X509

官方给出的case是基于x509证书的方式实现的Owner身份认证,主要是hack/create-user.sh脚本,这里讲一下流程
首先通过openssl创建证书: key及csr

1
2
openssl genrsa -out ${USER}-${TENANT}.key 2048
openssl req -new -key ${USER}-${TENANT}.key -subj "/CN=${USER}${MERGED_GROUPS}" -out ${TMPDIR}/${USER}-${TENANT}.csr

重点关注-subj参数, CN表示common Name, USER是脚本传入的用户,即tenant的Owner中指定的用户, MERGED_GROUPS为默认的capsule.clastix.io,然后生成CertificateSigningRequest对象提交给集群,集群自动approve及签发相关证书并生成USER的kubeconfig文件.由于CN中已经传入了USER, 这个USER也属于capsule.clastix.io组, 正好与–capsule-user-group参数capsule.clastix.io(默认值)一致,因此在创建完成后, USER使用kubeconfig文件即可拥有Tenant Owner权限,整个流程跟正常搭建kubernetes证书的生成是一个道理。

但是生产中不可能一个一个手工去创建用户证书, 前面说过,Capsule不关心集群中使用的身份验证策略,并且支持所有Kubernetes身份验证方法
所以只要是支持了kubernetes的身份验证策略的系统或者是平台,都可以对接上capsule, 以下再以rancher为例进行说明

对接Rancher

rancher做为一款常用的集群管理平台, 支持对接多种账号体系,如AD/LDAP, 这里以LDAP为例(rancher如何配置AD不属于本篇内容,不再赘述),
这里简单说一下原理: 当使用LDAP账号登陆rancher时,rancher会从LDAP中验证是否为合法用户,如果是则将该用户保存在自身的CRD对象中,同时生成该用户的唯一ID,同时这个用户会有一个用户组,也由rancher生成。
rancher在与kubernetes集群交互时,使用kubernetes提供的user impersontion机制进行通信,将ldap中的用户信息(比如cn=zsk)封装成自身的用户机制(id=u-32ipck5o7k)去与kubernetes交互

上述机制很重要,所以这种情况下,在指定Tenant的Owener时 User应该是LDAP中的用户吗?答案是否定的,而应该要是rancher中的用户

rancher中的User对象信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: management.cattle.io/v3
description: ""
displayName: data
kind: User
metadata:
finalizers:
- controller.cattle.io/mgmt-auth-users-controller
cattle.io/creator: norman
name: u-32ipck5o7k
principalIds:
- openldap_user://CN=zsk,OU=xxx,OU=xxx,DC=xxx,DC=xxx # ldap相关的用户组
- local://u-32ipck5o7k # 每一个ldap用户都会对应rancher的一个本地用户

所以capsule正确的设置userGroups的配置文件应该是, 与–capsule-user-group参数是等价的:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: capsule-default
spec:
enableTLSReconciler: true
forceTenantPrefix: true
protectedNamespaceRegex: kube
userGroups:
- capsule.clastix.io # 这里保留默认的用户组
- openldap_group://CN=zsk,OU=yyy,DC=zzz,DC=com

openldap_group是rancher基于ldap机制时写入到kubernetes中用户对应的group信息,这个参数就是我们需要的. 在集群中查找某个用户的信息可使用以下方法:

1
2
kubectl get clusterrole 
# 查看以cattle-impersonation-u-32ipck5o7k(为例) 开头的对象, 其中32ipck5o7k可以在rancher UI中的用户管理中看到)

要注意的是,查询用户信息需要在rancher自身的集群中查询,而不是在rancher托管的集群

相关issue:

  1. how capsule integrate with rancher under openLDAP

GitOps

capsule的所有属性都可以定义在git中, 那么与gitops的衔接可谓是非常便捷, 如flux, argoCD等gitops工具, 这一块不再赘述
可参考官网的详细步聚flux2-capsule

capsule-proxy

这里也简单提一下capsule-proxy项目,从上面的实践可以看出,虽然capsule通过webhook机制可以将权限限定在特定的namespaces中,但还是会存在下列一类的问题

1
2
3
4
5
6
7
$ kubectl get namespaces
Error from server (Forbidden): namespaces is forbidden:
User "alice" cannot list resource "namespaces" in API group "" at the cluster scope

# 但是在get时又会提示有权限
$ kubectl auth can-i [get|list|watch|delete] ns oil-production
yes

原因是RBAC操作仅在Cluster-Scope中可用,并且不授予没有适当权限的用户。
为了解决这类问题,capsule引入了自己的解决方案, 即capsule-proxy.capsule-proxy实现了一个简单的反向代理,它只拦截对API服务器的特定请求,
然后capsule完成请求的转换、响应、返回数据,这样就可解决上述问题
相关的issue:

  1. can list all pod in all namespace under a tenant?
  2. Feature: additional pod metadata

问题

  1. 当Tenant Owner用户使用命令创建ns时: kubectl create ns zsk, 提示以下错误:

    1
    Error from server (You do not have any Tenant assigned: please, reach out to the system administrators): admission webhook "owner.namespace.capsule.clastix.io" denied the request: You do not have any Tenant assigned: please, reach out to the system administrators

    原因: 出现这个报错的原因是在tenant中的owners.name设置成了ldap中的cn(zsk), 会与CapsuleConfiguration中的userGroups对应不上, 导致通过userinfo无法找到绑定的tenant.
    解决: 修改为rancher中的用户组即可(openldap_group://CN=zsk,OU=yyy,DC=zzz,DC=com),参考对接Rancher一节

  2. 如何解决Tenant下ns的resourcequota自定义问题:

    解决: 由于需要为该租户下的每个ns设定resourcequota, tenant对象支持设置resourcequota会统一下发到所有ns, 但考虑到后续业务上会出现ns的resourcequota的大小不一, 因此resourcequota不能设置在tenant上,
    可以通过tenantresource这个对象将resourcequota进行传播,同时使用namespaceselector来指定传播到ns范围,
    tenant在创建ns时支持给ns添加label或者是annotation,因此可以与tenantresource中的namespaceselector对应起来,这样处理后就能实现tenant下的ns可以自定义resourcequota
    不是必须: 这里最好有一个单独的ns(如ns-robot)专门用于传播对象,这样可以放心使用namespaceSelector选择ns而不用当心会重复选择到tenantresource所在的ns.如果没有namespaceSelector则默认是该租户下的所有ns
    当resourcequota期间变小后, 不会对现在ns里的pod产生影响,但会出现已经使用的quota超过了ns的resourcequota的现象

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    apiVersion: capsule.clastix.io/v1beta2
    kind: TenantResource
    metadata:
    name: tnt-default-rq
    namespace: notebook
    spec:
    resyncPeriod: 120s
    resources:
    - namespaceSelector:
    matchLabels:
    tenant-rq.k2.com/use-default: enable
    rawItems:
    - apiVersion: v1
    kind: ResourceQuota
    metadata:
    name: tnt-default-rq-notebook
    spec:
    hard:
    limits.cpu: "120"
    limits.memory: "928Gi"
    requests.cpu: "120"
    requests.memory: "928Gi"

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?