多租户

本页面概述了集群多租户的可用配置选项和最佳实践。

共享集群可以节省成本并简化管理。但是,共享集群也带来了诸如安全性、公平性和管理**“吵闹的邻居”**等挑战。

集群可以通过多种方式共享。在某些情况下,不同的应用程序可能在同一个集群中运行。在其他情况下,同一个应用程序的多个实例可能在同一个集群中运行,每个终端用户一个实例。所有这些类型的共享通常都用一个总称来描述:**多租户**。

虽然 Kubernetes 没有一流的终端用户或租户概念,但它提供了多种功能来帮助管理不同的租户需求。这些将在下面讨论。

用例

确定如何共享集群的第一步是了解你的用例,以便评估可用的模式和工具。通常,Kubernetes 集群中的多租户分为两大类,尽管也可能存在许多变体和混合模式。

多个团队

多租户的一种常见形式是在组织内多个团队之间共享一个集群,每个团队都可能操作一个或多个工作负载。这些工作负载通常需要在彼此之间以及与位于相同或不同集群上的其他工作负载之间进行通信。

在这种情况下,团队成员通常通过 `kubectl` 等工具直接访问 Kubernetes 资源,或者通过 GitOps 控制器或其他类型的发布自动化工具间接访问。不同团队成员之间通常存在一定程度的信任,但 Kubernetes 策略(如 RBAC、配额和网络策略)对于安全公平地共享集群至关重要。

多个客户

多租户的另一种主要形式通常涉及软件即服务(SaaS)供应商为其客户运行工作负载的多个实例。这种商业模式与这种部署风格密切相关,许多人称之为“SaaS 租户”。然而,一个更好的术语可能是“多客户租户”,因为 SaaS 供应商也可能使用其他部署模型,并且这种部署模型也可以在 SaaS 之外使用。

在这种情况下,客户无法访问集群;Kubernetes 在他们的视角下是不可见的,仅由供应商用于管理工作负载。成本优化通常是一个关键问题,Kubernetes 策略用于确保工作负载之间高度隔离。

术语

租户

在讨论 Kubernetes 中的多租户时,“租户”没有单一的定义。相反,租户的定义将根据讨论的是多团队还是多客户租户而异。

在多团队使用中,租户通常是一个团队,每个团队通常部署少量工作负载,其规模随服务的复杂性而定。然而,“团队”的定义本身可能模糊,因为团队可能被组织成更高级别的部门或细分为更小的团队。

相比之下,如果每个团队为每个新客户部署专用工作负载,他们使用的是多客户租户模型。在这种情况下,“租户”仅仅是一组共享单个工作负载的用户。这可能是一个完整的公司,也可能只是该公司的一个团队。

在许多情况下,同一个组织在不同上下文中使用“租户”的两种定义。例如,平台团队可能向多个内部“客户”提供安全工具和数据库等共享服务,SaaS 供应商也可能拥有多个团队共享一个开发集群。最后,混合架构也是可能的,例如 SaaS 提供商将每个客户的工作负载用于敏感数据,并结合多租户共享服务。

显示共存租户模型的集群

隔离

有几种方法可以使用 Kubernetes 设计和构建多租户解决方案。每种方法都有其自身的一系列权衡,影响隔离级别、实现工作量、操作复杂性和服务成本。

Kubernetes 集群由运行 Kubernetes 软件的**控制平面**和由执行租户工作负载的节点组成**数据平面**。租户隔离可以根据组织要求应用于控制平面和数据平面。

所提供的隔离级别有时用“硬”多租户(意味着强隔离)和“软”多租户(意味着弱隔离)等术语来描述。特别是,“硬”多租户通常用于描述租户之间互不信任的情况,通常从安全和资源共享的角度(例如,防范数据泄露或 DDoS 等攻击)。由于数据平面通常具有更大的攻击面,“硬”多租户通常需要额外注意隔离数据平面,尽管控制平面隔离也仍然至关重要。

然而,“硬”和“软”这两个术语常常令人困惑,因为没有适用于所有用户的单一固定定义。相反,“硬度”或“软度”最好理解为一个广阔的范围,有许多不同的技术可以根据你的需求在集群中维护不同类型的隔离。

在更极端的情况下,完全放弃任何集群级共享,并为每个租户分配其专用集群,甚至可能在虚拟机不被视为足够安全边界的情况下,在专用硬件上运行,这可能更容易或有必要。对于托管的 Kubernetes 集群,这可能更容易实现,因为创建和操作集群的开销至少部分由云提供商承担。必须权衡更强的租户隔离的收益与管理多个集群的成本和复杂性。多集群 SIG 负责处理这些类型的用例。

