Skip to main content

Cheatsheet

A reference you keep open. Every term, command, and link you'll need during the workshop is on this page. Skim it once, then come back whenever a module uses a word you don't remember.

1.1 Kubernetes in two screens

The shape of the API

ThingWhat it isExample
ResourceThe smallest API object you can createPod, Service
KindThe resource type namePod
NamespaceA scope for resources that are not cluster-widedefault, crossplane-system
Namespaced vs cluster-scopedWhether a resource lives inside a namespace (most things) or at the cluster level (Node, Namespace, CRDs)
CRD (CustomResourceDefinition)A way for controllers to register new kinds with the API serverproviders.pkg.crossplane.io

Commands you'll run

kubectl config current-context              # which cluster am I talking to
kubectl get namespaces # list namespaces
kubectl get pods -n <namespace> # pods in a namespace
kubectl get <kind> <name> -o yaml # print one resource as YAML
kubectl describe <kind> <name> -n <ns> # show events + conditions
kubectl logs <pod> -n <namespace> # stream logs
kubectl apply -f <file.yaml> # create or update from YAML
kubectl delete -f <file.yaml> # remove what was applied
kubectl api-resources | grep <word> # find which kinds exist

Put the verb first (kubectl get pods -n foo, not kubectl -n foo get pods). It reads left-to-right and caches better in your shell history.

Inline manifests use single-quoted heredocs so $ isn't expanded by the shell:

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: example
EOF

Conditions

Crossplane resources tell you their state through status.conditions. The two you'll see most:

ConditionMeaning
type: Ready, status: TrueThe object is fully reconciled and working
type: Synced, status: TrueCrossplane has the latest spec and is trying to apply it

If Ready=True and Synced=True, you're done. If either is False, kubectl describe shows you why.

Controllers and the operator pattern

A controller is a long-running Kubernetes program that watches resources of certain kinds and takes action when their declared state and actual state diverge. It runs in a loop: read the spec, compare to reality, reconcile (create / update / delete to match), repeat. This is the operator pattern.

The Deployment controller you've used since day one of Kubernetes works this way: it sees a Deployment with replicas: 3, counts how many Pods exist, creates or deletes Pods to match. The Crossplane core controller is one more controller in the same family — its spec describes things outside the cluster (cloud resources, Helm releases) or things you've defined yourself with an XRD. Same loop, broader scope.

When this workshop says "Crossplane reconciles" or "the provider watches", that's what's happening: a controller reading the spec you applied and making something real match it.

1.2 Tools worth installing

You don't need any of these to finish the workshop — plain kubectl is enough. They make things nicer.

ToolWhat it doesInstall
helmInstalls and upgrades Kubernetes applications packaged as chartsbrew install helm
k9sFull-screen terminal dashboard for watching resourcesbrew install k9s
kubectxSwitch between kubeconfig contexts: kubectx prodbrew install kubectx
kubensSwitch default namespace: kubens crossplane-systemShips with kubectx

1.3 Crossplane terminology

The big picture

The problem Crossplane solves. kubectl apply is great for a single team's manifests. But large platforms accumulate dozens of low-level objects (Deployments, Services, ConfigMaps, cloud IAM roles, DNS records, Helm releases) that have to be coordinated for any one user-facing thing — "an app", "a database", "a tenant environment". Crossplane lets you publish your team's own API — a single Application kind, a single Database kind — that fans out into all those low-level objects. Other teams consume your platform with one YAML file instead of dozens.

How it works. Crossplane is a controller (see §1.1 above). It watches two kinds of inputs:

  1. External-API resources via Providers. A Provider package teaches Crossplane to talk to a specific external API — AWS, GCP, Helm, another Kubernetes cluster. You apply a Bucket or Release resource; the Provider's controller calls the external API to make it real.
  2. Your own composite kinds via XRDs + Compositions. You declare a new kind with an XRD (schema) and a Composition (recipe); users apply an XR (an instance of your kind), and Crossplane fans it out into the resources the recipe declares — either native Kubernetes objects directly (in v2) or external resources via Providers.

You'll do the second of these in modules 4-6 of this workshop, then add a Provider in module 7.

Core objects

The Scope column has a subtle split. CRDs and XRDs are themselves cluster-scoped objects — but they define a kind whose own scope (Namespaced vs Cluster) is set in the definition's spec.scope. The "Defines a kind that is" column captures that.

ObjectWhat it isScope (the object itself)Defines a kind that is
CRDKubernetes Custom Resource Definition. Extends the API server with a new kind. Crossplane auto-generates one when you apply an XRDClusterNamespaced or Cluster, per spec.scope
Composite Resource Definition (XRD)Declares the schema of a new composite kind, e.g. XApplication with frontend + backend fields. Applying it makes Crossplane generate the matching CRDClusterNamespaced (v2 default), Cluster, or LegacyCluster (v1 compat with claims), per spec.scope
Composite Resource (XR)An instance of an XRD. Created by the user; Crossplane composes it into the resources its Composition declaresPer the XRD — Namespaced in v2 by default
CompositionThe recipe. "When someone creates this XR, fan it out into these resources." In v2 the composed resources can be plain native Kubernetes objects (ConfigMap, Deployment, …) or Managed Resources from a ProviderCluster
Composition functionPluggable logic the Composition's pipeline runs to produce desired state — function-patch-and-transform, function-go-templating, function-kcl, function-python, etc. A Crossplane packageCluster
ProviderA package that teaches Crossplane to manage an external API (AWS, GCP, Kubernetes, Helm, …)Cluster
ProviderConfigPer-Provider runtime config — credentials, target endpoint. v2 namespaced flavor (in *.m.crossplane.io for providers that ship the v2 split)Namespaced
ClusterProviderConfigPer-Provider runtime config — v2's cluster-scoped flavor, shared across namespaces. The "default" config for single-tenant clustersCluster
Managed Resource (MR)A Kubernetes representation of an external thing the provider reconciles by calling its API (e.g. Release.helm.m.crossplane.io, Bucket.s3.aws.upbound.io)Per-Provider choice — many v2 providers ship namespaced variants in a *.m.crossplane.io group alongside the legacy cluster-scoped kinds

