Crossplane : Kubernetes-Native Infrastructure as Code

In the cloud world, developers usually use Kubernetes to manage applications — like containers, deployments, and services. But what about the cloud infrastructure that these apps rely on? Things like databases, storage accounts, and networks still need to be created separately using tools like Terraform or the cloud portal.

Crossplane changes that. It lets Kubernetes itself create and manage cloud resources — just like it manages your apps. With Crossplane, you can describe not only your app, but also the infrastructure it needs, in YAML — and let Kubernetes handle everything automatically. This creates a single, unified way to manage both applications and infrastructure, all inside your Kubernetes cluster.


🌍 What is Crossplane?

Crossplane is an open-source project governed by the Cloud Native Computing Foundation (CNCF), currently at the Incubating maturity level. It is designed to extend the capabilities of Kubernetes beyond container orchestration, allowing you to provision and manage infrastructure resources across multiple cloud providers — all using standard Kubernetes APIs.

    • Crossplane is written in Go and follows Kubernetes controller patterns.
    • It turns Kubernetes into a universal control plane.
    • All infrastructure resources are represented as Custom Resources (CRs).

Crossplane provides a declarative, GitOps-friendly way to manage infrastructure, especially in environments already standardized on Kubernetes.


🎯 Why Crossplane?

“How can platform teams enable developers to provision infrastructure safely and quickly — without exposing the complexity of Terraform modules, ARM templates, or cloud consoles?”

    • App teams depend on infrastructure like databases, queues, secrets, and storage.
    • Provisioning these often requires separate tools (Terraform, Bicep) or manual effort.
    • Lifecycle management is disconnected from the application itself.

Crossplane solves this by bringing infrastructure provisioning inside Kubernetes.

Core Benefits:

    • Self-service for developers through Kubernetes CRs
    • Abstraction for platform teams using compositions
    • Unified lifecycle of app and infra through GitOps
    • Secure provisioning with RBAC and Kubernetes-native access control
    • No external state files — state is managed internally via Kubernetes etcd

🧠 The Core Idea: Let Kubernetes Provision Its Own Infrastructure

Once your Kubernetes cluster (e.g., AKS) is running, install Crossplane into it. From there:

    • Provision cloud resources on-demand, based on YAML manifests
    • Write PostgreSQLServer or VirtualNetwork manifests like you do with Deployment
    • Crossplane reconciles these objects via ARM APIs

Kubernetes becomes not just the container control plane, but the infrastructure control plane too.

Note: Crossplane does not replace Terraform/Bicep for bootstrapping.


🔌 Crossplane Providers and Resource Installation (Azure-Specific)

To interact with any cloud environment, Crossplane requires a Provider — a plugin that knows how to talk to that particular cloud’s APIs.

🧭 Step 1: Authenticate with Azure

Before installing any provider, you must configure how Crossplane will authenticate with Azure. Recommended options:

    • Azure Workload Identity (best choice) — uses federated identity to securely authenticate from inside AKS
    • Azure Managed Identity — works well for tightly scoped identity scenarios
    • Azure Service Principal via Secret — legacy, less secure (but still supported)

Example: Create a ProviderConfig that uses a Kubernetes secret to store Azure credentials:

apiVersion: azure.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: azure-creds
      key: creds

This ProviderConfig must be referenced in all subsequent resource definitions.

🧭 Step 2: Install Specific Azure Providers

In the previous section, we successfully authenticated Crossplane to Azure. However, that only enables access.

To actually create and manage specific Azure resources, we must now install the appropriate Crossplane provider modules — each one tailored to a particular set of Azure services.

Crossplane organizes Azure services into modular providers. So you must install a separate provider for each group of services:

    • provider-azure-network: for VNet, Subnet, NSG, Public IP
    • provider-azure-database: for PostgreSQL, MySQL, SQL
    • provider-azure-storage: for Storage Accounts and containers
    • provider-azure-keyvault: for secrets and certificates

Example: Installing the Azure network provider via Crossplane CLI:

kubectl crossplane install provider xpkg.upbound.io/upbound/provider-azure-network:v0.33.0

ℹ️ Replace version with the latest stable version.

🧾 Step 3: Define Azure Resources (VNet and Subnet Example)

📄 Create a Virtual Network in Azure:

apiVersion: network.azure.upbound.io/v1beta1
kind: VirtualNetwork
metadata:
  name: demo-vnet
spec:
  forProvider:
    location: eastus
    resourceGroupName: my-rg
    addressSpace:
      - 10.0.0.0/16
  providerConfigRef:
    name: default

📄 Now Create a Subnet within That VNet:

apiVersion: network.azure.upbound.io/v1beta1
kind: Subnet
metadata:
  name: demo-subnet
spec:
  forProvider:
    addressPrefix: 10.0.1.0/24
    resourceGroupName: my-rg
    virtualNetworkName: demo-vnet
  providerConfigRef:
    name: default

This creates an actual subnet inside the previously declared VNet in your Azure subscription, fully managed by Crossplane.

You can now use this subnet reference in other resources, like AKS node pools, App Gateways, or private endpoints.

🧩 Crossplane Abstractions: XRD + Composition Explained

When you start using Crossplane seriously, you’ll quickly encounter two core concepts:

  1. XRD — CompositeResourceDefinition
    This is like defining a new Kubernetes kind that doesn’t exist yet. Think of it as creating your own custom API.

🔍 Example:

You define a new kind called MySQLDatabase.

Now your developers can use:

apiVersion: platform.myorg.io/v1alpha1
kind: MySQLDatabase

This kind is not built into Kubernetes. You created it — through XRD.

  1. Composition
    This is where the magic happens. You define what this new kind actually does.

🔧 Example:

