Crossplane 控制平面

用 Kubernetes 控制平面管理云资源:Crossplane 的托管资源与 ProviderConfig、复合资源与组合模板、Provider 体系与 Functions、持续协调循环消除漂移,以及与 Terraform 的边界划分与选型取舍。

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 对象)
deletionPolicyDelete / 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 是被动持续:漂移存在的窗口只有「下一个调谐周期」那么长。

维度TerraformCrossplane
检测时机手动/定时 plan持续调谐
收敛动作人工 apply自动
误纠风险低(人审核)有(见 5.2)
审计粒度plan 产物 + PRKubernetes 审计日志

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 能力对比

维度TerraformCrossplane
执行模型一次性 apply持续调谐
状态存储远程 state 文件Kubernetes etcd
抽象能力模块(Module)复合资源(XR + Composition)
依赖表达depends_on / 引用*Ref / *Selector
开发者自助需懂 Terraform只需懂 kubectl
生态覆盖极广快速增长但仍有缺口
变更评审PR + planGitOps 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 一直 WaitingComposition 未匹配看 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 上做生产,并且愿意为控制平面付出运维成本。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. 引导与状态后端自举
  2. 数据平台基础设施即代码
  3. 从 CloudFormation 迁移到 Terraform