本页面的其余部分重点介绍用于共享 Kubernetes 集群的隔离技术。但是,即使你正在考虑专用集群,审查这些建议也可能很有价值,因为如果你的需求或能力发生变化,它将为你提供将来转向共享集群的灵活性。

控制平面隔离

控制平面隔离确保不同的租户不能访问或影响彼此的 Kubernetes API 资源。

命名空间

在 Kubernetes 中,命名空间提供了一种机制,用于在单个集群中隔离 API 资源组。这种隔离具有两个关键维度:

  1. 命名空间内的对象名称可以与其他命名空间中的名称重叠,类似于文件夹中的文件。这允许租户命名他们的资源,而无需考虑其他租户正在做什么。

  2. 许多 Kubernetes 安全策略都限定在命名空间范围内。例如,RBAC 角色和网络策略是命名空间范围的资源。使用 RBAC,用户和服务账号可以被限制在命名空间内。

在多租户环境中,命名空间有助于将租户的工作负载分段为逻辑上独立的管理单元。事实上,一种常见的做法是将每个工作负载隔离到其自己的命名空间中,即使多个工作负载由同一个租户操作。这确保每个工作负载都有其自己的身份,并且可以配置适当的安全策略。

命名空间隔离模型需要配置其他几个 Kubernetes 资源、网络插件,并遵循安全最佳实践,以正确隔离租户工作负载。这些考虑将在下面讨论。

访问控制

控制平面最重要的一种隔离类型是授权。如果团队或其工作负载可以访问或修改彼此的 API 资源,他们就可以更改或禁用所有其他类型的策略,从而抵消这些策略可能提供的任何保护。因此,至关重要的是要确保每个租户仅拥有其所需命名空间的适当访问权限,不多不少。这被称为“最小权限原则”。

基于角色的访问控制(RBAC)通常用于在 Kubernetes 控制平面中强制执行授权,适用于用户和工作负载(服务账号)。角色角色绑定是 Kubernetes 对象,它们在命名空间级别用于在应用程序中强制执行访问控制;类似的对象也用于授权访问集群级对象,尽管这些对象对于多租户集群不太有用。

在多团队环境中,必须使用 RBAC 限制租户对适当命名空间的访问,并确保集群范围的资源只能由集群管理员等特权用户访问或修改。

如果一个策略最终授予用户超出其所需权限,这很可能表明包含受影响资源的命名空间应该被重构为更细粒度的命名空间。命名空间管理工具可以通过对不同命名空间应用通用的 RBAC 策略来简化这些更细粒度命名空间的管理,同时在必要时仍然允许细粒度策略。

配额

Kubernetes 工作负载会消耗节点资源,例如 CPU 和内存。在多租户环境中,你可以使用资源配额来管理租户工作负载的资源使用。对于多个团队的用例,其中租户可以访问 Kubernetes API,你可以使用资源配额来限制租户可以创建的 API 资源数量(例如:Pod 数量或 ConfigMap 数量)。对对象数量的限制确保了公平性,旨在避免“吵闹的邻居”问题影响共享控制平面的其他租户。

资源配额是命名空间级别的对象。通过将租户映射到命名空间,集群管理员可以使用配额来确保租户不能垄断集群资源或使其控制平面过载。命名空间管理工具简化了配额的管理。此外,虽然 Kubernetes 配额仅适用于单个命名空间,但某些命名空间管理工具允许命名空间组共享配额,从而使管理员能够以比内置配额更少的精力获得更大的灵活性。

配额可防止单个租户消耗超出其分配份额的资源,从而最大限度地减少“吵闹的邻居”问题,即一个租户对其他租户工作负载的性能产生负面影响。

当你在命名空间上应用配额时,Kubernetes 要求你还为每个容器指定资源请求和限制。限制是容器可以消耗的资源量的上限。尝试消耗超出配置限制的资源的容器将根据资源类型被限流或终止。当资源请求设置低于限制时,每个容器都保证获得请求量,但工作负载之间仍可能存在一些潜在影响。

配额无法防止所有类型的资源共享,例如网络流量。节点隔离(下文描述)可能是解决此问题的更好方案。

数据平面隔离

数据平面隔离确保不同租户的 Pod 和工作负载充分隔离。

网络隔离

