在Kubernetes中,当你需要隔离不同的工作负载时,可以先使用命名空间。命名空间为在单个集群内分离这些工作负载提供了一种简便的方法。
但是,随着你的需求不断增加,尤其是在合规性、安全性、多租户架构或存在相互冲突的依赖关系等方面,你的团队很可能会不再仅仅依赖命名空间,而是开始创建独立的集群。
最初这种简单的隔离方式很快就会导致集群数量激增,进而带来更高的成本、更复杂的网络架构以及持续不断的运维开销。
在本文中,我们将探讨如何通过使用kcp来解决这个问题——它允许你在单一的控制平面内运行多个“逻辑集群”。

目录

先决条件

  • kubectl已安装。

  • 需要一个用于运行命令的终端。

  • Curl已安装。

命名空间与多个Kubernetes集群带来的挑战

虽然命名空间能够提供一定程度的隔离能力,但许多团队通常会选择创建全新的Kubernetes集群,以便实现更好的多租户管理、环境分离或地理分布。
起初,这种做法效果不错。但随着系统规模的扩大,维护这么多集群所带来的挑战往往会超过其带来的好处。
每个新的集群都需要配备自己的控制平面,而你需要持续为这些控制平面打补丁、进行升级并对其进行监控。长此以往,这些运维开销会逐渐累积,占用本应用于更有价值的工作的资源。
此外,各个集群之间并不会自动共享服务发现机制或身份认证信息。这就迫使你不得不引入额外的层,比如服务网格或基于VPN的网络架构,而这些措施只会增加系统的复杂性,并扩大系统受攻击的风险。
成本也是一个不可忽视的因素。无论这些集群承载了多少工作负载,它们都会产生基础的基础设施成本。为小型团队专门创建集群可能会导致资源利用率低下,甚至因为成本过高而推迟必要环境的搭建。
因此,平台维护团队往往不得不花费大量时间来维护这些集群基础设施,而不是专注于提升开发人员的工作效率。

阐释命名空间问题

正如我之前所说,当管理多个集群变得过于复杂时,一个自然的解决办法就是在单个集群内部使用命名空间来进行隔离。

乍一看,这似乎是一个完美的解决方案。

但要想了解这种方法的局限性,我们就通过一个在共享Kubernetes环境中常见的场景来进行分析:运行数据库。

我们首先为每个团队创建不同的命名空间:

➜ ~ kubectl create namespace team-a 
➜ ~ kubectl create namespace team-b

假设团队A需要为其某个服务使用MongoDB数据库。该团队首先必须将所需的MongoDB自定义资源定义文件安装到集群中,这样Kubernetes才能识别这些不同的MongoDB资源:

➜ ~ kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/crds.yaml

customresourcedefinition.apiextensions.k8s.io/clustermongodbroles.mongodb.com created 
customresourcedefinition.apiextensions.k8s.io/mongodbmongodb.com created 
customresourcedefinition.apiextensions.k8s.io/mongodbmulticluster mongodb.com created 
customresourcedefinition.apiextensions.k8s.io/mongodbsearch.mongodb.com created 
customresourcedefinition.apiextensions.k8s.io/mongodbusers.mongodb.com created 
customresourcedefinition.apiextensions.k8s.io/opsmanagersmongodb.com created 
customresourcedefinition.apiextensions.k8s.io/mongodbcommunity mongodbcommunity.mongodb.com created

接下来,团队A将实际的Operator应用程序(负责持续运行数据库逻辑的控制程序)安装到他们指定的命名空间中:

➜ ~ kubectl apply -n team-a -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/mongodb-kubernetes.yaml

然而,由于以下错误,安装过程并未完成:

提供的对象“mongodb”所对应的命名空间与“team-a”不匹配。必须使用“--namespace=mongodb”选项才能完成此操作。

为什么会出现这种问题呢?这是因为大多数Kubernetes Operator都是被设计为能够管理整个集群的,而不仅仅是一个命名空间。

为了强制让Operator在team-a命名空间中运行,我们可以直接修改配置文件:

curl -s https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/mongodb-kubernetes.yaml \
  | sed 's/namespace: mongodb/namespace: team-a/g' \
  | kubectl apply -f 

之后我们可以确认Operator已经成功安装并正在运行:

➜ ~ k get po -n team-a 
NAME                                          READY STATUS  RESTARTS AGE 
mongodb-kubernetes-operator-6f5f8bb7fd-8h5hj  1/1   Running 0        59s

但即使我们成功诱使操作员在team-a的命名空间内执行相关操作,我们仍然没有解决根本问题。

