本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
租户隔离
当我们想到多租户时,我们通常希望将用户或应用程序与其他用户或在共享基础架构上运行的应用程序隔离开来。
Kubernetes 是一个单租户协调器,即控制平面的单个实例由集群内的所有租户共享。但是,您可以使用各种 Kubernetes 对象来创建多租户的外观。例如,可以实现命名空间和Role-based 访问控制 (RBAC) 以在逻辑上将租户彼此隔离。同样,配额和限制范围可用于控制每个租户可以消耗的集群资源量。尽管如此,该集群是唯一提供强大安全边界的结构。这是因为设法获得对集群内主机的访问权限的攻击者可以检索安装在该主机上的所有 S ecret 和卷。ConfigMaps他们还可以模仿 Kubelet,这将允许他们操纵节点在集群内横 and/or 向移动的属性。
以下各节将解释如何实现租户隔离,同时降低使用 Kubernetes 等单一租户协调器的风险。
软多租户
在软多租户中,您可以使用原生 Kubernetes 结构,例如命名空间、角色和角色绑定以及网络策略,在租户之间建立逻辑分离。例如,RBAC 可以阻止租户访问或操纵彼此的资源。配额和限制范围控制每个租户可以消耗的集群资源量,而网络策略可以帮助防止部署在不同命名空间中的应用程序相互通信。
但是,这些控件都无法阻止来自不同租户的 Pod 共享节点。如果需要更强的隔离,你可以使用节点选择器、反关联性规则、 and/or 污点和容忍度来强制将来自不同租户的 Pod 调度到不同的节点上;通常称为独租户节点。 在租户众多的环境中,这可能会变得相当复杂,而且成本高得令人望而却步。
重要
使用命名空间实现的软多租户不允许您向租户提供经过筛选的命名空间列表,因为命名空间是全局范围的类型。如果租户能够查看特定的命名空间,则可以查看集群中的所有命名空间。
警告
借助软多租户,默认情况下,租户保留在 CoreDNS 中查询集群内运行的所有服务的能力。攻击者可以通过
..svc.cluster.local从集群中的任何 pod 运行 dig SRV 来利用此漏洞。如果您需要限制对集群内运行服务的 DNS 记录的访问权限,请考虑使用 CoreDNS 的防火墙或策略插件。有关更多信息,请参阅 https://github.com/coredns/policy #kubernetes-metadata-multi-tenancy-policy。
Kiosk
-
账户和账户用户在共享 Kubernetes 集群中分隔租户
-
Self-Service 为账户用户配置命名空间
-
账户限制,用于确保共享集群时的服务质量和公平性
-
用于安全租户隔离和自助命名空间初始化的命名空间模板
Loft
-
Multi-cluster 访问权限用于授予对不同集群中空间的访问权限
-
在不活动期间,睡眠模式会缩小空间的部署规模
-
使用 OIDC 身份验证提供商进行单点登录,例如 GitHub
软多租户可以解决三个主要用例。
企业设置
第一种是在企业环境中,“租户” 是半信任的,因为他们是员工、承包商或获得组织的其他授权。每个租户通常会与一个行政部门(例如部门或团队)保持一致。
在这种类型的设置中,集群管理员通常负责创建命名空间和管理策略。他们还可以实现委托管理模型,让某些人监督命名空间,允许他们对非策略相关的对象(如部署、服务、容器、任务等)执行 CRUD 操作。
在此设置中,容器运行时提供的隔离可能是可以接受的,或者可能需要通过额外的控制来增强 Pod 安全。如果需要更严格的隔离,可能还需要限制不同命名空间中的服务之间的通信。
Kubernetes 即服务
相比之下,软多租户可用于您想要提供 Kubernetes 即服务 (KaaS) 的设置。使用 KaaS,您的应用程序与一系列提供一组 PaaS 服务的控制器和 CRD 一起托管在共享集群中。租户直接与 Kubernetes API 服务器进行交互,并被允许对非策略对象执行 CRUD 操作。还有一个自助服务要素,即可以允许租户创建和管理自己的命名空间。在这种类型的环境中,假定租户正在运行不可信的代码。
要在这种环境中隔离租户,你可能需要实施严格的网络策略以及 pod 沙箱。沙箱是在 Firecracker 等微型虚拟机或用户空间内核中运行 Pod 容器的地方。现在,你可以使用 EKS Fargate 创建沙盒吊舱。
软件即服务 (SaaS)
软多租户的最终用例是 Software-as-a-Service (SaaS) 设置。在此环境中,每个租户都与集群内运行的应用程序的特定实例相关联。每个实例通常都有自己的数据,并使用单独的访问控制,这些控制通常独立于 Kubernetes RBAC。
与其他用例不同,SaaS设置中的租户不直接与Kubernetes API交互。相反,SaaS应用程序负责与Kubernetes API接口,以创建支持每个租户所需的对象。
Kubernetes 构造
在所有这些实例中,以下结构都用于将租户彼此隔离:
命名空间
命名空间是实现软多租户的基础。它们允许您将群集划分为逻辑分区。实现多租户所需的配额、网络策略、服务帐号和其他对象的范围限定为命名空间。
网络策略
默认情况下,允许 Kubernetes 集群中的所有 Pod 相互通信。可以使用网络策略来改变这种行为。
网络策略使用标签或 IP 地址范围限制 Pod 之间的通信。在需要在租户之间进行严格网络隔离的多租户环境中,我们建议从拒绝 pod 之间通信的默认规则开始,以及另一条允许所有 pod 向 DNS 服务器查询名称解析的规则。有了这个,你就可以开始添加更宽松的规则,允许在命名空间内进行通信。这可以根据需要进一步完善。
注意
亚马逊 VPC CNI 现在支持 Kubernetes 网络策略,
重要
网络策略是必要的,但还不够。网络策略的实施需要诸如 Calico 或 Cilium 之类的策略引擎。
Role-based 访问控制 (RBAC)
角色和角色绑定是用于在 Kubernetes 中强制执行基于角色的访问控制 (RBAC) 的 Kubernetes 对象。角色包含可以对集群中的对象执行的操作列表。角色绑定指定角色所适用的个人或群组。在企业和 KaaS 设置中,RBAC 可用于允许选定的团体或个人管理对象。
配额
配额用于定义集群中托管的工作负载的限制。使用配额,您可以限制命名空间内可消耗的 CPU 和内存总量,或限制可以创建的对象数量。限制范围允许您声明命名空间内单个 Pod 和容器的最小、最大和默认 CPU 和内存值。
在共享集群中过度使用资源通常是有益的,因为它允许您最大限度地利用资源。但是,对集群的无限访问可能会导致资源短缺,从而导致性能下降和应用程序可用性损失。如果 Pod 的请求设置得太低,并且实际资源利用率超过节点的容量,则该节点将开始承受 CPU 或内存压力。发生这种情况时,Pod 可能会被重新启动并 and/or 逐出节点。
为了防止这种情况发生,你应该计划对多租户环境中的命名空间施加配额,以强制租户在集群上调度 pod 时指定请求和限制。它还将通过限制 pod 可以消耗的资源量来缓解潜在的拒绝服务。
您还可以使用配额来分配集群的资源,以使其与租户的支出保持一致。这在 KaaS 场景中特别有用。
Pod 优先级和抢占权
当你想赋予一个 Pod 相对于其他 Pod 更重要时,Pod 优先级和抢占非常有用。例如,使用 pod 优先级,您可以将客户 A 的 pod 配置为以比客户 B 更高的优先级运行。当可用容量不足时,调度程序将从客户 B 驱逐优先级较低的 Pod 以容纳客户 A 的优先级较高的 Pod,这在愿意支付溢价的客户获得更高优先级的 SaaS 环境中尤其方便。
重要
Pod 优先级可能会对其他优先级较低的 Pod 产生不希望的影响。例如,尽管受害 Pod 可以正常终止但无法保证,这可能会破坏依赖于 Pod 法定数量的优先级较低的应用程序,请参阅抢占的限制。 PodDisruptionBudget
缓解控制
作为多租户环境的管理员,你最关心的是阻止攻击者获得对底层主机的访问权限。应考虑以下控制措施来减轻这种风险:
容器的沙盒执行环境
沙箱是一种技术,通过该技术,每个容器都在自己的独立虚拟机中运行。执行 pod 沙箱的技术包括 Fi
有关努力使 Firecracker 成为 EKS 支持的运行时的更多信息,请参阅 https://threadreaderapp.com/thread/1238496944684597248.html.
开放策略代理 (OPA) & Gatekeeper
Gatekeeper
还有一个用于CoreDNS的实验性 OPA插件
Kyverno
Kyverno
你可以使用 Kyverno 来隔离命名空间,强制执行 pod 安全和其他最佳实践,并生成网络策略等默认配置。该项目的 GitHub存储库
将租户工作负载隔离到特定节点
限制租户工作负载在特定节点上运行可用于增强软多租户模型中的隔离性。使用这种方法,租户特定的工作负载只能在为相应租户配置的节点上运行。为了实现这种隔离,使用原生 Kubernetes 属性(节点亲和力、污点和容忍度)将特定节点作为容器调度的目标,并防止来自其他租户的 pod 在租户特定的节点上调度。
第 1 部分-节点亲和力
Kubernetes 节点亲和性requiredDuringSchedulingIgnoredDuringExecution节点亲和力应用于相应的 pod。结果是,Pod 将瞄准标有以下内容的节点 key/value:node-restriction.kubernetes.io/tenant: tenants-x。
... spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-restriction.kubernetes.io/tenant operator: In values: - tenants-x ...
有了这种节点亲和力,在调度期间需要标签,但在执行期间不需要;如果底层节点的标签发生变化,则不会仅因为标签更改而驱逐 pod。但是,未来的日程安排可能会受到影响。
警告
的标签前缀在 Kubernetes 中node-restriction.kubernetes.io/具有特殊的含义。NodeRestrictionkubelet防止 adding/removing /更新带有此前缀的标签。攻击者无法使用kubelet不允许修改这些标签。kubelet’s credentials to update the node object or modify the system setup to pass these labels into `kubelet如果将此前缀用于所有 Pod 到节点的调度,则可以防止攻击者可能希望通过修改节点标签将一组不同的工作负载吸引到节点的情况。
例
我们可以使用节点选择器来代替
第 2 部分-污点和容忍
将 Pod 吸引到节点只是这种由三部分组成的方法的第一部分。为了使这种方法起作用,我们必须阻止 pod 调度到未获授权的节点上。为了击退不需要的或未经授权的 Pod,Kubernetes 使用节点污点。https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/tenant: tenants-x
... taints: - key: tenant value: tenants-x effect: NoSchedule ...
鉴于上述节点taint,只有容忍污点的 Pod 才允许在节点上调度。要允许将授权的 pod 调度到节点上,相应的 pod 规格必须包含 to toleration the taint,如下所示。
... tolerations: - effect: NoSchedule key: tenant operator: Equal value: tenants-x ...
具有上述内容的 Pod toleration 不会被阻止在节点上进行调度,至少不是因为那个特定的污点。在某些情况下,例如节点资源压力,Kubernetes 还使用污点来暂时停止容器调度。通过节点亲和力、污点和容忍度,我们可以有效地将所需的 Pod 吸引到特定的节点并击退不需要的 Pod。
重要
某些 Kubernetes Pod 需要在所有节点上运行。这些 pod 的例子是那些由容器网络接口 (CNI)
第 3 部分-节点选择 Policy-based 管理
有几种工具可以用来帮助管理 Pod 规范的节点亲和力和容忍度,包括在 CICD 管道中强制执行规则。但是,还应在 Kubernetes 集群级别强制隔离。为此,可以使用策略管理工具根据请求有效负载改变入站 Kubernetes API 服务器请求,以应用上述相应的节点关联性规则和容忍度。
例如,可以为发往 tenants-x 命名空间的 Pod 加上正确的节点亲和力和容忍度,以允许在 tenants-x 节点上进行调度。 利用使用 Kubernetes 变更准入 Webhook 配置的策略管理工具
apiVersion: mutations.gatekeeper.sh/v1alpha1 kind: Assign metadata: name: mutator-add-nodeaffinity-pod annotations: aws-eks-best-practices/description: >- Adds Node affinity - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity spec: applyTo: - groups: [""] kinds: ["Pod"] versions: ["v1"] match: namespaces: ["tenants-x"] location: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms" parameters: assign: value: - matchExpressions: - key: "tenant" operator: In values: - "tenants-x"
上述政策适用于 Kubernetes API 服务器请求,将 pod 应用于 tenants-x 命名空间。 该策略添加了requiredDuringSchedulingIgnoredDuringExecution节点关联性规则,以便 Pod 被带有tenant: tenants-x标签的节点所吸引。
第二项策略(如下所示)使用相同的目标命名空间和群组、种类和版本的匹配标准,将容忍度添加到相同的 pod 规格中。
apiVersion: mutations.gatekeeper.sh/v1alpha1 kind: Assign metadata: name: mutator-add-toleration-pod annotations: aws-eks-best-practices/description: >- Adds toleration - https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ spec: applyTo: - groups: [""] kinds: ["Pod"] versions: ["v1"] match: namespaces: ["tenants-x"] location: "spec.tolerations" parameters: assign: value: - key: "tenant" operator: "Equal" value: "tenants-x" effect: "NoSchedule"
上述策略特定于 pod;这是由于策略location元素中变异元素的路径所致。可以编写其他策略来处理创建 pod 的资源,例如部署和任务资源。列出的政策和其他示例可以在本指南的配套GitHub项目
这两个突变的结果是,Pod 被吸引到所需的节点,同时不会被特定的节点污点击退。为了验证这一点,我们可以看到两次kubectl调用的输出片段,以获取节点的标签tenant=tenants-x,并获取命名空间中的 tenants-x pod。
kubectl get nodes -l tenant=tenants-x NAME ip-10-0-11-255... ip-10-0-28-81... ip-10-0-43-107... kubectl -n tenants-x get pods -owide NAME READY STATUS RESTARTS AGE IP NODE tenant-test-deploy-58b895ff87-2q7xw 1/1 Running 0 13s 10.0.42.143 ip-10-0-43-107... tenant-test-deploy-58b895ff87-9b6hg 1/1 Running 0 13s 10.0.18.145 ip-10-0-28-81... tenant-test-deploy-58b895ff87-nxvw5 1/1 Running 0 13s 10.0.30.117 ip-10-0-28-81... tenant-test-deploy-58b895ff87-vw796 1/1 Running 0 13s 10.0.3.113 ip-10-0-11-255... tenant-test-pod 1/1 Running 0 13s 10.0.35.83 ip-10-0-43-107...
从上面的输出中我们可以看出,所有的 pod 都调度在标有标签的节点上tenant=tenants-x。简而言之,Pod 只能在所需的节点上运行,而其他 Pod(没有所需的亲和力和容忍度)不会。租户工作负载被有效隔离。
变异后的 pod 规格示例如下所示。
apiVersion: v1 kind: Pod metadata: name: tenant-test-pod namespace: tenants-x spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: tenant operator: In values: - tenants-x ... tolerations: - effect: NoSchedule key: tenant operator: Equal value: tenants-x ...
重要
Policy-management 集成到 Kubernetes API 服务器请求流的工具,使用变更和验证准入网络挂钩,旨在在指定的时间范围内响应 API 服务器的请求。这通常是 3 秒或更短。如果 webhook 调用未能在配置的时间内返回响应,则可能会也可能不会对入站 API 服务器请求 and/or 进行突变验证。此行为取决于准入 webhook 配置是设置为 “失效开放” 还是 “失败关闭
在上面的示例中,我们使用了为编写的策略 OPA/Gatekeeper。但是,还有其他策略管理工具可以处理我们的节点选择用例。例如,这个 Kyverno 策略
注意
如果操作正常,更改策略将对入站 API 服务器请求有效负载产生所需的更改。但是,在允许更改持续存在之前,还应包括验证策略,以验证所需的更改是否已发生。当使用这些策略进行租户到节点的隔离时,这一点尤其重要。加入审计策略来定期检查集群中是否有不需要的配置也是一个好主意。
参考
-
k-rail
旨在通过执行某些政策来帮助您保护多租户环境。
硬性多租户
硬多租户可以通过为每个租户配置单独的集群来实现。尽管这为租户之间提供了非常牢固的隔离,但它有一些缺点。
首先,当你有很多租户时,这种方法很快就会变得昂贵。您不仅需要为每个集群支付控制平面成本,还无法在集群之间共享计算资源。这最终会导致分散,即集群的一部分未得到充分利用,而其他集群却被过度利用。
其次,您可能需要购买或构建特殊工具来管理所有这些集群。随着时间的推移,管理成百上千个集群可能会变得过于笨拙。
最后,与创建命名空间相比,为每个租户创建集群会很慢。尽管如此,在高度监管的行业或需要严格隔离的SaaS环境中,可能需要采取硬租赁方法。
未来方向
Kubernetes 社区已经认识到软多租户当前的缺点以及硬多租户面临的挑战。Multi-Tenancy 特别兴趣小组(SIG)
HNC 提案 (KEP) 描述了一种通过 [策略] 对象继承以及租户管理员创建子命名空间的能力在命名空间之间创建父子关系的方法。
虚拟集群提案描述了一种机制,用于为集群中的每个租户(也称为 “Kubernetes on Kubernetes”)创建控制平面服务的单独实例,包括 API 服务器、控制器管理器和调度程序。
Multi-Tenancy基准测试