默认情况下,Kubernetes 集群中的所有 Pod 都允许相互通信,并且所有网络流量都是未加密的。这可能导致安全漏洞,即流量意外或恶意发送到非预期目的地,或被受损节点上的工作负载拦截。

Pod 间的通信可以使用网络策略进行控制,该策略使用命名空间标签或 IP 地址范围限制 Pod 间的通信。在需要租户间严格网络隔离的多租户环境中,建议首先使用拒绝 Pod 间通信的默认策略,并另外添加一条规则允许所有 Pod 查询 DNS 服务器进行名称解析。有了这样的默认策略,你就可以开始添加更宽松的规则,允许命名空间内的通信。此外,在网络策略定义中,如果需要在命名空间之间允许流量,建议不要使用空标签选择器 '{}' 作为 namespaceSelector 字段。该方案可以根据需要进一步完善。请注意,这仅适用于单个控制平面内的 Pod;属于不同虚拟控制平面的 Pod 无法通过 Kubernetes 网络相互通信。

命名空间管理工具可以简化默认或通用网络策略的创建。此外,其中一些工具允许你在整个集群中强制执行一致的命名空间标签集,确保它们是你策略的可靠基础。

更高级的网络隔离可以通过服务网格提供,除了命名空间之外,服务网格还提供基于工作负载身份的 OSI 第 7 层策略。这些更高级别的策略可以更容易地管理基于命名空间的多租户,特别是当多个命名空间专用于单个租户时。它们通常还通过双向 TLS 提供加密,即使在节点受损的情况下也能保护你的数据,并可在专用或虚拟集群之间工作。然而,它们在管理上可能要复杂得多,并且可能不适用于所有用户。

存储隔离

Kubernetes 提供了多种卷类型,可用作工作负载的持久存储。为了安全和数据隔离,建议使用动态卷配置,并应避免使用依赖节点资源的卷类型。

StorageClass 允许你根据服务质量级别、备份策略或集群管理员确定的自定义策略,描述集群提供的自定义存储“类”。

Pod 可以使用 PersistentVolumeClaim 请求存储。PersistentVolumeClaim 是一个命名空间资源,它允许隔离存储系统的部分并将它们专用于共享 Kubernetes 集群中的租户。但是,重要的是要注意 PersistentVolume 是一个集群范围的资源,并且其生命周期独立于工作负载和命名空间。

例如,你可以为每个租户配置单独的 StorageClass,并以此来加强隔离。如果 StorageClass 是共享的,则应设置回收策略为 Delete,以确保 PersistentVolume 不能在不同命名空间之间重复使用。

沙箱容器

Kubernetes Pod 由一个或多个在工作节点上执行的容器组成。容器利用操作系统级虚拟化,因此提供的隔离边界比利用基于硬件虚拟化的虚拟机弱。

在共享环境中,应用程序和系统层中未打补丁的漏洞可能被攻击者利用,导致容器逃逸和远程代码执行,从而获得对主机资源的访问权限。在某些应用程序中,例如内容管理系统 (CMS),客户可能被允许上传和执行不受信任的脚本或代码。在这两种情况下,都希望有进一步隔离和保护工作负载的强隔离机制。

沙箱提供了一种隔离在共享集群中运行的工作负载的方法。它通常涉及在单独的执行环境(例如虚拟机或用户空间内核)中运行每个 Pod。当你运行不受信任的代码(其中工作负载被假定为恶意)时,通常建议使用沙箱。这种隔离之所以必要,部分原因是容器是在共享内核上运行的进程;它们从底层主机挂载 `/sys` 和 `/proc` 等文件系统,使其不如在拥有自己内核的虚拟机上运行的应用程序安全。虽然可以使用 seccomp、AppArmor 和 SELinux 等控件来加强容器的安全性,但很难对在共享集群中运行的所有工作负载应用一套通用的规则。在沙箱环境中运行工作负载有助于保护主机免受容器逃逸,即攻击者利用漏洞获取对主机系统以及在该主机上运行的所有进程/文件的访问权限。

虚拟机和用户空间内核是两种流行的沙箱方法。

节点隔离

节点隔离是另一种可用于将租户工作负载彼此隔离的技术。通过节点隔离,一组节点专用于运行特定租户的 Pod,并且禁止租户 Pod 的混合。这种配置减少了“吵闹的邻居”问题,因为节点上运行的所有 Pod 都将属于单个租户。节点隔离泄露信息的风险略低,因为设法逃逸出容器的攻击者将只能访问该节点上挂载的容器和卷。