乍一看,team-a的操作员确实被限制在他们的命名空间内。但还记得第一步吗?CRD并不属于任何特定的命名空间——它们完全是集群范围的。因此,尽管team-a只是为了自己的需求来部署这些资源,但这些CRD实际上已经在整个集群中进行了全局注册。

如果B团队查看API,他们会看到A团队安装的所有与MongoDB相关的CRD。

➜ ~ kubectl get crds | grep mongodb

clustermongodbroles.mongodb.com               2026-03-24T10:49:35Z
mongodb.mongodb.com                           2026-03-24T10:49:36Z
mongodbcommunity.mongodbcommunity.mongodb.com 2026-03-24T10:49:38Z
mongodbmulticluster.mongodb.com               2026-03-24T10:49:36Z
mongodbsearch mongodb.com                     2026-03-24T10:49:37Z 
mongodbusers.mongodb.com                      2026-03-24T10:49:37Z 
opsmanagers.mongodb.com                       2026-03-24T10:49:37Z

现在想象一下,如果B团队需要为自己的服务安装不同版本的MongoDB会怎么样。由于这些CRD是在整个集群中共享的,因此两个团队实际上都在使用相同的配置。这意味着其中一个团队的更改很可能会影响到另一个团队,从而导致本应相互隔离的环境出现冲突。

介绍kcp

kcp是一个开源项目,它允许你在同一个控制平面上运行多个逻辑Kubernetes集群。

这些逻辑集群被称为工作区,每个工作区都像一个独立的Kubernetes集群一样运作。每个工作区都有自己的API端点、认证机制、授权规则以及配置策略,因此各团队可以享受到完全隔离的工作环境。

kcp的架构及组件示意图

正是这种将控制平面与工作节点分离的设计,使得kcp与众不同。

在传统的Kubernetes系统中,创建一个新的集群意味着需要配置新的API服务器、新的etcd实例以及所有相关的控制器。而使用kcp,你只需要创建一个工作区,就能为你的工作负载提供一个安全、隔离的环境。

需要注意的是,kcp本身并不负责运行实际的工作负载。它仅仅是一个控制平面工具。你的应用程序仍然需要运行在物理的Kubernetes集群上,而kcp只负责管理这些工作区以及确保资源能够正确地分配给相应的集群。

开始使用kcp

既然我们已经了解了kcp是什么以及它的重要性,接下来我们就来实际操作一下吧。我们将搭建一个本地的kcp环境,并通过实践来深入理解它的核心概念。

为了使这一方案更具现实可行性,我们将遵循常见的kcp工作流程:由平台团队提供定制的API,而租户团队则使用这些API。

在我们的案例中,平台团队会提供MongoDB相关的API,我们的两个租户团队则会通过APIBindings来订阅这些API。一旦完成绑定,它们就可以在自己的工作空间中部署MongoDB实例,并将这些实例与物理集群进行同步。

这种模式正是kcp实现可扩展多租户架构的核心所在。平台团队负责控制API的定义及版本管理,而租户团队则能够无需了解底层基础设施即可自行使用这些API。接下来我们就来看看具体是如何运作的吧!

安装kcp

在本地运行kcp所需的资源非常少,因为不需要启动任何复杂的计算节点。你需要准备两样东西:kcp服务器本身、kubectl-kcp工具,以及用于管理工作空间的kubectl-ws插件。

如果要下载这些二进制文件,请访问kcp-dev的发布页面

以下命令适用于macOS Apple Silicon系统。如果你使用的是Intel Mac或Linux系统,只需将darwin_arm64替换为相应的架构即可。

  1. 下载kcp服务器及工作空间插件:
➜ ~ curl -LO https://github.com/kcp-dev/kcp/releases/download/v0.30.1/kcp_0.30.1_darwin_arm64.tar.gz 

➜ ~ curl -LO https://github.com/kcp-dev/kcp/releases/download/v0.30.1/kubectl-kcp-plugin_0.30.1_darwin_arm64.tar.gz

➜ ~ curl -LO https://github.com/kcp-dev/kcp/releases/download/v0.30.1/kubectl-ws-plugin_0.30.1_darwin_arm64.tar.gz
  1. 解压下载到的文件:
➜ ~ tar -xzf kcp_0.30.1_darwin_arm64.tar.gz 
➜ ~ tar -xzf kubectl-kcp-plugin_0.30.1_darwin_arm64.tar.gz
➜ ~ tar -xzf kubectl-ws-plugin_0.30.1_darwin_arm64.tar.gz
  1. 将所需的二进制文件添加到你的PATH环境中:
➜ ~ sudo mv bin/kcp /usr/local/bin/
➜ ~ sudo mv bin/kubectl-kcp /usr/local/bin/
➜ ~ sudo mv bin/kubectl-ws /usr/local/bin/