You say, “Whenever someone creates a MySQLDatabase, I want Crossplane to create the following cloud resources behind the scenes”:

    • An Azure Flexible MySQL server
    • A Key Vault to store the password
    • A private endpoint (if required)

All of that logic — how the new kind maps to real Azure resources — lives in the Composition.

🧠 Real-World Analogy

Imagine you’re building a self-service platform at a company.

Your app teams keep raising tickets like:

“Hey, I need a MySQL database for my app. Can you create one in Azure? Make sure it’s encrypted, private, backed up, and has the right tags.”

Instead of manually doing it, you create:

    • An XRD: A new kind called MySQLDatabase
    • A Composition: It provisions the Azure MySQL server, configures backup, tags, Key Vault, etc.

Now your developers can request a database just by writing:

apiVersion: platform.myorg.io/v1alpha1
kind: MySQLDatabase
metadata:
  name: app-db
spec:
  parameters:
    location: eastus
    version: "5.7"

And that’s it. No Terraform. No tickets. No direct access to Azure.

🧭 How Crossplane Handles This Internally

Here’s the lifecycle in plain terms:

    • You create an XRD → This defines a new CRD (e.g., MySQLDatabase)
    • You create a Composition → This tells Crossplane what cloud resources to create when this kind is used
    • A developer applies a MySQLDatabase YAML
    • Crossplane creates a Composite Resource and provisions the Azure infrastructure described in the Composition
    • The actual state is reconciled continuously, just like Kubernetes manages Pods
+----------------------------+
|     MySQLDatabase (CR)    |  <-- developer applies
+------------+--------------+
             |
             v
+------------+--------------+
|  Composition (platform)   |  <-- defined by platform team
|   - Azure DB              |
|   - Key Vault             |
|   - Secret export         |
+------------+--------------+
             |
             v
+----------------------------+
| Azure resources provisioned|
+----------------------------+

🔐 RBAC and Namespace Isolation

Crossplane runs entirely within your Kubernetes cluster, which means it can fully leverage Kubernetes’ built-in Role-Based Access Control (RBAC) system. This allows you to tightly govern who is allowed to provision infrastructure, what types of infrastructure they can provision, and where they can provision it.

Without RBAC in place, any authenticated user could potentially create cloud resources through Crossplane — including expensive or sensitive ones like databases, public IPs, or network rules. Therefore, implementing proper RBAC is not optional — it is essential for real-world usage, especially in production or multi-tenant environments.

✅ Control Who Can Provision What

You can use Kubernetes Roles and RoleBindings to define which users or service accounts are allowed to create specific kinds of Crossplane resources — for example, a custom resource like MySQLDatabase.

Let’s say your platform team defines a custom abstraction using an XRD + Composition that exposes MySQLDatabase to developers. With RBAC, you can restrict the ability to create that resource to only certain namespaces, groups, or individual users.

📦 Namespace-Scoped Compositions

In Kubernetes, namespaces are a common way to isolate workloads between teams or environments (e.g., dev, staging, prod). With Crossplane, you can scope infrastructure requests to namespaces as well.

This means:

    • Each team can be assigned a dedicated namespace
    • RBAC rules can allow team members to create resources only in that namespace
    • Each namespace can have access to a subset of compositions (via RBAC or label-based scoping)

As a result, Team A cannot interfere with infrastructure provisioned by Team B. They also cannot accidentally use compositions meant for another team or environment.

🛡️ Example: Restricting Access to MySQLDatabase

Here’s an example of RBAC where a user can only provision MySQLDatabase resources in the team-a namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-a
  name: db-provisioner
rules:
- apiGroups: ["platform.myorg.io"]
  resources: ["mysqldatabases"]
  verbs: ["create", "get", "list", "watch"]
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bind-team-a
  namespace: team-a
subjects:
- kind: User
  name: alice@example.com
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: db-provisioner
  apiGroup: rbac.authorization.k8s.io

This ensures that:

    • alice@example.com can only create MySQLDatabase resources
    • Only within the team-a namespace
    • And cannot access or manage infrastructure in other namespaces

📋 Governance, Logging, and Compliance

Because everything happens inside Kubernetes, all activity is logged and auditable:

    • You can track who created or modified infrastructure resources
    • You can identify which composition or patch set was used
    • You can enforce naming conventions, cost policies, or quota limits via OPA Gatekeeper or Kyverno

This tight integration with Kubernetes RBAC and audit logging makes Crossplane a solid choice for teams that need to meet compliance requirements or implement strong separation of duties.

In short, RBAC and namespace isolation in Crossplane let you build a secure, governed, and team-friendly infrastructure platform — where the right people can provision the right resources in the right places, with full accountability.


🧾 State Management (Built-in)

Crossplane keeps state inside Kubernetes:

    • No external tfstate file
    • Reconciliation handles drift automatically
    • No backend locking or manual tracking

🔁 GitOps and CI/CD Friendly

    • Works with FluxCD, ArgoCD
    • Infra is defined in Git alongside apps
    • Everything is version-controlled and auditable

⚠️ Challenges and Considerations

Challenge Description
Needs existing infra Can’t provision the AKS cluster or network it’s running in
Learning curve Compositions, XRDs, PatchSets take time to master
Operational overhead Requires managing controllers and providers
Not IaC replacement Still need Terraform/Bicep for bootstrapping
Azure provider maturity Coverage is growing but may lag behind Terraform

✅ When to Use Crossplane on Azure

    • You use AKS or other managed Kubernetes services
    • You want self-service infra provisioning by dev teams
    • You adopt GitOps and CI/CD workflows
    • You’re building a secure internal platform

Use Crossplane to empower Kubernetes to provision and manage its own infrastructure, securely and declaratively.

 

Leave a comment