13.2. 平台工程与多集群:Crossplane、Backstage 与 vCluster
平台工程不是「给 Kubernetes 套一个 UI」。它的目标是把组织里重复了上百次的部署、申请、排障动作用少数几条铺好的路收敛起来,让应用团队自助完成,平台团队只在路径之外介入。本章按「基础设施编排 → 开发者入口 → 多租户 → 多集群与边缘 → 成本」这条链路,把 Crossplane、Backstage、vCluster 这些项目放回它们各自该在的位置。
- 层级:
L3 · 平台
- 难度:
进阶
- 预计:
80 分钟
- 前置:
CRD 与 Operator、Karmada、RBAC
你将学到
平台工程的边界:Golden Path 该覆盖什么、哪些问题不该由平台解决
Crossplane、Cluster API、Terraform / OpenTofu、Pulumi 在编排链路里的分工
Backstage、KubeVela、Radius 三种「开发者入口」的抽象层级差异
vCluster、Capsule、HNC 三档隔离强度,以及选择依据
Karmada、Fleet、Liqo、KubeEdge、OpenYurt 在多集群与边缘场景的定位
用 OpenCost / Kubecost 把账单摊到 namespace 与工作负载
一个 vCluster 最小实验:验证虚拟集群到底隔离了什么
13.2.1. 平台工程:把「能部署」变成「铺好的路」
内部开发者平台(IDP)是一个产品,用户是应用团队。它的验收指标不是「接入了多少集群」,而是新服务从创建到上线的时间、自助率、以及平台团队收到的工单量。判断一条路径是否值得铺,看四个默认值:默认安全(镜像扫描、最小权限、NetworkPolicy 开箱即有)、默认可观测(日志、指标、追踪不用申请)、默认可回滚(每次变更都有版本与回退路径)、有逃生舱(Golden Path 覆盖不到的合法需求能显式申请例外,而不是被逼去绕开平台)。
Golden Path 是「铺好的路」,不是「唯一的路」。把路修得足够顺,让 80% 的常规需求走上去;剩下 20% 保留人工评审通道。反过来,如果平台把 Kubernetes 藏得连 kubectl describe 都看不到,故障时应用团队只能等平台团队,这就把分布式系统的排障成本变成了排队成本。
角色 |
负责 |
交付物 |
|---|---|---|
基础设施团队 |
集群、网络、云账号、节点池 |
Cluster API 清单、Terraform 模块 |
平台团队 |
抽象 API、模板、门禁、成本模型 |
XRD / Composition、Backstage 模板、策略 |
应用团队 |
业务镜像、副本与资源声明、SLO |
一份 Claim 或 Application 清单 |
警告
三个常见的反模式
门户只做 YAML 表单:把
kubectl apply换成网页提交,抽象没变,平台团队反而多维护一个前端。平台团队变成工单队列:所有变更都要平台审批,自助率接近零,Golden Path 名存实亡。
抽象泄漏无人兜底:封装层出错时没有日志、没有事件、没有文档,用户只能猜。
13.2.2. 基础设施编排:四种定位
这五个工具经常被放在一张表里比较,但它们回答的是不同问题。
工具 |
状态存哪 |
收敛方式 |
主要管理对象 |
|---|---|---|---|
Terraform |
state 文件(本地或远端后端) |
|
云账号、网络、托管服务 |
OpenTofu |
同 Terraform(兼容 state 与 provider) |
同上,MPL 许可 |
同上 |
Pulumi |
state 后端 |
同上,用通用语言写 |
同上,可复用 SDK 与测试 |
Cluster API |
etcd(CRD) |
控制器持续调和 |
集群与机器本身 |
Crossplane |
etcd(CRD) |
控制器持续调和 |
云资源,以 K8s API 暴露 |
Terraform / OpenTofu / Pulumi 是「置备」工具:读取代码、对比 state、算出 diff、执行。漂移只在下一次 plan 时被发现,所以必须把 plan 放进 CI,并且给 state 加锁。OpenTofu 是 Terraform 改许可证后由 Linux Foundation 托管的分支,HCL、provider 生态与 state 格式保持兼容,迁移主要是换二进制与 registry 地址。Pulumi 的差异在于用 TypeScript、Go、Python 直接写逻辑,适合已经有工程化测试能力的团队。
Cluster API 管的是集群本身:Cluster 描述一个集群,MachineDeployment 描述一组机器,KubeadmControlPlane 描述控制平面,具体云资源由 infrastructure provider(CAPA、CAPV、CAPZ)创建。clusterctl 负责引导管理集群与迁移。它不负责应用放置,这件事交给 Karmada 或 Fleet。
Crossplane 把云资源变成 CRD,用 Kubernetes 的 RBAC、审计、GitOps 与调和循环管理它们。平台团队写 CompositeResourceDefinition(XRD)定义 API,写 Composition 声明这个 API 由哪些托管资源组成,应用团队只提交一个命名空间级的 Claim:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata: {name: xdatabases.platform.example.com}
spec:
group: platform.example.com
names: {kind: XDatabase, plural: xdatabases}
claimNames: {kind: Database, plural: databases} # 应用团队看到的是 Database
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
size: {type: string, enum: [small, medium]}
region: {type: string}
required: [size, region]
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata: {name: orders-db, namespace: team-a}
spec:
parameters: {size: small, region: cn-north-1}
compositionSelector: {matchLabels: {provider: aws}}
Composition 把 Database 映射成一组托管资源(RDSInstance、DBSubnetGroup、SecurityGroup),用 patches 把 small 翻译成实例规格,再由 provider-aws 真正调用云 API 并持续校正漂移。应用团队不需要知道 RDS 的参数名,也不需要云账号凭据——凭据在 ProviderConfig 里,权限由 RBAC 控制。
两者的分界线是引导悖论:用 Terraform 创建集群、安装 Crossplane、配置云凭据,再把集群内的云资源交给 Crossplane。不要让 Crossplane 管理它自己所在集群的引导资源,否则一次误删就无法自愈。
图:门户只负责入口与模板,真正的约束在 Golden Path API 与 GitOps 仓库上;放置与容灾交给多集群控制面。
13.2.3. 开发者门户:Backstage、KubeVela 与 Radius
Backstage 是 Spotify 开源的portal 框架,不是一个装完就能用的产品。核心是软件目录:catalog-info.yaml 声明 Component、System、Domain、API、Resource,由目录插件从 Git 拉取并建立关系图。软件模板(Scaffolder)用一套 action 把「新建服务」变成流水线:生成仓库、注册目录、开 CI、建 Argo CD Application。TechDocs 把 Markdown 渲染成文档站,Kubernetes 插件把集群里的 Deployment 与目录实体关联起来。代价是你要维护一个 React + Node 应用、跟进插件升级,并且持续治理目录数据——目录里有一半实体是孤儿,门户就失去了可信度。
KubeVela 走的是应用交付抽象:OAM 的 Application 由 Component 与 Trait 组成,平台团队用 Definition(CUE、Helm、Terraform)定义能力,用户在 Application 里组合;workflow 步骤负责多集群下发、灰度与人工确认,底层可以接 Karmada 或 OCM。它自带 VelaUX 作为界面,但定位是「交付层」,适合已经有 GitOps、只缺统一应用模型的环境。
Radius 以应用为中心:Application 下挂 Container、Route 与连接关系,底层运行时可以是 Kubernetes,也可以是 Azure Container Apps;环境能力用 recipe(Bicep / Terraform)描述,应用清单不绑定具体云资源。它建立在 Dapr 之上,适合「同一份应用定义要落到多种运行时」的场景。
项目 |
抽象层级 |
是否改应用清单 |
适合谁 |
|---|---|---|---|
Backstage |
目录 + 模板 + 文档 |
否,新增 |
需要统一入口与文档的中大型组织 |
KubeVela |
应用交付(OAM) |
是,改用 Application |
多集群交付、需要灰度与工作流 |
Radius |
应用 + 依赖关系 |
是,改用 Radius 资源 |
跨 K8s 与托管运行时的团队 |
备注
门户不等于平台
没有 Golden Path、没有配额与门禁、没有成本数据的门户,只是更漂亮的 kubectl。先有 API 与策略,再有界面;顺序反了,门户会变成第二个工单系统。
13.2.4. 多租户:三档隔离强度
多租户要回答的是「两个团队共享一套基础设施时,谁能看见谁、谁能影响谁」。三档方案对应三种隔离成本。
方案 |
API 隔离 |
CRD 版本冲突 |
网络/配额 |
额外控制面 |
|---|---|---|---|---|
HNC |
无,同一 apiserver |
仍存在 |
配额可继承,网络需自建 |
无 |
Capsule |
无,同一 apiserver |
仍存在 |
Tenant 模板统一注入 |
无 |
vCluster |
独立 apiserver + 存储 |
完全隔离 |
需 NetworkPolicy 补齐 |
每租户一套 |
HNC(Hierarchical Namespace Controller)给 namespace 建立父子关系,父级的 RBAC、NetworkPolicy、ResourceQuota 会传播到整棵子树,HierarchicalResourceQuota 让配额在子树内共享。它解决的是「一个团队十几个环境」的配置重复,不解决 API 隔离。
Capsule 引入 Tenant 对象:平台团队创建 Tenant 并指定 owner,租户只能在自己被分配的 namespace 前缀下建 namespace,配额、LimitRange、NetworkPolicy、节点选择器、可用 StorageClass 由 Tenant 统一注入。它仍然是共享 apiserver,所以 CRD 版本、准入 webhook、apiserver 故障半径都是共享的。几十个团队、彼此信任度中等时,这是性价比最高的一档。
vCluster 在宿主集群的一个 namespace 里跑一套独立的控制面(apiserver + kine/etcd),租户拿到的是自己的 kubeconfig。Pod、Service、PVC 由 syncer 同步到宿主 namespace,CRD、webhook、RBAC、版本则完全留在虚拟侧——这意味着租户可以自行安装 Operator、升级 Kubernetes 小版本,而不会污染邻居。代价是每套控制面的资源开销、syncer 的额外延迟,以及调试时要同时看两个集群。
警告
vCluster 不是安全边界
虚拟集群隔离的是 API 对象,不是内核。Pod 仍跑在宿主节点上,共享内核、容器运行时与网络插件。生产上必须叠加 NetworkPolicy、Pod Security Admission、租户专属节点池(配合污点与亲和),并对 hostPath、特权容器做准入限制。
13.2.5. 多集群与边缘:按「控制什么」分类
项目 |
控制对象 |
工作方式 |
典型场景 |
|---|---|---|---|
Karmada |
应用放置与故障转移 |
联邦控制面 + 成员集群 |
多云、多地域容灾 |
Fleet |
Git 仓库到大量集群的同步 |
GitRepo → Bundle → BundleDeployment |
上千个边缘/零售站点 |
Liqo |
集群间资源借用 |
对等 peering + 虚拟节点 |
突发容量、跨云借算力 |
KubeEdge |
边缘节点与设备 |
cloudcore / edgecore,边缘自治 |
工厂、车载、离线站点 |
OpenYurt |
边缘节点运维 |
yurthub 缓存 + 节点池 |
存量 K8s 延伸到边缘 |
Karmada 已在多集群编排:Karmada中展开:模板进 karmada-apiserver,PropagationPolicy 决定放置,Work 下发到成员集群,集群失联时按容忍时间重调度。
Fleet(Rancher)的模型更接近「GitOps 分发器」:注册下游集群后,一个 GitRepo 指向仓库路径,Fleet 把它渲染成 Bundle,再按 BundleDeployment 分发到匹配的集群,支持 Helm 与 Kustomize。它不提供跨集群重调度,集群失联时只是标记 NotReady,适合「每个站点一份配置、数量极大」的场景。
Liqo 建立集群间的对等关系,把远端集群的剩余容量映射成本地的虚拟节点,命名空间可以声明 offloading 策略,把 Pod 调度过去。它解决的是容量借用的临时需求,而不是长期的多集群治理。
KubeEdge 把 Kubernetes 延伸到边缘:cloudcore 在云侧,edgecore 在边缘节点,边缘断网时依靠本地缓存继续运行(边缘自治),EdgeMesh 处理边缘节点间的服务发现,设备通过 DMI 与 Mapper 接入。OpenYurt 走非侵入路线,用 yurthub 在节点上缓存 apiserver 请求实现边缘自治,用节点池与单元化(unit)控制升级与故障域,适合把存量集群直接延伸到边缘。
13.2.6. 成本治理:把账单摊到团队
OpenCost 从 Prometheus 读取 Pod 的 requests、节点价格与云厂商价目,按 namespace、controller、label 计算分摊,通过 /allocation API 输出。它区分三种成本:工作负载直接成本、集群 idle 成本、共享成本(如 DNS、监控),后两者需要显式分摊规则,否则各部门看到的数字加起来不等于账单。Kubecost 建立在同一套模型上,补了 UI、多集群聚合(Thanos)、预算告警与 rightsizing 建议。
三个容易踩的坑:
按 requests 还是按 usage:按 requests 分摊稳定、可预测,但会奖励「申请大、用得少」;按 usage 更真实,却随采样波动。生产上通常以 requests 为主、usage 作为效率指标并列展示。
GPU 不可压缩:一张卡只能给一个 Pod,空闲也是全价。GPU 成本必须按整卡归属,再单独统计利用率。
只看账单不看闭环:可见性 → 配额 → rightsizing(VPA 建议、Karpenter 选型)→ 竞价实例,四步缺一不可。只压 requests 会换来 OOM 与驱逐,成本转移到稳定性上。
备注
单位成本比总成本有用
总账单受业务增长影响,无法评价平台好坏。用「每千次请求成本」「每租户成本」「每训练任务 GPU·小时」这类单位成本,才能判断优化是否真的生效。
13.2.7. 动手实验:vCluster 最小实验
实验:在共享集群里开一个虚拟集群,验证隔离边界
目标:创建一个 vCluster,确认 CRD、RBAC、命名空间在虚拟侧独立,工作负载被同步到宿主 namespace,最后验证 sleep 与清理。
前置:一个可用的宿主集群(3 节点以上)、kubectl、helm、vcluster CLI;宿主有默认 StorageClass。
13.2.7.1. 步骤 1 · 创建虚拟集群
vcluster create team-a -n team-a --connect=false
kubectl -n team-a get pods
NAME READY STATUS RESTARTS AGE
team-a-0 1/1 Running 0 40s
team-a-syncer-6d9c7f4b8-x2m4p 1/1 Running 0 40s
coredns-5d78c9869d-8kzqv 1/1 Running 0 35s
team-a-0 是虚拟控制面(apiserver + kine),team-a-syncer 负责把虚拟侧的对象同步到宿主。--connect=false 避免 CLI 直接改写当前 kubeconfig;需要时用 vcluster connect team-a -n team-a 拿独立上下文。
13.2.7.2. 步骤 2 · 在虚拟集群里创建工作负载
vcluster connect team-a -n team-a -- bash -c '
kubectl create deployment web --image=nginx:1.27 --replicas=2
kubectl get ns
kubectl get crd | wc -l
'
虚拟侧只看到自己的 default、kube-system 等少量命名空间,CRD 数量也是个位数。回到宿主执行 kubectl -n team-a get pods,会看到两个带同步后缀的 nginx Pod,它们带 vcluster.loft.sh/managed-by=team-a 标签:
kubectl -n team-a get pods -l vcluster.loft.sh/managed-by=team-a
13.2.7.3. 步骤 3 · 验证 CRD 与 RBAC 隔离
vcluster connect team-a -n team-a -- bash -c '
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: {name: demos.example.com}
spec:
group: example.com
names: {kind: Demo, plural: demos}
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema: {openAPIV3Schema: {type: object}}
EOF
kubectl get crd demos.example.com
'
kubectl get crd demos.example.com # 宿主侧应返回 NotFound
虚拟侧 Established 为 True,宿主侧查不到——这就是 vCluster 与传统 namespace 隔离最本质的区别:租户可以自行安装 Operator 与 CRD。
13.2.7.4. 步骤 4 · sleep 与清理
vcluster sleep team-a -n team-a
kubectl -n team-a get pods # 控制面副本缩到 0,PVC 保留
vcluster wakeup team-a -n team-a
vcluster delete team-a -n team-a
delete 会连同宿主 namespace 里的同步对象一起清理;如果还挂了 PVC,先确认 StorageClass 的回收策略,避免留下孤儿卷。
13.2.7.5. 验证清单
vcluster list显示team-a为 Running,kubectl -n team-a get pods有控制面与 syncer虚拟侧
kubectl get ns看不到宿主的业务命名空间虚拟侧创建的 CRD 在宿主侧
NotFound宿主的同步 Pod 带
vcluster.loft.sh/managed-by=team-a标签vcluster sleep后控制面副本为 0,wakeup后恢复
13.2.8. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
虚拟集群创建后 Pod 一直 |
宿主资源不足,或控制面 requests 超过节点余量 |
|
虚拟侧 Pod |
宿主没有对应的 StorageClass,或 PVC 无法置备 |
在虚拟侧指定宿主存在的 StorageClass;确认 syncer 有 PVC 同步权限 |
同步的工作负载在宿主看不到 |
syncer 未就绪,或 |
|
虚拟侧拉不到私有镜像 |
imagePullSecret 不会自动跨边界同步 |
在虚拟侧重建 Secret,或用 |
域名解析失败 |
虚拟侧 Service 的 DNS 名与宿主侧不同 |
跨边界访问用宿主侧的 synced Service 名;虚拟侧内部用短名 |
|
kubeconfig 未切换,或 |
|
13.2.9. 进阶挑战
用 Crossplane 的
provider-kubernetes写一个 Composition,让DatabaseClaim 同时产出一个Deployment与一个Secret,并观察删除 Claim 时的级联回收顺序。在宿主集群上用 Capsule 建两个 Tenant,比较「Capsule 共享 apiserver」与「vCluster 独立 apiserver」在 CRD 版本冲突上的差异。
用 OpenCost 的
/allocationAPI 导出team-a一周的成本,按vcluster.loft.sh/managed-by标签做 showback 报表,并指出 idle 成本该如何分摊。
13.2.10. 小结
平台工程交付的是 Golden Path 与自助 API,门户只是入口;衡量指标是上线时间与自助率。
Terraform / Pulumi 负责置备,Cluster API 负责集群生命周期,Crossplane 用 CRD + 调和把云资源纳入 Kubernetes,三者可组合而不互斥。
隔离强度从 HNC 到 Capsule 再到 vCluster 递增,成本也递增;vCluster 隔离 API 对象,不隔离内核。
多集群工具按「控制什么」区分:Karmada 管放置,Fleet 管分发,Liqo 借容量,KubeEdge 与 OpenYurt 管边缘。
成本治理的关键是分摊规则与单位成本,而不是总账单。