你可以通过查看版本信息来确认安装是否成功。

➜ ~ kcp --version
kcp version v1.33.3+kcp-v0.0.0-627385a6

启动服务器

在完成二进制文件的安装后,我们可以启动本地的控制平面,并将其绑定到localhost地址上。不过在此之前,我们首先需要创建一个“工作文件夹”。

➜ ~ mkdir kcp-test
➜ ~ cd kcp-test

然后我们就可以在这个目录中启动kcp服务器了。

➜ ~ kcp start --bind-address=127.0.0.1

在kcp启动其内部数据库并暴露API服务器的过程中,你会看到一系列日志信息。请让这个终端程序在后台持续运行。

连接根工作空间

打开一个新的终端窗口,然后返回到我们刚刚创建的kcp-test文件夹中。

起初,如果你运行标准的ls命令,会看到这个文件夹看起来是空的。但实际上,在启动过程中,kcp会自动生成一个隐藏的.kcp目录,其中包含了我们的本地证书以及管理用的kubeconfig文件。让我们来验证一下这一点:

➜ ~ cd kcp-test 
➜ kcp-test ls
➜ kcp-test ls -a . .. .kcp 
➜ kcp-test ls .kcp admin.kubeconfig apiserver.crt apiserver.key etcd-server sa.key

现在我们已经知道配置文件的具体位置了,接下来我们需要将其导出,这样我们的kubectl命令就会指向kcp而不是默认的集群:

export KUBECONFIG=$PWD/.kcp/admin.kubeconfig

最后,让我们使用之前安装的工作区插件来确认我们是否已经正确连接到了kcp:

 ➜ kubectl ws .

你应该会看到控制台输出以下信息:

当前工作区是 'root'。

这说明你现在已经正式进入了kcp的root工作区。这里是我们开始创建各个租户逻辑集群的最高级管理区域。

创建和管理工作区

如上所述,在标准的Kubernetes集群中,如果要分隔不同的团队,就需要使用kubectl create namespace命令。而在kcp中,我们通过创建完全隔离的逻辑集群——即工作区——来解决这个问题。

如果你还记得我们之前的架构图,我们会为公司创建三个不同的环境:一个用于平台工程师管理共享API,另外两个则供各个独立的租户开发团队使用。

由于我们现在处于root管理工作区内,因此可以将其作为父工作区来创建新的租户工作区:

➜ kubectl ws create platform-team
工作区 "platform-team" (类型 root:organization) 已创建。
正在等待其准备就绪... 
工作区 "platform-team" (类型 root:organization) 已可使用。

➜ kubectl ws create team-a 
工作区 "team-a" (类型 root:organization) 已创建。
正在等待其准备就绪... 
工作区 "team-a" (类型 root:organization) 已可使用。

➜ kubectl ws create team-b
工作区 "team-b" (类型 root:organization) 已创建。
正在等待其准备就绪... 
工作区 "team-b" (类型 root:organization) 已可使用。

在这里,kcp的优势真正体现出来了。与标准集群不同,在标准集群中对象只是以扁平列表的形式存在,而kcp则将其API结构组织成层次结构。我们可以使用tree命令来直观地查看这些新创建的逻辑集群的结构:

➜ kubectl ws tree
.
└── root
      ├── platform-team
      ├── team-a
      └── team-b

在这些逻辑集群之间切换的速度,与在终端中更改目录的速度一样快。现在让我们将上下文切换到A团队的工作空间:

➜ kubectl ws team-a 
当前工作空间是 'root:team-a'(类型为 root:organization)。

验证隔离性

为了真正理解我们刚才所做的事情带来的强大功能,让我们尝试在team-a工作空间内运行一个标准的Kubernetes命令:

➜ kubectl get namespaces

NAME STATUS AGE 
default Active 15m

我们还可以询问集群系统,究竟有哪些API是默认就可以使用的:

➜ kubectl api-resources

你的输出结果应该与下图所示的内容相似:

775eff52-8ae7-4363-bd37-fce6ab0cc587

仔细观察这个列表,你会发现其中并没有Pod、Deployment,甚至也没有ReplicaSet。你看到的这些API,并不是标准Kubernetes集群中会提供的全部接口。

这个输出结果恰恰证明了我们在架构部分讨论的内容:kcp的设计非常轻量级,因为每一个新的工作空间在创建时完全不包含任何计算资源。默认情况下,它只包含路由、RBAC、命名空间以及认证功能所必需的最基本控制平面API。

