k8s 多租户方案

2024/01/26 k8s multi-tenancy 共 10798 字,约 31 分钟

目录

0. 背景和目标

背景

  • 业界的需求

多租户的运行机制意味着不同类型的工作负载(如:deploy/sts/rs/crd等)或者团队可以共享同一套集群中的资源(如:节点、网络、存储、容器运行时等),并且工作负载与团队之间还需要实现资源的逻辑隔离或者物理隔离,在资源利用率要求较高的地方,甚至还会要求不同租户间打破隔离,实现按资源按规则(如:指定节点比例等)共享。

  • 厂内的需求

当前用户区每申请一个智算环境,都需要创建一个孤立的 K8S 集群,K8S 集群运行所必须的控制面的服务需要同步部署。从用户角度来看,这些服务是无形的成本,这些节点通常不会用来部署用户的训推任务;从资源供应方来看,每个集群就相当于资源孤岛,不利于提高资源的售卖比,在多集群管理及资源池化方面也有不小的挑战。

目标

  • 做什么:本文是针对 K8S 实现多租户方案的调研,目的是针对 K8S 平台,理清有哪些方案可以实现多租户,同时对友商多租户的实现方案做调研对比。
  • 不做什么:K8S 多集群

1. 概述

企业组织在使用 K8S 时,为简化运维以及降低资源成本,通常组织内各个部门、团队需要共享使用 K8S 集群。

K8S 通过适当的配置,可以允许集群管理员创建一个共享基础设施,在其中多个租户可以在单个 K8S 环境中运行独立的工作负载,管理员根据租户的需求划分基础设施。

什么是租户? 租户可以简单理解为拥有一些资源(计算、网络、存储)、工作负载及用户/团队/组织的逻辑实体,租户管理员可以控制和管理集群的安全性和租户间的隔离性,用户可以共享底层集群的基础设施。

什么是租户

单多租户的区别?

二者对比请看下图:

diff

