Crossplane
Universal control plane for managing infrastructure and cloud resources via Kubernetes APIs.
Crossplane extends Kubernetes with CRDs that let you provision and manage external infrastructure (cloud resources, SaaS APIs, IaC runners) using standard Kubernetes manifests. It is used in this cluster primarily to run OpenTofu workspaces via the upbound/provider-opentofu, enabling infrastructure-as-code pipelines that are GitOps-driven and managed by Flux.
Alternatives considered
Self Hosted
| Tool | Open Source | Full Features | Notes |
|---|---|---|---|
| ACK (AWS Controllers for Kubernetes) | Yes | Yes | AWS-specific; no multi-cloud |
| Config Connector | Yes | Yes | GCP-specific |
| Terraform / OpenTofu | Yes | Yes | Not Kubernetes-native; Crossplane can wrap it |
Installation
Architecture
HelmRelease crossplane in namespace crossplane-system, chart from https://charts.crossplane.io/stable, version pinned via Renovate. Deploys the Crossplane core controller and RBAC manager. No providers are installed by the Helm chart itself — providers are managed separately via crossplane-extras.
Security
No explicit securityContext set in the HelmRelease values. RBAC manager runs cluster-wide. Resource limits: 512Mi memory / 500m CPU for both crossplane and rbac-manager. The namespace has the label secrets.k8up: "true" for k8up backup inclusion.
Updates
Managed by Renovate. The chart version is semver-pinned in the HelmRelease.
Usage
The Crossplane control plane watches for Composite Resource and Managed Resource objects. Providers installed via crossplane-extras (e.g. provider-opentofu) extend it with additional resource types. Operators create Provider, Function, and composition resources to define and instantiate infrastructure. The go-templating and auto-ready functions support composition pipelines.
Cluster-specific deviations from the above live in the per-cluster README — see k8s/platform/crossplane/talos/README.md.
Cluster Deployment
Crossplane — Talos cluster
- HelmRelease:
crossplane@2.4.2 - HelmRepo:
crossplane-stable(https://charts.crossplane.io/stable)
Rendered manifests (kustomize build)
apiVersion: v1
data:
values.yaml: |
provider:
defaultActivations: []
# default 1m poll re-runs every composition function pipeline each minute
# (~2.4k invocations/h of pure drift-checking). Spec changes still reconcile
# immediately via watch events — this only stretches the periodic re-check.
args:
- --poll-interval=10m
# --enable-realtime-compositions is OFF on purpose. It puts a watch on every
# composed *and* required resource, and the required-resource selectors are
# effectively cluster-wide because function-go-templating before 0.13.0 dropped
# the namespace on matchLabels requests. So all 42 TofuWorkspace XRs woke on every k8up
# Schedule status write: 5899 of 6018 reconcile triggers logged before the
# 2026-09-23 OOM kill were fired by a *foreign* namespace's resource.
# Required resources are still fetched during each reconcile — only the
# change-triggered wakeup is gone, so composed-resource drift is now caught
# by --poll-interval within 10m instead of instantly.
# TODO(realtime): the upstream blocker is gone — function-go-templating 0.13.0
# carries crossplane-contrib/function-go-templating#612 (namespace honoured for
# matchLabels requests) and the composition's namespace filters are dropped, so
# the watch set is per-namespace now and the fan-out should disappear. Re-enable
# as its own change, after 0.13.0 has run clean for a while, and watch the core's
# memory for a window before calling it good.
#
# Skip re-running the function pipeline when the XR and its inputs are
# unchanged (go-templating was the busiest function at ~90m CPU).
- --enable-function-response-cache
# The core keeps informer caches for every CRD in the cluster (210, several
# of them huge: Gateway API, VM operator, Longhorn, Kyverno) plus the composed
# kinds, and --enable-realtime-compositions adds a watch per required resource
# — a ~430Mi floor whose peaks sat at 870-1014Mi against the old 1Gi limit in
# every 3h window for at least two weeks, until the OOM kill on 2026-09-23.
resourcesCrossplane:
limits:
cpu: 500m
memory: 1536Mi
requests:
cpu: 100m
memory: 512Mi
# GOMEMLIMIT is pinned ~15% under the limit by the postRenderer on the
# HelmRelease (helm-patch.yaml), not here: the chart hardcodes it to
# limits.memory via resourceFieldRef and extraEnvVarsCrossplane cannot
# override that — env entries merge by name, so a second GOMEMLIMIT collapses
# into the first and the API rejects `value` + `valueFrom` on one entry.
rbacManager:
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
kind: ConfigMap
metadata:
name: crossplane-values-766mh55f6t
namespace: crossplane-system