从A团队的角度来看,他们拥有这个原始的、空荡荡的工作空间。如果他们现在安装像MongoDB CRD这样的组件,那么这些组件也只会存在于这个特定的API范围内。

但这就引出了一个关键问题:既然这个工作空间中没有DeploymentPod相关的API,那我们究竟该如何部署应用程序呢?

部署和管理应用程序

既然我们已经创建好了这些隔离的工作环境,接下来就必须解决上一个终端输出中提到的问题:如果没有任何DeploymentPod相关的API,开发者们究竟该如何部署应用程序呢?

在标准的Kubernetes系统中,所有API都是集成在一起的。无论你是否需要某些功能,这些API都会被全部提供出来;而添加新的功能模块(比如Operator)也会强制影响到所有用户。

kcp采取了完全相反的设计思路:每个工作空间在创建时都是空白的,然后你可以根据实际需求,通过APIExportsAPIBindings这两个强大的机制,有选择地“订阅”你需要的API。

让我们一步步来看,这是如何解决MongoDB多租户问题的。

1. 平台团队“导出”API

<平台工程师并没有将这些自定义资源定义视为全局性风险,而是对它们进行了集中管理。首先,让我们切换到平台团队的工作空间吧:

➜ kubectl ws :root:platform-team

当前工作空间为 'root:platform-team'(类型为 root:organization)。

在这里,我们将在 platform-team 的工作空间中安装 MongoDB Operator CRD:

➜ kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/crds.yaml

为了确认这些 CRD 确实是隔离在 platform-team 的工作空间中的,我们先来查看一下到底安装了哪些 CRD:

➜ kubectl get crd

NAME                                          CREATED AT
clustermongodbroles.mongodb.com               2026-03-24T20:45:50Z
mongodb.mongodb.com                           2026-03-24T20:45:50Z
mongodbcommunity mongodbcommunity.mongodb.com 2026-03-24T20:45:51Z
mongodbmulticluster.mongodb.com               2026-03-24T20:45:50Z
mongodbsearch.mongodb.com                     2026-03-24T20:45:51Z
mongodbusers.mongodb.com                      2026-03-24T20:45:51Z
opsmanagers.mongodb.com                       2026-03-24T20:45:51Z

我们可以切换到 team-a 的工作空间(其实可以使用该团队的任意工作空间,我们只是想验证:这些安装的 CRD 仅能在 platform-team 的工作空间中看到)。

➜ kubectl ws :root:team-a

当前工作空间为 'root:team-a'(类型为 root:organization)。
➜ kubectl get crd 
未找到任何资源

从输出结果来看,确实没有发现或注册任何自定义资源。这就是 kcp 的强大之处。

如果你不想每次都要手动输入路径来在不同的逻辑集群之间切换,kcp 插件为你的终端提供了强大的交互式用户界面。

运行 kubectl ws -i 后,你可以使用箭头键在层次结构中导航,并按下 Enter 键来立即切换工作环境。更重要的是,这种交互模式能让你随时全面了解当前的环境状况:只需看一眼,就能知道某个特定工作空间中托管了多少 APIExport,或者哪些 API 当前被其他工作空间所 引用

4d86d960-a23c-4cb1-8155-6fe236240893

让我们回到 platform-team 的工作空间,继续完成我们的设置。

现在,我们需要进行一些与 kcp 相关的操作。如果你现在查看资源列表,会发现那些 CRD 仅限于这个工作空间内使用。为了能够安全地与其他团队共享这些资源,我们必须将它们转换成一种名为 APIResourceSchema 的内部跟踪对象。这就是 kcp 对 API 进行结构化版本控制的方式,这样这些 API 才能被安全地导出。
为了实现这一目标,我们使用了 `kcp` 插件来对本地的 MongoDB CRD 进行“快照”操作:
kubectl get crd mongodbcommunity.mongodbcommunity.mongodb.com -o yaml | kubectl kcp crd snapshot -f - --prefix v1 | kubectl apply -f -
你应该会看到如下输出:

apiresourceschema.apis.kcp.io/v1mongodbcommunity.mongodbcommunity.mongodb.com created

这条命令告诉 `kcp`:“获取我们刚刚安装的 CRD,为其创建一个前缀为 ‘v1’ 的快照,然后将生成的 APIResourceSchema 再应用回集群中。” 现在,让我们来查看 `kcp` 为我们生成的那个模式文件:
➜ kubectl get apiresourceschemas

