1. 从 Terraform 到控制平面
一句话总结: Terraform 是「执行一次、改变现状」的命令式工具,Crossplane 是「持续观察、不断收敛」的控制平面,两者的差异不在语法而在时间维度。
Terraform 的工作模型是请求-响应:你运行 apply,它调 API 创建资源,写完 state 就退出。此后它对资源的了解只停留在 state 文件里,真实世界发生了什么它并不知道,除非你再次运行 plan。
Crossplane 把这个模型反过来:资源以 Kubernetes 自定义资源(CRD)的形式存在,一个常驻控制器持续观察期望状态与真实状态,一旦偏离就重新调谐(reconcile)。这不是「更快的 apply」,而是另一种范式。
Terraform:
apply ──► 创建资源 ──► 写 state ──► 退出
(此后无人看管,漂移要等下次 plan 才发现)
Crossplane:
kubectl apply ──► CR 进入 etcd
▲ │
│ ▼
│ Controller 观察 CR
│ │
│ ┌────────┴────────┐
│ ▼ ▼
│ 调云 API 创建 比较现状与期望
│ │ │
└────────┴──── 状态写回 CR.status
(周期调谐,漂移被自动纠正)
把基础设施搬进 Kubernetes 的收益集中在三点:统一控制面(应用与基础设施共用 RBAC、审计与 GitOps 工具链)、天然自愈(手工改回去无意义,下轮调谐会覆盖)、面向开发者的抽象(一个 kubectl apply -f mydb.yaml 就能建出完整数据库服务)。
代价同样明确:需要维护 Kubernetes 集群本身,控制平面成了新单点;调试链路比 Terraform 长(kubectl describe → conditions → events → provider 日志);Provider 覆盖率不如 Terraform,冷门云服务可能没有实现。
一句话: 如果团队已在 Kubernetes 上跑生产、且以 GitOps 为默认工作流,Crossplane 的收益最大;否则 Terraform 仍是更省心的选择。
2. Kubernetes 原生资源模型
一句话总结: Crossplane 把每一类云资源注册成 CRD,用
spec表达期望、status回写现状,云资源的生命周期完全由 Kubernetes 的声明式机制托管。
2.1 托管资源(Managed Resource)
以 AWS S3 桶为例,Crossplane 注册的 CRD 是 Bucket.s3.aws.upbound.io:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: acme-logs
spec:
forProvider:
region: ap-northeast-1
tags:
team: platform
providerConfigRef:
name: aws-prod
deletionPolicy: Delete # 删除 CR 时是否删云资源
| 字段 | 含义 |
|---|---|
forProvider | 直译为「给 provider 的参数」,即云 API 参数 |
providerConfigRef | 引用哪套凭据(ProviderConfig 对象) |
deletionPolicy | Delete / Orphan,决定 CR 删除时是否级联删云资源 |
managementPolicies | 更细粒度控制,如 Observe(只读不写) |
2.2 状态与 conditions
资源创建后,控制器把结果写回 status:
status:
conditions:
- type: Ready
status: "True"
reason: Available
- type: Synced
status: "True"
reason: ReconcileSuccess
atProvider:
arn: arn:aws:s3:::acme-logs
id: acme-logs
Synced 表示「成功调用了云 API」,Ready 表示「资源已达期望状态」。两者分离很重要——Synced=True, Ready=False 常见于资源正在异步创建(如 RDS 实例启动中)。
2.3 ProviderConfig 与凭据
凭据不写在 CR 里,而是抽到 ProviderConfig:
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: aws-prod
spec:
credentials:
source: IRSA # 用 Pod 的 IAM Role,无静态密钥
region: ap-northeast-1
source 支持 Secret、IRSA、WebIdentity 等,优先用 IRSA/Workload Identity。另外,Upbound 维护的 provider 大多由 Terraform Provider schema 自动生成,所以资源字段与 Terraform 几乎一一对应——Kubernetes provider
里学到的语义可以迁移,但要注意:资源名从 aws_s3_bucket 变成 Bucket.s3.aws.upbound.io,字段从下划线变驼峰,Terraform 的隐式依赖在 Crossplane 里靠 *Ref / *Selector 显式表达。
3. 组合资源与复合资源
一句话总结: 复合资源(XR)是面向开发者的自研 API,组合(Composition)定义「一个 XR 应该展开成哪些托管资源」,二者配合把多云资源的复杂度封装成一个对象。
3.1 三层抽象
开发者创建 AppDB(XR)
└─► Composition 按模板展开 ─► RDS + SubnetGroup + SecurityGroup(MR)
由 XRD 定义 AppDB 的 schema,多个 Composition 可对应同一个 XRD
- XRD(CompositeResourceDefinition):定义新的 API 类型,即
AppDB的 schema。 - XR(Composite Resource):开发者创建的实例。
- Composition:把 XR 展开成一组托管资源的模板。
3.2 定义 XRD
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xappdbs.platform.acme.io
spec:
group: platform.acme.io
names:
kind: XAppDB
plural: xappdbs
claimNames: # 允许命名空间级的 claim
kind: AppDB
plural: appdbs
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, large]}
engine: {type: string, enum: [postgres, mysql]}
required: [size, engine]
required: [parameters]
注意 claimNames:它让命名空间级的 AppDB 与集群级的 XAppDB 成对出现。开发者在自己的 namespace 里创建 AppDB,平台团队在集群级管理实际的 XAppDB,这是 Crossplane 的权限分层设计。
3.3 定义 Composition
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: appdb-aws
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: platform.acme.io/v1alpha1
kind: XAppDB
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: ap-northeast-1
engine: postgres
instanceClass: db.t3.small
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.engine
toFieldPath: spec.forProvider.engine
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.small
medium: db.t3.medium
large: db.r6g.large
base 是模板,patches 负责把 XR 的字段映射进去。补丁类型有 FromCompositeFieldPath(XR → 资源)、ToCompositeFieldPath(资源 → XR,回写状态)、FromEnvironmentFieldPath 等。
3.4 连接信息的自动传递
一个高频需求是「RDS 建好后,把连接串给应用」,用 connectionDetails 解决:
connectionDetails:
- fromConnectionSecretKey: endpoint
name: host
- fromConnectionSecretKey: username
name: user
控制器会把 RDS 的 endpoint/username 写进 XR 的 connection secret,应用只需挂载这个 Secret,这替代了 Terraform 里 output + 外部脚本注入的繁琐流程。
一句话: 复合资源的价值在于把「N 个云资源 + 它们之间的引用 + 连接信息」收敛成开发者可见的一个对象,平台团队改 Composition 不影响开发者的 API 契约。
4. Provider 与托管资源
一句话总结: Crossplane 的 Provider 是一组 CRD + 控制器,安装一个 Provider 就等于把一类云资源接进集群,升级要谨慎因为它可能引入新的资源 schema。
4.1 安装 Provider
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-s3
spec:
package: xpkg.upbound.io/upbound/provider-aws-s3:v1.14.0
packagePullPolicy: IfNotPresent
Upbound 把 provider 按服务拆包(provider-aws-s3、provider-aws-rds……),避免一次装进上千个 CRD 压垮 API Server。安装后用 kubectl get providerrevision 查看状态、kubectl get crd | grep upbound.io | wc -l 确认 CRD 数量。
Provider 升级会替换 CRD schema,如果新版本删了某个字段,已有 CR 可能校验失败。建议锁定版本、先在非生产集群验证、用 packagePullPolicy: IfNotPresent 避免意外拉新版。
4.2 Functions(组合函数)
新版本 Crossplane 用 Function 取代了内置 patch 引擎,让组合逻辑可以用代码写:
spec:
pipeline:
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
Function 是独立部署的 Pod,可用 Go/Python 编写,实现任意复杂逻辑(如「按 region 生成一组子网」)。代价是多一跳 gRPC 调用与一个额外组件要运维。
5. 漂移协调循环
一句话总结: Crossplane 的调谐循环周期性地对比 CR 与云资源,发现差异就尝试纠正;这与 Terraform 的「被动发现漂移」形成根本对比。
5.1 协调循环的工作方式
每隔 N 秒:
1. 从 etcd 读 CR 的 spec(期望)
2. 调云 API 的 Get/Describe(现状)
3. 若不一致:
├─ spec 改了 → Update 云资源
├─ 云上被删 → 重新 Create(deletionPolicy=Delete 时)
└─ 云上多改了 → Update 回期望值
4. 把结果写入 status.conditions
漂移检测
在 Terraform 里是主动动作:跑 plan 才发现、才收敛,人不在就漂着。Crossplane 是被动持续:漂移存在的窗口只有「下一个调谐周期」那么长。
| 维度 | Terraform | Crossplane |
|---|---|---|
| 检测时机 | 手动/定时 plan | 持续调谐 |
| 收敛动作 | 人工 apply | 自动 |
| 误纠风险 | 低(人审核) | 有(见 5.2) |
| 审计粒度 | plan 产物 + PR | Kubernetes 审计日志 |
5.2 自动收敛的副作用
自动收敛并不总是好事:应急热修被回滚(运维在控制台紧急改了参数,几分钟后被控制器改回去,对策是走 CR 变更而非控制台);与外部系统打架(另一个工具也在改同一资源,两者来回覆盖);删除保护缺失(误删 CR 且 deletionPolicy: Delete 会级联删云资源,生产资源建议先用 Orphan 观察)。
排查期间可以给 CR 加 crossplane.io/paused: "true" 注解暂停调谐,避免自动收敛干扰定位。
5.3 managementPolicies:更细的控制
spec:
managementPolicies: ["Observe"] # 只观察不写,用于接管既有资源
Observe 让 Crossplane 只读不写,适合「先纳管、后接管」的迁移场景——先让它观察现有资源、确认无差异,再切成 ["*"] 全量管理。
6. 与 Terraform 的边界与取舍
一句话总结: Crossplane 与 Terraform 不是替代关系而是分工关系,常见的健康组合是「Terraform 建集群与底座,Crossplane 管集群内的应用依赖资源」。
6.1 能力对比
| 维度 | Terraform | Crossplane |
|---|---|---|
| 执行模型 | 一次性 apply | 持续调谐 |
| 状态存储 | 远程 state 文件 | Kubernetes etcd |
| 抽象能力 | 模块(Module) | 复合资源(XR + Composition) |
| 依赖表达 | depends_on / 引用 | *Ref / *Selector |
| 开发者自助 | 需懂 Terraform | 只需懂 kubectl |
| 生态覆盖 | 极广 | 快速增长但仍有缺口 |
| 变更评审 | PR + plan | GitOps diff(Argo CD) |
6.2 边界划分的推荐
Terraform 负责:
├─ 云账号与 IAM 基座
├─ VPC / 网络 / 集群本身(EKS、GKE)
├─ 跨账号、跨区域的基础设施
└─ 生命周期长、变更少的底座
Crossplane 负责:
├─ 集群内的应用依赖(数据库、缓存、队列、对象存储桶)
├─ 面向开发者的自助 API
└─ 与工作负载生命周期同步的资源
划分依据是变更频率与所有权:底座多年变一次且由平台团队集中管理,Terraform 的 PR 流程最合适;应用依赖按业务节奏变化,开发者自助最合适,Crossplane 的 XR 最合适。
6.3 混用的两种模式与反模式
- Terraform 引导 Crossplane:用 Terraform 装 Crossplane、建 ProviderConfig、建 IAM 角色。这是最干净的方式——IaC 备选方案 里讨论的「用代码管控制平面自身」在这里同样适用。
- Crossplane 纳管既有资源:用
managementPolicies: ["Observe"]先观察,确认无差异后再接管。
一句话: 「一半资源在 Terraform state、一半在 Crossplane、还互相依赖」是事故温床——两个系统都不知道对方的存在,删除顺序无法保证。
7. 实战:从零定义一个数据库服务
一句话总结: 完整链路是「装 Provider → 定义 XRD → 写 Composition → 开发者创建 claim → 验证连接信息注入」。
# 1. 用 Helm 安装 Crossplane
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system --create-namespace
# 2. 安装 RDS provider
kubectl apply -f - <<'EOF'
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-rds
spec:
package: xpkg.upbound.io/upbound/provider-aws-rds:v1.14.0
EOF
# 3. 应用 XRD 与 Composition,确认 CRD 已注册
kubectl apply -f xrd-appdb.yaml
kubectl apply -f composition-appdb.yaml
kubectl api-resources | grep AppDB
开发者随后创建 claim(与 托管数据库 里 Terraform 侧的 RDS 配置形成对照):
apiVersion: platform.acme.io/v1alpha1
kind: AppDB
metadata:
name: orders-db
namespace: team-orders
spec:
parameters:
size: medium
engine: postgres
compositionSelector:
matchLabels:
provider: aws
writeConnectionSecretToRef:
name: orders-db-conn
验证整条链路:
kubectl get appdb orders-db -n team-orders
kubectl describe appdb orders-db -n team-orders # 看 conditions 与 events
kubectl get managed | grep orders-db # 看底层托管资源
kubectl get secret orders-db-conn -n team-orders -o jsonpath='{.data.host}' | base64 -d
8. 排错与生产清单
一句话总结: Crossplane 的排错是「从 claim 往下追到托管资源再追到 provider 日志」,绝大多数问题出在权限、引用未就绪或字段映射错误。
| 现象 | 常见原因 | 排查 |
|---|---|---|
claim 一直 Waiting | Composition 未匹配 | 看 claim 的 events 与 compositionRef |
Synced=False | 凭据无效/权限不足 | kubectl logs -n crossplane-system deploy/provider-aws-rds |
Ready=False 长时间 | 云资源异步创建中 | kubectl describe 看 atProvider 进度 |
| 引用报错 | *Ref 目标不存在 | 检查被引用资源是否已 Ready |
| 字段没生效 | patch 路径写错 | kubectl get composition -o yaml 核对 fromFieldPath |
kubectl get managed # 所有托管资源一览
kubectl logs -n crossplane-system -l pkg.crossplane.io/revision --tail=200
kubectl get composite <name> -o jsonpath='{.spec.compositionRef}' # 检查 composition 选中
生产清单:
- 凭据用 IRSA / Workload Identity,无静态密钥。
- 生产资源先
Orphan观察,确认后切Delete。 - Provider 版本锁定,升级走非生产验证。
- XR 的
spec.parameters有合理 enum 约束,避免开发者传错值。 - Composition 的 patch 覆盖了全部必填字段。
- 有
managementPolicies: ["Observe"]的纳管流程文档。 - 与 Terraform 的边界写清楚,同一资源只有一个管理者。
- 集群级 XR 与命名空间级 claim 的 RBAC 已分层,开发者不能越权改底层资源。
一句话收尾: Crossplane 把「基础设施即代码」推进到「基础设施即 API + 持续收敛」,它解决的是 Terraform 结构上解决不了的问题(持续自愈、开发者自助、与工作负载同生命周期),代价是更高的运维复杂度。选它的前提是团队已经在 Kubernetes 上做生产,并且愿意为控制平面付出运维成本。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。