TL;DR
- Crossplane is a CNCF Incubating project (Apache 2.0, Go) from Upbound that turns Kubernetes into a universal control plane — you declare external resources (AWS RDS, Azure Storage, GCP GKE, Snowflake databases, GitHub repos, Helm releases) as Kubernetes CRDs and Crossplane reconciles them continuously.
- Differs from Terraform / Pulumi by being continuous rather than imperative: there is no `apply` step, the desired state lives in etcd, and the controller drives reality toward it forever, surfacing drift as a standard Kubernetes condition.
- Core CRDs: `Provider` (packaged controller for a target API), `ProviderConfig` (credentials), Managed Resources (one CRD per external object), `CompositeResourceDefinition` (XRD — declares a new abstract CRD), `Composition` (template that fans an XR into many MRs), and Function pipelines (Wasm / container transformations replacing patch-and-transform).
- Major providers in mid-2026: provider-upjet-aws / azure / gcp (auto-generated from Terraform, thousands of resources each), provider-kubernetes, provider-helm, provider-terraform, provider-github, plus community providers for Snowflake, Datadog, Cloudflare, Vault and dozens of SaaS APIs.
- Yobibyte's multi-cloud and multi-region orchestration uses Crossplane-style composition under the hood — Yobitel operates the control plane on the customer's behalf so customers see a clean workspace API, not the underlying Composition graph.
Overview#
Crossplane's thesis is that the right home for infrastructure is a control plane, not a script. Terraform and Pulumi run as one-shot processes — someone executes `apply`, the world moves, then nothing watches the world until the next apply. If a human, an SRE break-glass, or another tool drifts the cloud state, you do not find out until the next apply, which could be minutes or months later. Drift becomes invisible until it bites in production. The state file (S3, a Pulumi service, or a local file) is the system of record and locking it serialises everyone — which is precisely the wrong primitive when a platform team and a dozen application teams all want to evolve the same infrastructure.
Crossplane replaces the script with a Kubernetes controller. The desired state of every external resource lives in etcd as a Kubernetes object. A Provider runs as a Kubernetes deployment, watches its managed CRDs, calls out to the target API (AWS, Azure, GCP, SaaS, etc.) and reconciles drift continuously. There is no `apply` — the controller is always running. When a human drifts the cloud out-of-band, the controller notices on its next reconcile loop and corrects it (or surfaces a `LateInitialization` / `Synced=false` condition for human review). Audit is automatic: every change is a standard Kubernetes audit event feeding the regional SIEM.
Crossplane started at Upbound in 2018, joined CNCF as a Sandbox project in 2020, and was promoted to Incubating in 2021. By mid-2026 it is on v1.18, supports Kubernetes 1.27-1.33, and ships a stable Function-pipeline composition model (functions replaced the original patch-and-transform composition in v1.14 and have been the recommended path since v1.16). It is dual-purpose: a tool you can use to build a single multi-cloud control plane, or a framework on which to build internal-platform APIs that present your developers with abstractions (`AppEnvironment`, `Database`, `MessagingTopic`) instead of raw cloud resources.
This entry helps you decide when Crossplane is the right answer (and when Terraform or Pulumi remains the better choice), understand the Provider / XRD / Composition model, design Function pipelines that stay maintainable, and operate the resulting control plane at scale. Yobibyte's multi-cloud orchestration is built using Crossplane-style composition primitives under the hood — but Yobibyte customers never see CompositeResourceDefinitions, Functions or the internal Composition graph; this entry documents Crossplane for teams operating their own platform.
Quick start#
The fastest sane path is the upstream Helm install, a single Provider (we use the example AWS provider here), a ProviderConfig pointing at IRSA / OIDC credentials, and one Managed Resource (a small S3 bucket). The five-step sequence below installs Crossplane, registers a Provider, sets credentials, and creates a real bucket reconciled continuously. Run it against any Kubernetes 1.27+ cluster with internet egress to the cloud API.
# 1. Install Crossplane via the upstream Helm chart
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install --wait crossplane crossplane-stable/crossplane \
--version "1.18.0" \
--namespace crossplane-system --create-namespace
# 2. Install the upjet-aws Provider (one of dozens available)
cat <<'YAML' | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata: { name: provider-aws-s3 }
spec:
package: xpkg.upbound.io/upbound/provider-aws-s3:v1.7.0
YAML
kubectl wait --for=condition=Healthy provider/provider-aws-s3 --timeout=5m
# 3. Configure the Provider with IRSA / OIDC credentials
cat <<'YAML' | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata: { name: default }
spec:
credentials:
source: IRSA # or InjectedIdentity / Secret
YAML
# 4. Create a Managed Resource — Crossplane reconciles continuously
cat <<'YAML' | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata: { name: example-ml-platform-bucket }
spec:
forProvider:
region: eu-west-2
objectLockEnabled: false
providerConfigRef: { name: default }
YAML
# 5. Inspect reconciliation status
kubectl get buckets.s3.aws.upbound.io -o wide
kubectl describe bucket example-ml-platform-bucket | grep -A2 ConditionsAlways use IRSA (EKS) / Workload Identity (GKE) / Azure AD Workload Identity (AKS) for ProviderConfig credentials rather than long-lived secrets in etcd. Crossplane supports `source: IRSA` and `source: InjectedIdentity` exactly so that you never need to store cloud keys in a Kubernetes Secret. This is what NCSC Cloud Security Principle 7 (Secure development) expects of a control plane that holds keys to production.
How it works#
Crossplane is built around four object types: Providers (packaged controllers), Managed Resources (one CRD per external object), Composite Resources (custom abstract types built on top of Managed Resources) and Functions (composable transformations that decide which MRs to produce for a given XR). The Provider model is the same pattern as a Kubernetes operator, just generalised to any API surface: a Provider declares a set of CRDs and runs a controller that reconciles each instance against the target API.
A Managed Resource (MR) is a Kubernetes object that represents one external resource — an AWS S3 bucket, a Snowflake database, a GitHub repository. Each MR has a `spec.forProvider` block (what you want), a `status.atProvider` block (what the cloud actually has) and standard conditions (`Synced`, `Ready`). The controller reconciles `forProvider` against the cloud at a regular interval (`--poll-interval`, default 1 min) and on any change event. Drift on the cloud side is corrected automatically; drift on the spec side is rolled out continuously.
Composite Resources (XRs) and Composite Resource Definitions (XRDs) are how platform teams publish abstractions. An XRD declares a new CRD — say `AppEnvironment` with fields `region`, `size`, `compliance` — and what fields the resulting `XAppEnvironment` should expose. A Composition (or, since v1.14, a Function pipeline) then says "when an XAppEnvironment is created, produce the following Managed Resources". Application teams consume the abstraction (`kubectl apply -f appenv.yaml`); the underlying cloud detail stays inside the platform team's Composition.
Function pipelines are the current recommended composition mechanism. A Composition references one or more Functions (Wasm or container-packaged); each Function receives the XR as input and returns a set of MRs as output. Functions are composable — a typical pipeline might run `function-patch-and-transform` (legacy compatibility), then `function-go-templating` (generate MRs from Go templates) or `function-kcl` (write composition logic in KCL), then `function-auto-ready` (decide when the XR is ready). Functions can call external APIs during composition, which makes complex logic — "look up the regional VPC ID from a registry, then create the subnets" — possible while keeping the control loop declarative.
- Provider — packaged controller + CRDs for a target API (AWS, Azure, GCP, GitHub, etc.).
- ProviderConfig — credentials and per-Provider configuration (IRSA, OIDC, injected identity, secret).
- Managed Resource (MR) — one CRD instance per external object; declares `forProvider` and exposes `atProvider`.
- CompositeResourceDefinition (XRD) — declares a new abstract CRD that wraps multiple MRs.
- Composite Resource (XR) — instance of the abstract type; cluster-scoped or namespace-scoped (with Claim).
- Claim — namespace-scoped pointer to an XR; the developer-facing object in many designs.
- Composition — template that maps an XR to a set of MRs, via either patch-and-transform or a Function pipeline.
- Function — Wasm or container-packaged transformation invoked as a step in the Composition pipeline.
- Configuration package — bundle of XRDs + Compositions + Functions, distributed via OCI like a Provider.
- Reconciliation — controller polls Kubernetes for spec changes and the target API for drift; both directions corrected to match.
The XR / XRD model is the value multiplier. Without XRDs, Crossplane is just a slower Terraform; with XRDs, Crossplane is the foundation of a platform-as-an-API. Plan to spend the first few weeks understanding when an abstraction earns its place versus when you should expose the raw MR.
Reference and specifications#
The fields below are the Crossplane core surface that matters in production. The reference covers Provider, ProviderConfig, Managed Resource convention, XRD, Composition and Function. Defaults are taken from v1.18.0.
| Resource / field | Type | Default | Purpose |
|---|---|---|---|
| Provider.spec.package | string | (required) | OCI image reference of the Provider package. |
| Provider.spec.packagePullPolicy | string | IfNotPresent | IfNotPresent | Always | Never. |
| Provider.spec.revisionActivationPolicy | string | Automatic | Automatic | Manual — control upgrades. |
| Provider.spec.revisionHistoryLimit | int | 1 | How many old ProviderRevisions to keep. |
| Provider.spec.controllerConfigRef.name | string | (none) | Per-Provider tuning (resources, args, tolerations). |
| ProviderConfig.spec.credentials.source | string | (required) | IRSA | InjectedIdentity | Secret | Filesystem | Environment | Webhook | Upbound. |
| ProviderConfig.spec.credentials.secretRef | ref | (if Secret) | Reference to credentials Secret. |
| MR.spec.forProvider | object | (required) | Desired state — schema is Provider-specific. |
| MR.spec.providerConfigRef.name | string | default | Which ProviderConfig drives this MR. |
| MR.spec.deletionPolicy | string | Delete | Delete | Orphan — controls behaviour on MR removal. |
| MR.spec.managementPolicies | list | [FullControl] | Modern replacement for deletionPolicy; per-action policy. |
| MR.status.atProvider | object | (populated) | Actual cloud state observed by the controller. |
| MR.status.conditions[*] | list | (populated) | Synced, Ready, LateInitialized, etc. |
| XRD.spec.group | string | (required) | API group for the abstract type. |
| XRD.spec.names.kind / plural | string | (required) | Kind name of the XR (e.g. XAppEnvironment). |
| XRD.spec.claimNames.kind / plural | string | (optional) | Kind name of the namespace-scoped Claim (e.g. AppEnvironment). |
| XRD.spec.versions[*].schema.openAPIV3Schema | object | (required) | OpenAPI schema for the XR spec / status. |
| XRD.spec.defaultCompositionRef.name | string | (optional) | Default Composition for XRs of this type. |
| XRD.spec.enforcedCompositionRef.name | string | (optional) | Force this Composition; ignore user override. |
| Composition.spec.compositeTypeRef | ref | (required) | Which XR kind this Composition targets. |
| Composition.spec.mode | string | Resources | Resources (legacy) | Pipeline (recommended, function-based). |
| Composition.spec.pipeline[*].step | string | (required) | Name of the pipeline step. |
| Composition.spec.pipeline[*].functionRef.name | string | (required) | Function to invoke at this step. |
| Composition.spec.pipeline[*].input | object | (per Function) | Function-specific configuration (templates, KCL source, etc.). |
| Function.spec.package | string | (required) | OCI image of the Function package. |
| Function.spec.packagePullPolicy | string | IfNotPresent | As Provider. |
| Configuration.spec.package | string | (required) | OCI image bundling XRDs + Compositions + Function refs. |
| Configuration.spec.dependsOn | list | [] | Other Providers / Configurations required. |
| Crossplane CLI `crossplane render` | command | n/a | Dry-run a Composition pipeline against a sample XR. |
| Crossplane CLI `crossplane validate` | command | n/a | Static-check XRDs / Compositions before commit. |
`MR.spec.deletionPolicy: Orphan` is the lifesaver when you start managing pre-existing infrastructure. Without it, deleting a Crossplane MR for a production database deletes the database. Use the modern `managementPolicies: [Observe]` to take over an existing resource without granting Crossplane mutate authority until you trust the model.
Workload patterns#
Three patterns cover the bulk of production Crossplane deployments. Each pattern uses a different combination of XRDs, Compositions and Functions; pick the one closest to your operating model.
Pattern A — multi-cloud platform abstraction. Application teams want "an environment"; the platform team curates an `AppEnvironment` XRD with fields `cloud`, `region`, `size`, `compliance`. A Composition fans the XR into the right cloud's MRs (VPC + private subnets + load balancer + secret store), driven by a Function pipeline that branches on `spec.cloud`. The dev team submits the same `AppEnvironment` YAML for AWS, Azure or GCP; the underlying MRs are different. This is the textbook Crossplane use case and the design pattern Yobibyte builds on internally.
Pattern B — internal database-as-a-service. Application teams need a database; the platform team curates a `Database` XRD with fields `engine` (postgres / mysql), `size`, `region`, `backupRetention`. A Composition produces an RDS instance + subnet group + parameter group + IAM role + secret in AWS Secrets Manager, with the connection string written back to a Kubernetes Secret in the consuming namespace. Application teams `kubectl apply` a Claim; Crossplane provisions; the connection Secret is mounted into the app pod. Same pattern works for Snowflake (via provider-snowflake) or self-hosted Postgres on Kubernetes (via provider-kubernetes + Postgres Operator).
Pattern C — declarative GitHub / SaaS lifecycle. Crossplane is not limited to cloud infrastructure. provider-github lets you declare repositories, branch protection rules, team memberships and secrets as MRs reconciled from etcd. provider-vault manages Vault policies, roles and secret paths. provider-snowflake manages databases, schemas and users. A Composition can stitch these together — "give every new service its repo, its Vault role, its Snowflake schema and its bucket" — in a single declarative bundle.
# Pattern B: a "Database" XRD + Function-pipeline Composition
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.platform.example-org.io
spec:
group: platform.example-org.io
names: { kind: XDatabase, plural: xdatabases }
claimNames: { kind: Database, plural: databases }
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
engine: { type: string, enum: [postgres, mysql] }
size: { type: string, enum: [small, medium, large] }
region: { type: string }
backupRetention: { type: integer, default: 7 }
required: [engine, size, region]
---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xdatabases.platform.example-org.io
spec:
compositeTypeRef:
apiVersion: platform.example-org.io/v1alpha1
kind: XDatabase
mode: Pipeline
pipeline:
- step: render-rds
functionRef: { name: function-go-templating }
input:
apiVersion: gotemplating.fn.crossplane.io/v1beta1
kind: GoTemplate
source: Inline
inline:
template: |
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
metadata:
name: {{ .observed.composite.resource.metadata.name }}-rds
spec:
forProvider:
engine: {{ .observed.composite.resource.spec.parameters.engine }}
instanceClass: {{ if eq .observed.composite.resource.spec.parameters.size "small" }}db.t3.medium{{ end }}
region: {{ .observed.composite.resource.spec.parameters.region }}
backupRetentionPeriod: {{ .observed.composite.resource.spec.parameters.backupRetention }}
- step: auto-ready
functionRef: { name: function-auto-ready }Yobibyte exposes multi-cloud workspaces through a similar Crossplane-style composition surface at the outcome level — customers declare a workspace and a region intent, Yobibyte produces the underlying capacity on Yobitel NeoCloud or partner clouds. The composition mechanics are Yobitel-internal; the customer-facing API stays clean. This entry deliberately does not enumerate Yobibyte's internal Compositions — see [[yobibyte]] for the customer surface.
Sizing and capacity planning#
Crossplane's control plane is light at the centre but Providers can be heavy at the edge. The Crossplane controller itself runs as a small Go process (~200 mCPU, 256 MiB) — it manages package installation, function execution and XR / Composition reconciliation. Each Provider runs its own deployment; provider-upjet-aws is the largest of the common ones because it carries thousands of MR types in a single binary.
- Split provider-upjet-aws into family-scoped providers (provider-aws-rds, provider-aws-s3, provider-aws-ec2) in production — smaller binaries, faster startup, cheaper memory.
- ControllerConfig per Provider lets you set requests / limits / tolerations / nodeSelector — pin Providers to a dedicated control-plane node pool.
- Function cold-starts dominate XR latency — keep Function images small (~50 MB) and prewarm popular Functions if XR creation latency matters.
- Cloud API rate limits are the real ceiling — AWS throttles describe / mutate calls per account; spread Providers across accounts for parallelism.
- Yobibyte's multi-region control plane runs Crossplane with per-region Provider deployments inside each Yobitel NeoCloud region's sovereign perimeter; customers see a unified API across regions without cross-region calls.
| Component | CPU | Memory | Notes |
|---|---|---|---|
| crossplane (core) | 100-300 mCPU | 256-512 MiB | Package manager, Function executor, XR reconciler. |
| provider-upjet-aws | 500 mCPU - 2 vCPU | 1-4 GiB | Single binary covering ~2,000 AWS resources; biggest of the upjet providers. |
| provider-upjet-azure | 500 mCPU - 2 vCPU | 1-4 GiB | Same as AWS — large generated provider. |
| provider-upjet-gcp | 500 mCPU - 2 vCPU | 1-4 GiB | Same — split into per-family providers in newer releases. |
| provider-kubernetes / provider-helm | 100-300 mCPU | 256-512 MiB | Light; handle Kubernetes-shaped resources only. |
| Function pod (per Function invocation) | 200 mCPU | 256-512 MiB | Cold-start ~50-300 ms; warm-pool optional. |
| Per MR overhead | n/a | ~5-20 KiB etcd | MR + status; status grows with cloud-side complexity. |
| Per XR overhead | n/a | ~10-50 KiB etcd | XR + connection details + composition revision history. |
| Reconcile poll interval | 1 min default | n/a | Tuneable via `--poll-interval`; tighter = more API calls to cloud. |
Limits and quotas#
Crossplane's limits in production are dominated by upstream cloud-API quotas and by etcd object count. The limits below are the envelope teams hit on a real cluster.
| Dimension | Soft limit | Hard limit | Mitigation |
|---|---|---|---|
| Managed Resources per cluster | ~10,000 | ~100,000 | Past 10k MRs, etcd compaction and apiserver list cost matter; partition by control-plane cluster. |
| Composite Resources per cluster | ~1,000 | ~10,000 | Each XR fans out to many MRs; budget accordingly. |
| XRDs per cluster | ~50 | ~500 | Each XRD is a new CRD; apiserver discovery cost scales linearly. |
| Compositions per XRD | ~10 | ~50 | Multiple Compositions per XRD for different size / cloud variants. |
| Function pipeline steps | ~5 | ~20 | Each step adds latency; pipelines past 10 steps are usually a smell. |
| Cloud API rate limit per account | varies | varies | AWS DescribeInstances ~100/s; spread across accounts. |
| Reconcile poll interval | 1 min | 5 s | Lower polls catch drift faster but burn API quota. |
| Provider startup time | 30-60 s | 5 min | Cold-load of generated CRDs; pre-pull image. |
Observability#
Crossplane and every Provider expose Prometheus metrics on `:8080/metrics`. The core surface covers package install state, reconcile rates and Function execution. Providers add per-MR-type reconciliation metrics. Combined with standard Kubernetes events on MR transitions, this is enough to operate Crossplane at scale and to evidence SLA against composition contracts.
- `crossplane_managed_resource_seconds_total` — total reconcile time per MR type; the cost signal.
- `crossplane_managed_resource_apply_total{result=...}` — apply success / failure rate per MR type.
- `crossplane_composition_revision_state{state=...}` — Composition revision rollout status.
- `crossplane_function_request_duration_seconds` — Function execution latency by step.
- `crossplane_function_response_errors_total` — Function failure rate.
- `workqueue_depth{controller=...}` — Provider controller backlog.
- `workqueue_unfinished_work_seconds` — oldest in-flight reconcile; alert on long-tail.
- `provider_aws_api_calls_total` / `_throttled_total` — AWS API call cost and throttling rate.
- Per-MR conditions — `Synced=False` and `Ready=False` are the right paging signals.
# Prometheus alerts for Crossplane in production
groups:
- name: crossplane-sla
interval: 30s
rules:
- alert: CrossplaneMRSyncFailing
expr: |
sum by (kind) (
kube_customresource_status_condition{condition="Synced",status="false"}
) > 0
for: 15m
labels: { severity: warning }
annotations:
summary: "Crossplane MR kind {{ $labels.kind }} has Synced=False"
- alert: CrossplaneFunctionErrors
expr: rate(crossplane_function_response_errors_total[10m]) > 0.1
for: 10m
labels: { severity: warning }
annotations:
summary: "Composition Function errors above threshold"
- alert: ProviderAPIThrottled
expr: rate(provider_aws_api_calls_throttled_total[5m]) > 1
for: 10m
labels: { severity: critical }
annotations:
summary: "AWS throttling Crossplane provider — reduce poll interval or split account"
- alert: CrossplaneWorkqueueBacklog
expr: workqueue_depth{controller=~".*\\.crossplane.io"} > 200
for: 15m
labels: { severity: critical }
annotations:
summary: "Crossplane controller backlog growing — investigate Provider health"Cost and FinOps#
Crossplane software is free (Apache 2.0). Upbound sells a managed control plane and enterprise compositions, but the open-source path is fully production-capable. The real FinOps story is two-sided: Crossplane reduces drift and the resulting cost overruns (no orphaned cloud resources that nobody knows about), and it enables platform abstractions that constrain what dev teams can spin up (a `Database` XR with `size: small` is cheaper to operate than a free-for-all `RDS Instance`).
- Drift recovery — measure cloud bill items not represented by an MR (or vice versa); Crossplane reconciles both directions, eliminating orphan cost.
- Abstraction-driven cost ceilings — XRDs can encode `size` to specific instance classes; dev teams cannot bypass to a 16xlarge by accident.
- Yobitel NeoCloud pricing for Crossplane-managed capacity — H100 SXM5 at $3.00/GPU/hr on-demand; reserved at $2.00 with annual commit. Composition can target the right SKU per workload.
- Cloud API call cost — most clouds do not charge for describe calls, but rate-limit them; tighter poll intervals burn quota that other tools also need.
- Yobibyte's customer-facing workspace pricing wraps the Crossplane-style composition cost — Yobitel runs the control plane on Yobitel-operated capacity and bills customers in USD per workspace consumption, not per MR.
Security and compliance#
Crossplane's security model has two facets: the credentials Providers hold to mutate the cloud, and the RBAC governing who can create XRs, MRs and Compositions. ProviderConfig is the credential boundary — use `source: IRSA` (EKS) / `source: InjectedIdentity` (GKE Workload Identity, Azure AD Workload Identity) so that no long-lived cloud key ever lives in etcd. For sovereign-cloud builders, the entire Crossplane control plane sits inside the sovereign perimeter; calls go from a Provider running on the customer-region cluster to the regional cloud endpoint, with no SaaS hop.
Multi-tenant isolation comes from RBAC plus XRD claim scoping. Cluster admins create XRDs and Compositions; application teams create Claims in their namespace. The Claim is namespace-scoped so a tenant cannot affect another tenant's resources. ProviderConfigs can be per-tenant, each pointing at a tenant-scoped IAM role — combined with `providerConfigRef` on each MR, this enforces blast-radius isolation: tenant A's MRs use tenant A's IAM role, which has no permission against tenant B's account.
Audit and accountability are standard Kubernetes. Every MR / XR / Composition mutation is an audit event with the user, timestamp and diff. For UK NCSC OFFICIAL workloads, this satisfies Principle 9 (Secure user management) and Principle 13 (Audit information for users) on Yobitel-operated clusters. For SOC 2 / ISO 27001, the etcd audit log plus standard Crossplane metrics provide the evidence trail. For GDPR Article 32, Crossplane processes no personal data itself but reconciles infrastructure that may; the relevant evidence is that the control plane ran inside the sovereign perimeter.
Never grant Crossplane the cloud role wholesale. Scope the IAM role to exactly the MR types the Provider needs to manage. A blast-radius leak (Crossplane compromised, Provider creates arbitrary cloud resources) is the highest-impact attack on a Crossplane install — and entirely preventable with least-privilege IAM.
Migration and alternatives#
Most clusters that adopt Crossplane migrate from one of three starting points: Terraform (script-driven, state-file-based), Pulumi (script-driven with general-purpose languages), or a hand-rolled internal automation (CI jobs + custom controllers + scripts). Each has a different migration path.
Crossplane and Terraform are not mutually exclusive. A common 2026 pattern: Terraform provisions the foundational cluster once (VPC, EKS, Crossplane install, ArgoCD bootstrap) at platform birth; Crossplane then manages everything else continuously, including provisioning further AWS resources via provider-upjet-aws. The split is between one-shot bootstrap (Terraform) and continuously-reconciled platform (Crossplane). Pulumi-to-Crossplane is more common as a wholesale replacement because Pulumi already has a code-first feel that XRDs partially replace.
| From | Effort | Risk | Notes |
|---|---|---|---|
| No IaC (manual cloud-console) | High | Medium | Adopt MRs incrementally; start with `managementPolicies: [Observe]` to take over existing resources without mutating. |
| Terraform monorepo | Medium-High | Medium | Keep Terraform for bootstrap; migrate post-bootstrap resources to MRs over time. |
| Pulumi monorepo | Medium-High | Medium | Pulumi state can be exported; reproduce as MRs; abstract upward with XRDs. |
| Hand-rolled CI scripts | High | Medium | Inventory the scripts; produce one Composition per script equivalent; cut over per-resource. |
| Cloud-native CDK (AWS CDK, Bicep) | Medium | Low | CDK output is CloudFormation; Crossplane reconciles past CloudFormation just fine. |
| Existing Kubernetes Operators only | Low | Low | Crossplane complements Operators; provider-kubernetes lets you keep them. |
| vs Yobibyte managed alternative | n/a | n/a | If you would rather not own a Crossplane control plane at all, Yobibyte exposes the equivalent customer surface (declarative workspaces, region-pinned, sovereign-scoped) on Yobitel-managed tenancies — Yobitel runs the control plane and the customer never sees XRDs or Functions. See `yobibyte` and `neocloud`. |
Troubleshooting#
The error patterns below cover roughly 80% of production Crossplane incidents observed on Yobitel-operated control planes and on the upstream community tracker.
| Symptom | Cause | Fix |
|---|---|---|
| MR stuck Synced=False forever | Cloud-side error in `status.conditions[*].message` — typically IAM, naming or quota. | Read the condition message; fix the IAM / quota; reconcile resumes. |
| MR keeps recreating | Spec drift between two controllers (e.g. Terraform also managing it). | Set `managementPolicies: [Observe]` until one owner is chosen. |
| Provider Pod CrashLoopBackOff | OOM on large provider-upjet binary, or missing CRDs. | Split provider into family-scoped providers; raise memory limit. |
| XRD created but Claim not accepted | RBAC missing for the Claim group/kind in the tenant namespace. | Grant the tenant `create/get/list` on the Claim kind. |
| Composition Function errors | Function input schema mismatch with Composition spec. | Run `crossplane render` locally; fix input; revalidate. |
| Function cold-start timeout | Function image large or registry slow. | Pre-pull image; shrink image; warm-up pool. |
| Cloud API throttling | Poll interval too tight or too many MRs per account. | Raise `--poll-interval`; spread MRs across accounts. |
| Composition revision rollback unwanted | Auto rollback on `Synced=False` from a noisy MR. | Pin to a Composition revision via `compositionRevisionRef`. |
| Provider upgrade breaks MRs | Generated CRD schema changed between versions. | Pin Provider version; use staged rollout via `revisionActivationPolicy: Manual`. |
| Orphaned cloud resource after MR delete | `deletionPolicy: Orphan` or quota error during delete. | Inspect audit log; delete cloud-side; reconcile. |
Where this fits in the Yobitel stack#
Crossplane is the multi-cloud and multi-region orchestration substrate inside Yobibyte. When a customer declares a workspace in a particular Yobitel NeoCloud region with a particular sovereignty posture and a particular GPU mix, the engine that turns that customer intent into the right cloud resources on the right capacity is built using Crossplane-style composition primitives. Yobitel customers never see the underlying CompositeResourceDefinitions, Compositions or Function pipelines — the workspace API is the customer surface; Crossplane is one of the engines underneath. The recipe stays inside Yobitel; the outcome (a workspace, fully provisioned, sovereign-scoped, GPU-backed) is what customers consume.
On Yobitel-managed clusters Crossplane is installed via GitOps from the platform's Argo CD root, with per-region Provider deployments inside each sovereign perimeter. Customer-facing workspace lifecycles are reconciled continuously — drift correction, region failover and capacity rebalancing all happen via Crossplane's reconciliation loop without operator intervention. InferenceBench publishes benchmark provider feeds reconciled by Crossplane Providers so that adding a new model or a new endpoint is a Configuration package change rather than a manual edit.
For UK and EU sovereign workloads, Crossplane satisfies the platform's continuous-control-plane requirement (Principle 5 — Operational security on NCSC), the audit-evidence requirement (Principle 13 — Audit information for users), and the customer-controlled-key requirement (via per-tenant ProviderConfig IRSA). Customers who consume Yobibyte get this automatically; customers who run their own clusters with Yobitel Managed Operations get Crossplane installed, configured, and on-call covered. Customers who want to build their own platform on Crossplane and run it on Yobitel NeoCloud get the substrate they need from the underlying compute and the freedom to wire their own XRDs above it.
References
- Crossplane Documentation · Crossplane Project
- crossplane on GitHub · GitHub (crossplane)
- CNCF Crossplane Project Page · CNCF
- Upbound Marketplace (Providers, Functions, Configurations) · Upbound
- Crossplane Composition Functions Guide · Crossplane Docs