Concepts overview (docs.crossplane.io)

Naming convention: XR kinds start with X

Crossplane recommends starting the XR kind with X so it cannot collide with downstream API kinds the Composition might emit: Application is an ArgoCD kind, Database ships in several providers, Bucket is an S3 MR. Naming your XR XApplication keeps yours and theirs unambiguous in kubectl get.

The convention is on the kind only. Two follow-ons drop out of that:

  • The XRD's metadata.name is <plural>.<group>, and the plural derives from the kind — so XApplication gives xapplications.workshop.example.io. That's mechanical, not a separate rule.
  • The XRD object's own kind is always CompositeResourceDefinition — never X-prefixed. The prefix applies to the kind it defines, not to itself.

What is not part of the convention:

  • The Composition's metadata.name — it can be anything. This workshop names it the same as the XRD purely for clarity (one Composition per XRD).
  • The XR instance's metadata.namewall-tile, hello-world, whatever you pick. No X prefix.
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition # XRD object — never X-prefixed
metadata:
name: xapplications.workshop.example.io # <plural-of-XR-kind>.<group>
spec:
group: workshop.example.io
names:
kind: XApplication # ← the only place the convention applies
plural: xapplications
---
apiVersion: workshop.example.io/v1alpha1
kind: XApplication # XR instance carries the X-kind…
metadata:
name: wall-tile # …but its own name does not

When to reach for what

GoalUse this
Create a fresh native Kubernetes object (ConfigMap, Deployment, Service, Job, …) as part of a CompositionA Composition with a function — Crossplane v2 emits the resource directly, no Provider needed
Create or manage something outside the cluster (cloud bucket, DNS record, GitHub repo)Install the matching provider-* package and apply its MR (e.g. Bucket.s3.aws.upbound.io)
Take ownership of an existing in-cluster resource without recreating itprovider-kubernetes Object MR with managementPolicies: [Observe]
Install a Helm chart through Crossplaneprovider-helm Release.helm.m.crossplane.io MR
Aim a single Provider at multiple target clusters or accountsOne ProviderConfig per target, with separate credentials in each

Things that trip people up

MR ≠ XR. A Managed Resource is a 1-to-1 wrapper for a real external object (one Object = one Kubernetes object the provider will apply to a target cluster). A Composite Resource is the user-facing abstraction that fans out into MRs.

ProviderConfig is not Provider. Provider is the package (the thing you install). ProviderConfig is a runtime configuration object the provider watches — it tells the provider how to authenticate.

Namespaced XRs are a v2 feature. In v1, XRs were cluster-scoped and users had to create "claims" to get a namespaced handle on them. In v2 XRs are namespaced by default and claims are optional (the workshop doesn't use them).

Claims vs XRs — why this workshop skips claims

In Crossplane v1, users created a claim (namespaced) and Crossplane created a matching XR (cluster-scoped) for each one. The claim was a handle; the XR was the real thing. This made RBAC work cleanly but introduced a second layer for every user-facing kind.

In Crossplane v2 — what you're using today — XRs are first-class and can be namespaced. You apply an Application XR directly, no claim required. When you read older Crossplane tutorials that talk about Claim objects, that's the v1 pattern; the v2 equivalent is just the XR itself.

Crossplane v1 vs v2

v1v2
XR scopeCluster-scopedNamespaced or cluster-scoped
Claim layerRequired for namespaced accessOptional (the workshop doesn't use it)
Composition stylePatch-and-transform (declarative), Functions (later addition)Functions are the default
XRD claimNamesRequired for claim-backed XRDsNot required for claim-free XRDs
API group (XR types)apiextensions.crossplane.io/v1apiextensions.crossplane.io/v2

v2 migration guide (docs.crossplane.io)

Crossplane vs UXP

Crossplane is the CNCF upstream project — community-maintained, shipped as a Helm chart from charts.crossplane.io/stable.

UXP (Upbound Crossplane) is Upbound's distribution of Crossplane. It's the same core plus a bundled read-only Web UI and some enterprise-only features. It ships from charts.upbound.io/stable and the chart is still called crossplane.

This workshop uses UXP v2 so you also get the Web UI for free — convenient for visualising what you've composed. Everything you learn transfers to upstream Crossplane one-for-one.

A quirk to know: UXP v2 releases are tagged 2.x.y-up.N, which Helm treats as pre-release. That's why helm install for UXP uses --devel; it's not actually a pre-release in the Upbound sense.

1.4 Where to read more

When the assistant cites a URL, it comes from one of the two doc sites above. If anyone tells you something different, verify against the source.