多租户的场景分类?

  • 场景1:多团队租户(Multi-team tenancy
    • 情景: 具有多个团队聚焦在不同项目或应用的组织。
    • 用例: 有效地利用少量集中管理的 K8S 集群,为各个团队提供各自隔离的环境。

      multi-team tenancy

  • 场景2:多环境租户(Multi-environment tenancy
    • 情景: 应用或服务的开发、测试和生产阶段。
    • 用例: 在共享的集群中创建灵活的分段、测试和生产环境。这通过允许开发人员在模拟生产设置的环境中工作,同时减少管理每个阶段的独立集群的开销,提高了开发人员的生产力。
  • 场景3:多客户租户(Multi-customer tenancy
    • 情景: 为多个客户提供软件即服务(SaaS)的提供商。
    • 用例: SaaS 提供商通过共享的集群基础设施实现可伸缩性和成本节省。每个客户的数据和应用在相同的集群中被逻辑隔离,提供了效率和成本效益。

      multi-customer-tenant

多租户使用场景示例

所有用户都来自企业,可以通过命名空间对不同部门或团队进行资源的逻辑隔离:

  • 集群管理员:
    • 具有集群的管理能力(扩缩容、添加节点等操作)
    • 负责为租户管理员创建和分配命名空间
    • 负责各类策略(RAM/RBAC/networkpolicy/quota…)的CRUD
  • 租户管理员
    • 至少具有集群的 RAM 只读权限
    • 管理租户内相关人员的 RBAC 配置
  • 租户内用户
    • 在租户对应命名空间内使用权限范围内的k8s资源

2. K8S 隔离能力

根据 K8S 集群的架构可知,K8S 由控制平面(controller-manager/api-server/scheduler等)和数据平面(node等)组成, 租户工作负载最终以 Pod 的形式运行在各工作节点。

由此,多租户的实现可以从两个方面入手,即:

  • 通过控制面实现
  • 通过数据面实现

基于上述两个方面,设计和构建多租户的方案会有多种。每种方法都有自己的一组权衡,其中隔离性是首要考虑的事情,另外,基于不同程度的隔离实现,不同方案的工作量、操作复杂性和服务成本都会有差异。

下面从控制面和数据面出发,看一下 K8S 中如何实现不同程度的隔离。

2.1 基于控制面隔离

2.1.1 基于 Namespace 隔离

基于命名空间(namespace)的多租户是 K8S 中最简单的多租户实现方式。每个租户分配一个 namespace,租户内的资源都位于该 namespace 中,命名空间之间相互隔离,租户之间无法互相访问对方的资源,通过网络策略可以控制不同租户间的服务访问。

基于命名空间的多租户实现方案的优点是简单易用,不需要额外的配置或扩展。

同时缺点也是很明显的:

  • 租户之间的资源隔离不够彻底。例如,租户内的 Pod 可以通过 Service 访问其他租户的资源。
  • 租户的管理和控制权限不够灵活。例如,管理员无法为每个租户单独设置资源配额。
  • 粒度划分的局限性比较大。例如,单个租户或应用团队再划分,这种细粒度级别几乎无法完成,这增加了管理难度。
  • 隔离范围受限。例如:ds、storeageclass、pv 等这种属于集群范围的的资源不方便管理。

2.1.2 基于 RBAC 隔离

受 ns 控制,基于角色的多租户是 K8S 中功能最强大的多租户实现方案。基于角色的多租户使用角色来定义租户的权限,租户管理员可以通过角色来控制用户访问 K8S 集群中的资源。

基于角色的多租户实现方案的优点是原生支持,租户需要根据自己的需求来定义角色,并将角色授予用户,使用基于角色的访问控制(RBAC)来定义集群内每个用户和用户组的权限和角色,RBAC 可以帮助强制最小特权原则,并防止未经授权的操作或对集群资源的访问。

缺点是配置复杂,它不能防止人为错误或配置错误。

rbac

2.1.3 基于资源配额隔离

受 ns 控制,通过资源配额,可以限制每个命名空间或租户可以使用的资源量,如 CPU、内存、存储和其资源的数量等,这有助于防止单一个租户占用过多的集群资源,导致资源过度分配和集群资源的匮乏影响其他租户,另外,也可以开发 Operator,使用 CRD 定义自定义资源,以适应特定的多租户需求。例如,可以定义租户资源对象,并在其上应用 RBAC 规则和网络策略。

缺点是不能保证每个租户的性能或可用性。

2.1.4 基于虚拟控制面隔离

K8S SIG 提供了虚拟集群的概念,允许在同一物理集群中创建多个虚拟集群。这些虚拟集群可以被不同的租户拥有,每个租户提供单独的虚拟控制面,以便完全隔离租户的资源。虚拟控制面通常是通过为每个租户运行一组单独的 apiserver 来实现的,同时使用控制器将资源从租户 apiserver 同步到原有的 K8S 集群。

每个租户只能访问其相应的 apiserver,而原始 K8S 集群的 apiserver 通常不能从外部访问。虚拟控制面的完全隔离通常需要较高的资源消耗,但与数据面隔离技术结合使用,它是一个更彻底和安全的多租户解决方案。

针对虚拟集群,其中一个例子是 vcluster 项目,其架构如下:

vcluster

它通过为每个租户提供一个虚拟控制面来实现租户之间的隔离,虚拟控制面通常由 K8S apiserver、控制器和 etcd 组成。

这种方案的优点是租户之间的隔离更加彻底,租户的管理和控制权限也更加灵活。但是,它也存在一些缺点,例如:

  • 需要额外的配置和扩展,例如,需要为每个租户单独部署一个虚拟控制面。
  • 成本更高,例如,需要额外的计算资源和存储资源。

2.2 基于数据面隔离

2.2.1 基于网络策略隔离

借助支持 NetworkPolicy 的 CNI 实现网络流量的隔离,有助于进一步隔离不同租户的网络流量,增强安全性;或者也可以使用 service mesh (istio/linkerd)提供的 7 层网络策略实现网络访问控制。

2.2.2 存储隔离

存储的隔离应保证 volume 不会跨租户访问。由于 StorageClass 是集群范围的资源,为防止 PV 被跨租户访问,应指定其 reclaimPolicy 为 Delete。此外,也应禁止使用例如 hostPath 这样的 volume,以避免节点的本地存储被滥用。

2.2.3 节点隔离

节点隔离可以使用将 Pod 指派给节点或 Virtual Kubelet 来实现。

使用多集群管理工具,可以将多个独立的 K8S 集群管理为一个整体。这种方法可以在不同的集群之间实现多租户隔离。

2.2.4 安全容器隔离

由于容器和宿主机共享内核,应用程序或者主机系统上的漏洞可能被攻击者所利用,从而突破容器边界而攻击到主机或者其他容器。解决方法通常是将容器放到一个隔离的环境中运行,例如虚拟机或者是用户态 kernel。前者以 Kata Containers 为代表,后者的代表则是 gVisor。

2.3 基于数据面和控制面隔离

从上面的讨论可以看出,共享 K8S 集群不是一件容易的事情,单纯的做数据面隔离或控制面隔离都是无法完全做的彻底隔离的;多租户不是 K8S 的内置特性,只能在其他项目的支持下在控制面和数据面上的租户隔离中实现,这为整个解决方案带来了相当大的学习和适应成本。因此,越来越多的用户转而采用多集群解决方案(关于多集群方案的实现,社区有专门的 SIG 讨论)。

常见的多集群方案有:

与集群共享解决方案相比,多集群解决方案有利有弊,优点是隔离程度高、边界清晰,缺点是开销和运维成本高。由于每个集群都需要独立的控制面和工作节点,K8S 集群通常构建在虚拟机上,以提高物理集群的利用率。然而,传统的虚拟化产品往往又大又重,因为它们需要处理广泛的场景。这使得它们过于昂贵,无法成为支持虚拟化Kubernetes集群的最佳选择。

3. 多租户实现方案

通过对多租户定义的理解及 K8S 隔离能力的分析,很明显的可以看出,为多租户共享 K8S 集群可以有以下几种方案:

  • 基于虚拟化控制平面实现多租户
  • 基于命名空间实现多租户
  • 基于多集群实现多租户(不在这里讨论)

在进行方案选型时,要考虑以下几个方面

  1. 功能需求:
    • 考虑租户是否需要访问完整集群以部署全局 K8S 对象,还是命名空间级别的隔离已经足够。
    • 多集群/虚拟集群是可以让租户部署全局对象(如自定义资源定义(CRD))的合适解决方案,使其能够灵活地利用整个集群的功能而不影响其他租户,同时。
  2. 安全能力:
    • 确定租户的工作负载是否会影响整个集群还是仅限于其命名空间。
    • 多集群/虚拟集群提供比基于命名空间的方法更强大的隔离和安全边界,适用于部署集群范围资源的关键任务应用程序。
  3. 实现成本:
    • 考虑每种多租户策略的成本、复杂性和运营开销。
    • K8S 多租户实现方案的选择需要根据实际需求来决定。如果对隔离性要求不高,可以选择基于命名空间的多租户。如果对隔离性要求较高,可以选择基于角色的多租户。

3.1 每个租户独立的虚拟控制面

这种多租户模型会为每个租户提供专用虚拟控制面, 从而完全控制集群范围的资源和附加服务。 工作节点在所有租户之间共享,并由租户通常无法访问的 host 集群管理。 由于租户的控制面不直接与底层计算资源相关联,因此它被称为虚拟控制平面.

虚拟控制面/虚拟集群(VirtualCluster)它代表一种新的架构,用于解决各种 K8S 控制平面隔离挑战。它通过为每个租户提供一个集群视图来扩展现有基于命名空间的 K8S 多租户模型。vc 完全利用了 K8S 的可扩展性,并保持了完整的API兼容性。

使用 vc,每个租户被分配一个专用的租户控制平面,租户可以在租户控制平面中创建集群范围的资源,例如命名空间和 CRD,而不会影响其他租户。因此,由于共享一个 apiserver 而导致的大多数隔离问题消失了。实际管理物理节点的 K8S 集群被称为 host 集群,现在成为 Pod 资源提供者。

vc 由以下组件组成:

  1. vc-manager(虚拟集群管理器):引入了一个新的CRD(Custom Resource Definition)VirtualCluster,用于建模租户控制平面。vc-manager 管理每个 vc 自定义资源的生命周期。根据规范,它要么在本地K8s集群中创建 CAPN(Custom API Server Node)控制平面Pods,要么在提供有效 kubeconfig 的情况下导入现有的集群。
  2. syncer(同步器):一个中心化的控制器,从每个租户控制平面填充用于 Pod 提供的 API 对象到host 集群,并双向同步对象状态。它还定期扫描同步的对象,以确保租户控制平面和超级集群之间的状态一致。
  3. vn-agent(虚拟节点代理):一个节点守护程序,代理所有租户 kubelet API 请求到运行在节点上的 kubelet 进程。它确保每个租户只能访问本节点上的自己的 Pods。

通过这一思路实现的提供多租户的能力项目有

3.1.1 vcluster - 架构图

vcluster arch

含义解析:

  • 一个虚拟集群对应物理集群中一个命名空间
  • 虚拟集群的控制面是通过物理集群的服务暴露的
  • 虚拟集群通过 syncer 将指定类型的资源向物理集群做同步
  • Service 与 Pod 的关系的绑定是在物理集群中做的,而非虚拟集群中
  • 工作负载是否向物理集群做同步是有区分的
  • 网络策略是在物理集群中实现

名词解释:

名称解释
Host Cluster 
底层 K8S 物理集群,真正提供 node 节点等资源 
host-namespacehost 集群中的命名空间,在不同的命名空间中运行着不同 vc 中创建的 pod/service 等工作负责和资源
vcluster-control-plane虚拟控制平面,允许用户将 host 集群的一部分资源(例如CPU、内存和存储)分配给单个vc,可以在 vc 级别实施访问策略、资源配额、k8s 版本、ingress 等,确保只有授权用户才能访问
High-Level = Purely Virtual所有由 vc 创建的资源对象(如:deployment, sts, rs, crd 等)都只存储在 vc 侧
Low-Level = Sync’d Resources需要同步给物理集群的资源
syncer完成资源从虚拟集群同步到物理集群

3.1.2 vcluster - Demo

  1. 创建 vcluster

1

  1. 切换到 vc 并部署 volcano 及 普通 deployment

2

3

  1. vc 中创建的 ns

Untitled

  1. 对配置了 ResourceQuota 的 vc, 验证超 quota 后的结果

Untitled

  1. 切换到 host 集群并查看工作负载

可以看到虚拟集群中的资源同步到物理集群后的对应关系

check1 check2 check3

3.1.3 vcluster - 工作原理

从上面的实验中可以看出,一个 vcluster 实际上只有一个 coredns + statefulset。其中 statefulset 实际上是启动了两个容器:一个容器中运行 k3s 服务,另一个容器中运行了 syncer(即图中中间部分)。

Init Containers:
  vcluster:
    Image:         rancher/k3s:v1.28.2-k3s1
    Command:
      /bin/sh
    Args:
      -c
      cp /bin/k3s /k3s-binary/k3s
Containers:
  syncer:
    Image:         ghcr.io/loft-sh/vcluster:0.18.1

这两个容器中,K3S 是与 K8S 完全兼容的简化的发行版,这里启动的 K3S 是一个裁剪版的 K3S, 诸如网络策略、调度的能力是没有启动的,在虚拟集群中创建的 pod 信息会通过 syncer 同步到物理集群中,在同步过程中,虚拟集群中创建的资源(如 pod)名称同步到物理集群时会被重写,即 vcluster 中的资源与物理集群中的资源是有映射关系的;pod 的调度是会交还到物理集群中去完成的。

  • 存在的问题:由于网络是透传的,如果有些资源中是通过注解去做资源绑定时,在虚拟集群中创建的资源,是可以使用到物理集群中的一些服务的; 无法在 vcluster 中指定端口号使用 NodePort。

  • https://github.com/clastix/kamaji

kamaji

3.1.4 不同方案对比

方案社区活跃度资源消耗灵活性支持的规模部署形式隔离性高可用文档支持调度器pv/storageclassAPI 兼容性
【推荐】vcluster高(3.8k star)-pod弱/软支持共用或独占管理集群提供
kamaji低(600+ star)100 个租户完成同步需要 7m30s. https://kamaji.clastix.io/reference/benchmark/pod强/硬支持独占虚拟集群提供

3.2 每个租户独立的命名空间

需要考虑的问题:

  • 层级组织与层级配额管理
  • 父子层级间策略的继承

使用命名空间实现多租户有两种思路:

  • 思路1: namespace + operator + ResourceQuota + kyverno

我们知道,K8S 原生的 namespace 是一种扁平的资源隔离手段,如果它具备分层能力,那就会比较容易组织成分成层的结构,定义父子命名空间,并在父子命名空间中复制资源。例如:把不同的命名空间组织成层次结构,并在不同 namespace 之间共享某些策略和资源。分层和隔离能力具备后,我们就可以开发自己 operator 用于管理命名空间生命周期和委托管理,并在相关命名空间之间共享资源配额,开源实现有 hnc/capsule/volcano 等。

  • 思路2: 协议转换

对租户的请求进行协议转换,使得每个租户看到的都是独占的 K8S 集群。对于后端集群来说,多个租户实际上是利用了 namespace 的原生隔离性机制而共享了同一个集群的资源, 这种较轻量的开源实现有 kubezoo。

3.2.1  Hierarchical Namespace Controller (HNC)

除了对命令空间有分层外,还支持对资源分层(hrq)

**[][jeffrey] ﴱ kind-kind() ~/vcluster_example [ 13GiB/16GiB]
[14:15:35] ❯ k api-resources | grep hnc
    61: hierarchicalresourcequotas        hrq                                             hnc.x-k8s.io/v1alpha2                  true         HierarchicalResourceQuota
    62: hierarchyconfigurations                                                           hnc.x-k8s.io/v1alpha2                  true         HierarchyConfiguration
    63: hncconfigurations                                                                 hnc.x-k8s.io/v1alpha2                  false        HNCConfiguration
    64: subnamespaceanchors               subns                                           hnc.x-k8s.io/v1alpha2                  true         SubnamespaceAnchor

[][jeffrey] ﴱ kind-kind() ~/vcluster_example [ 13GiB/16GiB]
[14:16:25] ❯ k get all -n hnc-system
NAME                                          READY   STATUS    RESTARTS      AGE
pod/hnc-controller-manager-84cbc6d6b6-vvsdv   1/1     Running   1 (17m ago)   29m

NAME                                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/hnc-controller-manager-metrics-service   ClusterIP   10.96.155.237   <none>        8080/TCP   29m
service/hnc-webhook-service                      ClusterIP   10.96.143.11    <none>        443/TCP    29m

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hnc-controller-manager   1/1     1            1           29m

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/hnc-controller-manager-84cbc6d6b6   1         1         1       29m**

3.2.2 Capsule

https://github.com/clastix/capsule

与 HNC 思路类似,它是基于 HNC,在上层抽象出了 Tenant 的 CRD,Tentant 是多个命名空间的集合,用一个 Tenant 中,用户可以任意创建 ns 和其它资源;另外,他有一个集中式的策略引擎,用于保证每个 Tenant 都会各自的网络策略、安全策略、ResourceQuota 等。

架构图

Untitled

kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
  name: oil
spec:
  owners:
  - name: alice
    kind: User
  namespaceOptions:
    quota: 3
  resourceQuotas:
    scope: Tenant
    items:
    - hard:
        limits.cpu: "8"
        limits.memory: 16Gi
        requests.cpu: "8"
        requests.memory: 16Gi
    - hard:
        pods: "10"
  limitRanges:
    items:
    - limits:
      - default:
          cpu: 500m
          memory: 512Mi
        defaultRequest:
          cpu: 100m
          memory: 10Mi
        type: Container
EOF

benchmark

3.2.3 Kiosk

https://github.com/loft-sh/kiosk

多租户扩展项目,在控制面和数据平面上实现租户隔离来实现多租户。

在控制面上,kiosk 使用 RBAC 来限制租户对 K8S 资源的访问。每个租户都有一个独立的 RBAC 策略,该策略定义了该租户可以创建和管理的资源类型和权限;在数据平面上,kiosk 使用容器网络隔离来隔离不同租户的 Pod。每个租户都有一个独立的网络命名空间,该命名空间中的 Pod 只能与同一租户中的 Pod 通信。

架构图

kiosk

功能主要分为三部分:一块是和 account (即租户)相关的。另一个部署是 quota。第三部分模板。space 从属于 account,与原生的 ns 为 1:1 关系。

工作原理

kiosk

分为三层,最下面是标准 k8s API

3.2.4 KubeZoo

KaaS 或 CPaasS 的隔离方案引入了过多的额外开销,比如每个租户需要建立独立的控制面组件,这样就降低了资源利用率;同时大量租户集群的建立,也会带来运维方面的负担。

另外,无论是公有云还是私有云,都存在大量小租户并存的场景。在这些场景下,每个租户的资源需求量比较小,同时租户又希望在创建集群之后,能够立即使用集群。

kiosk

针对这种海量小租户并存的场景,我们就提出了一种轻量级的多租户方案——KubeZoo。它本质是作为一个网关服务,部署在 API Server 的前端。它会抓取所有来自租户的 API 请求,然后注入租户的相关信息,最后把请求转发给 API Server,同时也会处理 API Server 的响应,把响应再返回给租户。

3.2.5 不同方案对比

方案隔离性运维成本资源利用率API 兼容性集群创建效率高可用调度器
HNC       
capsule       
kiosk       
kubezoo       

3.3 其它方案

3.3.1 基于 volcano 队列实现多租户

3.4 不同实现方案的对比

方案实现方式优点缺点适用场景
【推荐】虚拟集群vcluster独立性高管理相对复杂大规模、需要高度隔离的多租户需求
命名空间隔离HNC简单易用灵活性相对较低小型、简单的多租户需求
RBAC 和网络策略灵活,细粒度控制管理复杂中等规模、复杂的多租户需求
多集群管理kamada硬隔离部署和管理复杂多集群、跨地域管理的多租户需求

4. 参考

Search

    Table of Contents