尽管来自不同租户的工作负载在不同的节点上运行,但重要的是要注意 kubelet 和(除非使用虚拟控制平面)API 服务仍然是共享服务。熟练的攻击者可能会利用分配给 kubelet 或在节点上运行的其他 Pod 的权限,在集群内部横向移动并访问在其他节点上运行的租户工作负载。如果这是一个主要问题,请考虑实施补偿控制,例如 seccomp、AppArmor 或 SELinux,或探索使用沙箱容器或为每个租户创建单独的集群。

从计费角度来看,节点隔离比沙盒容器更容易理解,因为你可以按节点而不是按 Pod 收费。它还具有更少的兼容性问题和性能问题,并且可能比沙盒容器更容易实现。例如,可以为每个租户的节点配置污点,以便只有具有相应容忍度的 Pod 才能在其上运行。然后,可以使用一个变更 Webhook 自动向部署到租户命名空间的 Pod 添加容忍度和节点亲和性,以便它们在为该租户指定的特定节点集上运行。

节点隔离可以使用Pod 节点选择器实现。

其他注意事项

本节讨论与多租户相关的其他 Kubernetes 构造和模式。

API 优先级和公平性

API 优先级和公平性是 Kubernetes 的一个功能,允许你为集群中运行的某些 Pod 分配优先级。当应用程序调用 Kubernetes API 时,API 服务器会评估分配给 Pod 的优先级。优先级较高的 Pod 的调用将在优先级较低的 Pod 之前完成。当争用很高时,优先级较低的调用可能会排队,直到服务器不那么繁忙,或者你可以拒绝这些请求。

在 SaaS 环境中,使用 API 优先级和公平性不会很常见,除非你允许客户运行与 Kubernetes API 交互的应用程序,例如控制器。

服务质量(QoS)

当你运行 SaaS 应用程序时,你可能希望能够为不同的租户提供不同服务质量 (QoS) 等级的服务。例如,你可能拥有免费增值服务,其性能保证和功能较少,而收费服务层则具有特定的性能保证。幸运的是,有一些 Kubernetes 构造可以帮助你在共享集群中实现这一点,包括网络 QoS、存储类以及 Pod 优先级和抢占。这些方法的目的是为租户提供他们所支付的服务质量。让我们从网络 QoS 开始。

通常,节点上的所有 Pod 共享一个网络接口。如果没有网络 QoS,一些 Pod 可能会以牺牲其他 Pod 为代价,不公平地占用可用带宽的份额。Kubernetes 带宽插件为网络创建了一个扩展资源,允许你使用 Kubernetes 资源构造(即请求/限制)通过 Linux tc 队列对 Pod 应用速率限制。请注意,根据网络插件文档,该插件被认为是实验性的,在生产环境中使用之前应进行彻底测试。

对于存储 QoS,你可能希望创建具有不同性能特征的不同存储类或配置文件。每个存储配置文件都可以与针对不同工作负载(如 IO、冗余或吞吐量)优化的不同服务层相关联。可能需要额外的逻辑来允许租户将适当的存储配置文件与其工作负载相关联。

最后是Pod 优先级和抢占,你可以为 Pod 分配优先级值。当调度 Pod 时,如果资源不足以调度分配了更高优先级的 Pod,调度器将尝试驱逐优先级较低的 Pod。如果你的用例中租户在共享集群中具有不同的服务层级(例如免费和付费),你可能希望使用此功能为某些层级赋予更高的优先级。

DNS

Kubernetes 集群包含一个域名系统 (DNS) 服务,用于为所有 Service 和 Pod 提供名称到 IP 地址的转换。

在多租户环境中,如果租户可以访问 Pod 和其他 Kubernetes 资源,或者需要更强的隔离,可能需要阻止 Pod 查找其他命名空间中的服务。你可以通过配置 DNS 服务的安全规则来限制跨命名空间 DNS 查找。例如,CoreDNS(Kubernetes 的默认 DNS 服务)可以利用 Kubernetes 元数据将查询限制在命名空间内的 Pod 和 Service。有关更多信息,请阅读 CoreDNS 文档中配置此功能的示例

当使用每个租户一个虚拟控制平面模型时,必须为每个租户配置一个 DNS 服务,或者必须使用多租户 DNS 服务。这是一个定制版 CoreDNS 的示例,它支持多个租户。

运算符

Operator 是管理应用程序的 Kubernetes 控制器。Operator 可以简化应用程序多个实例(如数据库服务)的管理,这使其成为多消费者(SaaS)多租户用例中的常见构建块。

