- name
- loom-crossplane
- description
- Cloud-native infrastructure management with Crossplane via Kubernetes APIs.
- allowed-tools
- ["Read","Grep","Glob","Edit","Write","Bash"]
- triggers
- ["crossplane","XRD","composition","claim","provider","managed resource","composite resource","infrastructure API","platform engineering","platform API","infrastructure abstraction","self-service infrastructure","kubernetes infrastructure","cloud control plane"]
# Crossplane Infrastructure Management
Crossplane extends Kubernetes to manage cloud infrastructure using declarative APIs. It enables platform teams to build internal cloud platforms with self-service capabilities.
## Architecture Overview
### Core Components
1. **Providers**: Kubernetes controllers that provision infrastructure in external systems (AWS, GCP, Azure, etc.)
2. **Managed Resources (MRs)**: Custom resources representing external infrastructure (S3 buckets, RDS instances, etc.)
3. **Composite Resources (XRs)**: Higher-level abstractions composed of multiple managed resources
4. **Composite Resource Definitions (XRDs)**: Schemas defining composite resource types
5. **Compositions**: Templates that map XRs to managed resources with transformation logic
6. **Claims**: Namespace-scoped resources that provision composite resources for application teams
7. **Composition Functions**: Extension points for complex transformation logic
### Resource Hierarchy
```text
Claim (namespace-scoped) -> Composite Resource (cluster-scoped) -> Managed Resources -> Cloud Infrastructure
```
## Installation and Setup
### Install Crossplane
```bash
# Add Crossplane Helm repository
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
# Install Crossplane
# Composition functions are enabled by default since v1.14 — no feature-gate flag needed.
helm install crossplane \
crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace \
--wait
# Verify installation
kubectl get pods -n crossplane-system
```
### Install Crossplane CLI
```bash
# Install CLI for local development
curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh
sudo mv crossplane /usr/local/bin/
# Verify CLI
crossplane --version
```
## Provider Configuration
### AWS Provider
> **v2 note:** `ControllerConfig` (`pkg.crossplane.io/v1alpha1`) was deprecated in v1.11 and **removed in Crossplane v2**. Configure provider runtime with `DeploymentRuntimeConfig` (`pkg.crossplane.io/v1beta1`, beta-enabled by default since v1.14) referenced via `runtimeConfigRef`. Migrate any existing config with `crossplane beta convert deployment-runtime controller-config.yaml -o deployment-runtime-config.yaml` before upgrading.
```yaml
# providers/aws-provider.yaml
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-s3
spec:
package: xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0
runtimeConfigRef:
name: aws-runtime
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-rds
spec:
package: xpkg.upbound.io/upbound/provider-aws-rds:v1.1.0
runtimeConfigRef:
name: aws-runtime
---
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: aws-runtime
spec:
deploymentTemplate:
spec:
selector: {}
template:
spec:
securityContext:
fsGroup: 2000
containers:
- name: package-runtime
args:
- --poll=1m
- --max-reconcile-rate=100
```
### Provider Authentication
```bash
# Create AWS credentials secret
kubectl create secret generic aws-creds \
-n crossplane-system \
--from-file=creds=/path/to/aws-credentials.txt
# credentials.txt format:
# [default]
# aws_access_key_id = AKIAIOSFODNN7EXAMPLE
# aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```
```yaml
# providers/aws-provider-config.yaml
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: creds
```
### GCP Provider
```yaml
# providers/gcp-provider.yaml
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-gcp-storage
spec:
package: xpkg.upbound.io/upbound/provider-gcp-storage:v1.1.0
---
apiVersion: gcp.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
projectID: my-gcp-project
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: gcp-creds
key: creds.json
```
## Managed Resources
### Direct Managed Resource Usage
```yaml
# managed-resources/s3-bucket.yaml
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: my-app-data-bucket
spec:
forProvider:
region: us-west-2
tags:
Environment: production
ManagedBy: crossplane
providerConfigRef:
name: default
deletionPolicy: Delete
```
```yaml
# managed-resources/rds-instance.yaml
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
metadata:
name: my-postgres-db
spec:
forProvider:
region: us-west-2
allocatedStorage: 20
engine: postgres
engineVersion: "14.7"
instanceClass: db.t3.micro
dbName: myappdb
username: dbadmin
passwordSecretRef:
namespace: crossplane-system
name: db-password
key: password
skipFinalSnapshot: true
publiclyAccessible: false
vpcSecurityGroupIdSelector:
matchLabels:
role: database
providerConfigRef:
name: default
writeConnectionSecretToRef:
namespace: production
name: postgres-connection
```
## Composite Resource Definitions (XRDs)
### Database XRD
The XRD **is your platform API** — the OpenAPI schema is your only guardrail against consumer misconfiguration. Enforce with `enum`, `minimum`/`maximum`, `pattern`, `default`, and `required`. `spec.group` and `spec.names` are **immutable**: changing them requires deleting/recreating the XRD, which cascades to all its XRs.
**v1 (below) vs v2:** the v1 form uses `claimNames` (cluster-scoped XR + namespaced claim) and `connectionSecretKeys`. New XRDs should use `apiextensions.crossplane.io/v2` — **namespaced by default** (`scope: Namespaced`), **no claims** (`claimNames` gone), and `connectionSecretKeys` **does not apply** (aggregate via `function-patch-and-transform`'s `writeConnectionSecretToRef.patches`).
```yaml
# xrds/database-xrd.yaml (v1 legacy-cluster form with claims)
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.database.example.com # must be <plural>.<group>
spec:
group: database.example.com
names: { kind: XPostgreSQLInstance, plural: xpostgresqlinstances }
claimNames: { kind: PostgreSQLInstance, plural: postgresqlinstances } # v1 only
connectionSecretKeys: [username, password, endpoint, port] # v1 only
versions:
- name: v1alpha1
served: true
referenceable: true # exactly one version must be referenceable
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB: { type: integer, default: 20, minimum: 20, maximum: 1000 }
size: { type: string, enum: [small, medium, large], default: small }
networkRef: # nested object with its own required field
type: object
properties: { id: { type: string } }
required: [id]
required: [size, networkRef]
required: [parameters]
status: # ToCompositeFieldPath patches write here
type: object
properties:
address: { type: string }
```
For the **v2** form, drop `claimNames`/`connectionSecretKeys` and add `scope: Namespaced` (XRs then live in a namespace and follow standard Kubernetes RBAC):
```yaml
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
spec:
scope: Namespaced # default in v2; also LegacyCluster (v1 behavior) or Cluster
group: platform.example.com
names: { kind: XAppPlatform, plural: xappplatforms }
# ... versions/schema as above
```
## Compositions
A Composition implements an XRD's API by templating one or more resources. **On v2, `mode: Pipeline` (with `function-patch-and-transform`) is the ONLY supported form** — native `mode: Resources` (`spec.resources`/`spec.patchSets`) was deprecated in v1.17 and removed in v2. See the full Pipeline example under *Composition Functions*; migrate legacy compositions with `crossplane beta convert pipeline-composition old.yaml -o new.yaml`.
### Patch & Transform vocabulary
These patch/transform types are the workhorses of both legacy `mode: Resources` **and** `function-patch-and-transform` input — the syntax is identical, so knowledge transfers verbatim.
| Patch `type` | Direction | Use |
| ----------------------- | ----------------- | ------------------------------------------------------ |
| `FromCompositeFieldPath`| XR → composed MR | Push a user parameter onto a managed resource field |
| `ToCompositeFieldPath` | composed MR → XR | Surface `status.atProvider.*` back to the XR status |
| `CombineFromComposite` | many XR → one MR | Build a value (e.g. name) from multiple fields via `fmt`|
| `PatchSet` (+`patchSets`)| — | Reuse a named patch group across resources (e.g. common tags)|
| Transform `type` | Use / gotcha |
| ---------------- | ---------------------------------------------------------------------------- |
| `map` | Enum → value (`small`→`db.t3.micro`). **Errors if key absent** — prefer `match` with `fallbackTo` (see *Expert Practices → Idioms*) |
| `string` | `fmt: "%s-connection"` for derived names — must stay deterministic across reconciles |
| `math` | Scale a numeric field |
| `convert` | Type coercion (string↔int↔bool) |
```yaml
# Inside mode: Pipeline -> function-patch-and-transform input (see Composition Functions)
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
engine: postgres
username: dbadmin
# Selectors let Crossplane resolve refs & order deps automatically:
vpcSecurityGroupIdSelector: { matchControllerRef: true }
dbSubnetGroupNameSelector: { matchControllerRef: true }
writeConnectionSecretToRef: { namespace: crossplane-system }
patches:
- type: PatchSet
patchSetName: common-tags
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map: { small: db.t3.micro, medium: db.t3.medium, large: db.m5.large }
- type: FromCompositeFieldPath # derive a stable secret name from UID
fromFieldPath: metadata.uid
toFieldPath: spec.writeConnectionSecretToRef.name
transforms: [{ type: string, string: { fmt: "%s-connection" } }]
- type: ToCompositeFieldPath # surface endpoint to XR status
fromFieldPath: status.atProvider.endpoint
toFieldPath: status.address
- type: FromCompositeFieldPath # LOAD-BEARING patch: fail loud, don't skip
fromFieldPath: spec.parameters.networkRef.id
toFieldPath: spec.forProvider.vpcId
policy: { fromFieldPath: Required }
```
**Environment-driven variation** (dev/staging/prod) is just a `map` transform per field (e.g. `environment → multiAz`, `→ numCacheClusters`, `→ backupRetentionDays`). Conditional resource **inclusion** (create a cache only when `enableCache=true`) is NOT expressible in patch-and-transform — it needs a templating function (see *Conditional Resource Creation* and *Expert Practices*).
## Claims (Self-Service Resources)
### Database Claim
```yaml
# claims/my-app-database.yaml
apiVersion: database.example.com/v1alpha1
kind: PostgreSQLInstance
metadata:
name: my-app-db
namespace: production
spec:
parameters:
size: medium
storageGB: 100
engineVersion: "14.7"
highAvailability: true
backupRetentionDays: 30
networkRef:
id: vpc-0a1b2c3d4e5f6g7h8
compositionSelector:
matchLabels:
provider: aws
database: postgresql
writeConnectionSecretToRef:
name: my-app-db-connection
```
### Application Platform Claim
```yaml
# claims/my-app-platform.yaml
apiVersion: platform.example.com/v1alpha1
kind: AppPlatform
metadata:
name: my-application
namespace: team-alpha
spec:
parameters:
environment: prod
appName: my-app
region: us-west-2
databaseSize: large
enableCache: true
compositionSelector:
matchLabels:
provider: aws
writeConnectionSecretToRef:
name: my-app-platform-secrets
```
## Composition Functions
Composition Functions enable complex transformation logic using WebAssembly or container-based functions.
### Function Configuration
```yaml
# compositions/postgres-with-functions.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: postgres.function-based.aws.database.example.com
spec:
compositeTypeRef:
apiVersion: database.example.com/v1alpha1
kind: XPostgreSQLInstance
mode: Pipeline
pipeline:
# Step 1: Patch and transform resources
# Note: function-auto-ready only marks resources Ready — it does NOT generate
# passwords or accept a Resources input. For secret/password generation use
# function-kcl (random string) or External Secrets Operator; never write a
# literal placeholder into a Secret.
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
patchSets:
- name: common-tags
patches:
- type: FromCompositeFieldPath
fromFieldPath: metadata.labels[crossplane.io/claim-name]
toFieldPath: spec.forProvider.tags.ClaimName
- type: FromCompositeFieldPath
fromFieldPath: metadata.labels[crossplane.io/claim-namespace]
toFieldPath: spec.forProvider.tags.ClaimNamespace
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
engine: postgres
username: dbadmin
skipFinalSnapshot: true
patches:
- type: PatchSet
patchSetName: common-tags
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.medium
large: db.m5.large
# Step 2: Mark as ready
View on GitHub