Building a Platform Abstraction for AWS Networks Using Crossplane

Crossplane helps platform engineers develop abstractions for developers. It is an open-source, multicloud control plane that handles interactions with cloud providers’ APIs for you.

In this post, I’ll show how developers can create an AWS network (VPC, Subnet, etc.) with just a single YAML request to the Kubernetes API.

Crossplane leverages Kubernetes CRD (Custom Resource Definition), which extends Kubernetes APIs with new custom objects. Along with that, Crossplane creates controllers for these objects, implementing the reconciliation loop to ensure the system’s actual state always matches the declared state.

For example, if you define a Table custom resource with spec.legs = 3, Kubernetes continuously checks:

“Does this table have 3 legs?”

  • If it finds only 2, it builds another.
  • If someone manually adds a 4th, it removes the extra one.

This ensures the resource has exactly 3 legs—no more, no less.

Pre-Requisites 

  • Docker  & Kubernetes setup
  • AWS account with user or IAM role credentials (below, I provide steps to set up user in AWS)

Docker Kubernetes Setup 

I am assuming you already have a local Kubernetes setup. If not, you can install Docker Desktop from https://docs.docker.com/desktop/setup/install/mac-install/  and enable Kubernetes. 

Install Docker Desktop and enable Kubernetes

Confirm the setup by checking Kubernetes version. 

kubectl version

Crossplane Installation 

Now, we will install Crossplane using helm, helm is a packaging tool for Kubernetes resources. 

Install helm using 

brew install helm

confirm, using version check 

helm version

Let’s add helm repo for Crossplane using commands:

helm repo add crossplane-stable https://charts.crossplane.io/stable

helm repo update

And then, the command below will install Crossplane.

helm install crossplane --namespace crossplane-system --create-namespace crossplane-stable/crossplane

You can change the namespace to one of your choice, and then check the installation status using:

helm list -n crossplane-system

Provider 

In Crossplane, a provider acts like a software package or driver that enables functionality. It bundles the necessary Custom Resource Definitions (CRDs) and controllers to interact with external systems, most often, cloud provider APIs. In our case, we’ll use provider-aws-ec2, which allows us to manage AWS EC2 instances and networking resources. 

To install provider, save the lines below as .yaml file,

apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
 name: provider-aws-ec2
spec:
 package: xpkg.upbound.io/upbound/provider-aws-ec2:v1

and apply using:

kubectl create secret generic aws-creds 
  --from-file=creds=./aws-creds

To link this secret with Crossplane, we need something called a ProviderConfig. Create the provider config by saving the yaml content below into a .yaml file,

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

and using kubectl apply -f <your-file-name>.yaml. Note that we used the name of the secret aws-creds as a reference to this provider. 

Crossplane Composite Resource Definition (XRD)

This is where Crossplane leverages Kubernetes Custom Resource Definitions (CRDs) to define custom resources that represent infrastructure abstractions, essentially the interface. A Composite Resource (XR) bundles and abstracts the underlying cloud resources, so instead of worrying about configuring VPCs, subnets, and routing, developers can simply declare that they need an AWS network. Think of it like ordering a dish: You care about the outcome, not the individual ingredients. To create an XRD, save the following content into a .yaml file: 

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xnetworks.aws.platformref.crossplane.io
spec:
  group: aws.platformref.crossplane.io
  names:
    kind: XNetwork
    plural: xnetworks
  claimNames:
    kind: Network
    plural: networks
  connectionSecretKeys:
    - vpcId
    - subnetIds
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                parameters:
                  type: object
                  properties:
                    vpcCidr:
                      type: string
                  required: [vpcCidr]
              required: [parameters]
            status:
              type: object
              properties:
                vpcId:
                  type: string
                subnetIds:
                  type: array
                  items:
                    type: string

and apply 

XNetwork resource, the Composition ensures that the subnet and VPC are created. To create a composition, save the below content into a yaml file and apply 

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: network.aws.platformref.crossplane.io
  labels:
    crossplane.io/xrd: xnetworks.aws.platformref.crossplane.io
spec:
  compositeTypeRef:
    apiVersion: aws.platformref.crossplane.io/v1alpha1
    kind: XNetwork
  resources:
    - name: vpc
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: VPC
        spec:
          forProvider:
            cidrBlock: 10.0.0.0/16
            enableDnsSupport: true
            enableDnsHostNames: true
            region: us-east-1
          providerConfigRef:
            name: default
      patches:
        - fromFieldPath: spec.parameters.vpcCidr
          toFieldPath: spec.forProvider.cidrBlock
          type: FromCompositeFieldPath
        - fromFieldPath: spec.claimRef.name
          toFieldPath: spec.forProvider.tags.Name
          type: FromCompositeFieldPath
          transforms:
            - type: string
              string:
                fmt: "%s-vpc"
        - fromFieldPath: status.atProvider.id
          toFieldPath: status.vpcId
          type: ToCompositeFieldPath
    - name: subnet
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: Subnet
        spec:
          forProvider:
            cidrBlock: 10.0.1.0/24
            availabilityZone: us-east-1a
            mapPublicIpOnLaunch: true
            region: us-east-1
          providerConfigRef:
            name: default
      patches:
        - fromFieldPath: status.vpcId
          toFieldPath: spec.forProvider.vpcId
          type: FromCompositeFieldPath
        - fromFieldPath: spec.claimRef.name
          toFieldPath: spec.forProvider.tags.Name
          type: FromCompositeFieldPath
          transforms:
            - type: string
              string:
                fmt: "%s-subnet"
        - fromFieldPath: status.atProvider.id
          toFieldPath: status.subnetIds[0]
          type: ToCompositeFieldPath

apiVersion: aws.platformref.crossplane.io/v1alpha1
kind: Network
metadata:
  name: crossplane-demo-network
  namespace: default
spec:
  parameters:
    vpcCidr: "10.0.0.0/16"
  compositionRef:
    name: network.aws.platformref.crossplane.io

and apply. 

Similar Posts