NAME                                             AGE
v1mongodbcommunity.mongodbcommunity mongodb.com 11s
为了能够安全地与我们的团队共享这个 API,我们需要将生成的模式文件封装到 `APIExport` 中。这样就可以实现“API即服务”的功能,让其他工作空间可以选择是否使用这个 API。 让我们使用刚才找到的那个模式名称来创建 `APIExport` 文件:
➜ cat <<EOF | kubectl apply -f -
apiVersion: apis.kcp.io/v1alpha1
kind: APIExport
metadata:
  name: mongodb-v1
spec:
  latestResourceSchemas:
    - v1mongodbcommunity.mongodbcommunity.mongodb.com
EOF
我们可以通过检查系统中是否存在 `APIExport` 资源来确认文件是否已经成功创建:
➜ kubectl get apiexports

NAME       AGE
mongodb-v1 2m46s

2. 租户团队“绑定”到 API

现在,让我们将终端环境切换回 Team A。还记得之前的输出结果吗?他们的当前工作空间根本不知道 MongoDB 集群是什么。我们来验证一下:
➜ kubectl ws :root:team-a
Current workspace is "root:team-a" (type root:organization).

➜ kubectl api-resources | grep mongodb
# (没有输出结果。这个 API 在这里并不存在!)
为了能够安全地使用平台团队新创建的 API 服务,Team A 需要创建一个 `APIBinding`。

虽然我们可以使用标准的 Kubernetes YAML 文件来完成这一操作,但 `kcp` 插件提供了专门的 `bind` 命令。Team A 只需要将这个命令指向他们想要使用的具体工作空间和 API 出口文件即可:
➜ kubectl kcp bind apiexport root:platform-team:mongodb-v1
apibinding mongodb-v1 created. Waiting to successfully bind ...
mongodb-v1 created and bound.

➜ kcp-test kubectl get apibindings
NAME                  AGE   READY
mongodb-v1            73s   True
tenancy.kcp.io-bqt7a  7h10m True
topology.kcp.io-9dlvq 7h10m True
一旦 Team A 执行了那个 `bind` 命令,他们的当前工作空间就会立即获得新的功能。让我们再检查一次 `api-resources` 的输出结果:
➜ kubectl api-resources | grep mongodb
mongodbcommunity mdbc mongodbcommunity.mongodb.com/v1 true MongoDBCommunity

超越基本功能:未涉及的内容

到目前为止,您应该已经对kcp的核心用户功能有了扎实的了解,这些功能包括工作区API导出以及API绑定。但实际上,这种架构所能实现的功能还远不止这些。

为了保持这份指南的简洁性,我在本文中刻意没有涉及以下一些重要主题:

  1. 分片与高可用性:由于kcp被设计用来托管数千个逻辑集群,因此单个数据库显然无法满足需求。kcp引入了分片这一机制,使平台管理员能够将工作区状态横向分布到多个etcd实例上。这样一来,kcp便具备了极高的可扩展性以及出色的高可用性,同时也不会给开发者的使用体验带来任何不便。

  2. 前置代理:当kcp需要托管数百万个逻辑集群时,就需要有一种机制来无缝地引导流量。kcp的前置代理位于整个架构的最外层,它能够动态地将传入的kubectl API请求路由到正确的目标工作区或分片上,从而确保无论后台基础设施规模有多大,开发者的使用体验都不会受到影响。

  3. 虚拟工作区:虽然我们目前构建的工作区只是简单的、独立的状态存储单元,但kcp也支持虚拟工作区。这些虚拟工作区可以动态地生成只读的数据视图。例如,kcp可以利用虚拟工作区在多个租户工作区内展示某个特定API的统一视图,从而使开发人员能够轻松地同时监控所有相关数据。

  4. API导出切片:就像标准Kubernetes使用端点来将流量路由到Pod上一样,kcp也利用EndpointSlices来高效地管理和扩展大量API导出内容的分发工作,确保这些内容能够被数千个使用者工作区顺利接收。

  5. 同步代理的配置:我们在架构图中概念性地讨论了这一机制,但实际上并未具体说明如何将其部署到实际环境中。在生产环境中,您需要将同步代理部署在一组下游执行集群上(比如EKS、GKE或本地环境),这样它就能自动将工作负载从kcp中提取出来,并在物理硬件上顺利执行这些任务。

  6. 与Crossplane等外部工具的集成:由于kcp纯粹是一个多租户API控制层,因此它与Crossplane配合使用效果非常好。通过将Crossplane作为API导出进行发布,开发团队就可以直接从他们完全隔离的kcp工作区内,使用标准的YAML格式来配置实际的云基础设施(如AWS数据库或Cloud Spanners)了。

我们将在未来的专题文章中详细探讨这些高级集成技术。不过,仅仅依靠今天我们所掌握的基础功能,就已经足以解决文章开头提到的那些极其复杂的基础设施问题了。

Comments are closed.