在多租户环境中使用 Operator 应遵循更严格的准则。具体而言,Operator 应该:

  • 支持在不同的租户命名空间中创建资源,而不仅仅是在 Operator 部署的命名空间中。
  • 确保 Pod 配置了资源请求和限制,以确保调度和公平性。
  • 支持为数据平面隔离技术(如节点隔离和沙箱容器)配置 Pod。

实现

共享 Kubernetes 集群以实现多租户主要有两种方式:使用命名空间(即每个租户一个命名空间)或虚拟化控制平面(即每个租户一个虚拟控制平面)。

在这两种情况下,还建议进行数据平面隔离以及管理其他注意事项,例如 API 优先级和公平性。

命名空间隔离得到了 Kubernetes 的良好支持,资源成本可以忽略不计,并提供了允许租户适当交互的机制,例如允许服务到服务通信。然而,它可能难以配置,并且不适用于无法进行命名空间管理的 Kubernetes 资源,例如自定义资源定义、存储类和 Webhook。

控制平面虚拟化允许隔离非命名空间资源,代价是资源使用量略高,跨租户共享更困难。当命名空间隔离不足但又不想使用专用集群时,它是一个不错的选择,因为专用集群维护成本高昂(尤其是在本地)或开销更高且缺乏资源共享。然而,即使在虚拟化控制平面内,你很可能也会通过使用命名空间获得收益。

这两个选项将在以下部分中更详细地讨论。

每个租户一个命名空间

如前所述,即使你正在使用专用集群或虚拟化控制平面,也应考虑将每个工作负载隔离到其自己的命名空间中。这确保每个工作负载仅能访问自己的资源(例如 ConfigMap 和 Secret),并允许你为每个工作负载定制专用安全策略。此外,最佳实践是为每个命名空间提供在整个集群中(即,即使它们在单独的集群中)唯一的名称,因为这使你可以在将来灵活地在专用集群和共享集群之间切换,或使用服务网格等多集群工具。

反之,在租户级别(而不仅仅是工作负载级别)分配命名空间也有优势,因为通常存在适用于单个租户拥有的所有工作负载的策略。然而,这会带来自身的问题。首先,这使得为单个工作负载定制策略变得困难甚至不可能;其次,要确定一个单一的“租户”级别来分配命名空间可能具有挑战性。例如,一个组织可能有部门、团队和子团队——应该为哪个层级分配命名空间?

一种可能的方法是将命名空间组织成层次结构,并在它们之间共享某些策略和资源。这可能包括管理命名空间标签、命名空间生命周期、委派访问以及跨相关命名空间的共享资源配额。这些功能在多团队和多客户场景中都很有用。

每个租户一个虚拟控制平面

控制平面隔离的另一种形式是使用 Kubernetes 扩展为每个租户提供一个虚拟控制平面,从而实现集群范围 API 资源的细分。此模型可与数据平面隔离技术结合使用,以安全地管理跨租户的工作节点。

基于虚拟控制平面的多租户模型通过为每个租户提供专用的控制平面组件,从而扩展了基于命名空间的多租户,并完全控制集群范围的资源和附加服务。工作节点在所有租户之间共享,并由通常租户无法访问的 Kubernetes 集群管理。这个集群通常被称为**超级集群**(或有时称为**主机集群**)。由于租户的控制平面不直接与底层计算资源关联,因此被称为**虚拟控制平面**。

虚拟控制平面通常由 Kubernetes API 服务器、控制器管理器和 etcd 数据存储组成。它通过元数据同步控制器与超级集群交互,该控制器协调租户控制平面和超级集群控制平面之间的更改。

通过为每个租户使用专用的控制平面,解决了由于所有租户共享一个 API 服务器而产生的大部分隔离问题。示例包括控制平面中的“吵闹的邻居”、策略配置错误无法控制的爆炸半径,以及 Webhook 和 CRD 等集群范围对象之间的冲突。因此,虚拟控制平面模型特别适用于每个租户需要访问 Kubernetes API 服务器并期望完全集群可管理性的情况。

改进的隔离是以运行和维护每个租户独立的虚拟控制平面为代价的。此外,每个租户的控制平面并不能解决数据平面中的隔离问题,例如节点级别的“吵闹的邻居”或安全威胁。这些问题仍然必须单独解决。

最后修改于 2025 年 5 月 22 日太平洋标准时间上午 11:53:从多租户页面删除指向第三方项目的链接 (56db43a063)