Skip to main content

Crossplane Ep 3: Managed Resources (MR)

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
crossplane - This article is part of a series.
Part 3: This Article
If you know how to write a Kubernetes Deployment YAML, you now know how to write AWS Infrastructure as Code. Crossplane’s AWS Provider maps every single AWS API endpoint to a native Kubernetes Custom Resource. Let’s create an S3 bucket without ever leaving the terminal.

1. What is a Managed Resource (MR)?
#

A Managed Resource (MR) is a Kubernetes Custom Resource that represents exactly one piece of infrastructure in the real world.

If you look at the Terraform AWS Provider, you have aws_s3_bucket and aws_vpc. In Crossplane, you have Bucket.s3.aws.upbound.io and VPC.ec2.aws.upbound.io.

They are conceptually identical, but the execution engine is fundamentally different. Terraform runs once. Crossplane runs continuously.

The Anatomy of an MR
#

All Managed Resources share a few core fields:

  1. spec.forProvider: This is where you configure the specific cloud API arguments (e.g., Region, Bucket ACL, Instance Type).
  2. spec.providerConfigRef: This tells the MR which AWS credentials to use (the one we set up in Ep 2).
  3. spec.deletionPolicy: Determines what happens in AWS when you run kubectl delete on the YAML. (Either Delete or Orphan).

2. Practice: Creating an S3 Bucket
#

Let’s write our first Crossplane YAML file. Create a file named my-bucket.yaml.

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  # This is the name of the object IN KUBERNETES
  name: rhidayat-crossplane-demo-bucket
spec:
  # We want Crossplane to delete the physical AWS bucket if we delete this YAML
  deletionPolicy: Delete
  
  # Point to the credentials we created in Ep 2
  providerConfigRef:
    name: default

  # These are the arguments passed directly to the AWS API
  forProvider:
    region: us-east-1
    # This is the physical name of the bucket IN AWS
    # It must be globally unique!
    bucket: rhidayat-crossplane-demo-bucket-prod
    forceDestroy: true

Now, apply it to the cluster just like you would apply a Pod:

kubectl apply -f my-bucket.yaml

Checking the Status
#

Because Crossplane operates asynchronously, the kubectl apply command will return instantly. The Kubernetes API accepted your Desired State. Now, the AWS Provider Pod running in the background is making the REST API calls to AWS to fulfill it.

Check the status of your Managed Resource:

kubectl get buckets

Expected Terminal Output:

NAME                              READY   SYNCED   EXTERNAL-NAME                            AGE
rhidayat-crossplane-demo-bucket   True    True     rhidayat-crossplane-demo-bucket-prod     1m

When both READY and SYNCED are True, the bucket physically exists in AWS!

  • SYNCED: Means the Crossplane controller successfully communicated with the AWS API without authentication or syntax errors.
  • READY: Means the physical resource in AWS is fully provisioned and available for use (e.g., an RDS instance might be SYNCED instantly, but READY will be False for 10 minutes while AWS boots the database).

3. The Power of Continuous Reconciliation
#

Now we will demonstrate why Crossplane is fundamentally superior to traditional CI/CD IaC pipelines.

We are going to simulate a rogue administrator (or a malicious script) modifying production infrastructure outside of our IaC.

Step 1: The Rogue Edit
#

Log into the AWS Web Console. Navigate to S3. Find the bucket you just created.

Manually add a Tag to the bucket:

  • Key: HackedBy
  • Value: RogueAdmin

Step 2: Observe Crossplane
#

Do not run any commands in your terminal. Just wait 60 seconds.

Go back to the AWS Web Console and refresh the page. The tag is gone.

What just happened?
#

  1. Crossplane’s reconciliation loop wakes up (by default, every 10-60 seconds for MRs).
  2. It queries the AWS API to check the Current State of the bucket.
  3. It compares the Current State (which has the HackedBy tag) against the Desired State stored in Kubernetes (your YAML file, which does not have that tag).
  4. Crossplane detects Configuration Drift.
  5. Without any human intervention, Crossplane sends a PUT request to AWS to strip the unauthorized tag, returning the physical infrastructure to the exact state defined in Kubernetes.

This is self-healing infrastructure.


4. Deleting Infrastructure
#

If we want to destroy the bucket, we do not run terraform destroy. We simply delete the Kubernetes object.

kubectl delete bucket rhidayat-crossplane-demo-bucket

Because we set spec.deletionPolicy: Delete in our YAML, Crossplane will issue an API call to AWS to delete the physical bucket, and then it will remove the object from the Kubernetes cluster.

(If you set it to Orphan, Crossplane would delete the object from Kubernetes but leave the physical bucket untouched in AWS).


Troubleshooting & Common Errors
#

  1. kubectl get buckets shows SYNCED: False

    • Root Cause: There is an error communicating with the AWS API. Usually, this means your IAM credentials are invalid, or you lack the s3:CreateBucket IAM permission.
    • Solution: You must inspect the Kubernetes Events attached to the object! Run kubectl describe bucket rhidayat-crossplane-demo-bucket. Scroll to the very bottom to the Events: section to see the exact AWS API error message.
  2. Bucket is stuck in a Terminating state when deleting

    • Root Cause: Crossplane is trying to delete the physical bucket in AWS, but AWS is rejecting the request (usually because the bucket is not empty, and you didn’t set forceDestroy: true). Because Crossplane uses Kubernetes Finalizers, it will refuse to delete the Kubernetes object until AWS confirms the physical deletion.
    • Solution: Empty the bucket manually in the AWS console, or edit the Kubernetes object (kubectl edit bucket <name>) and manually delete the finalizers block to force Kubernetes to drop the object.

Conclusion & Next Steps
#

You have successfully provisioned and destroyed AWS infrastructure using nothing but kubectl and YAML. You have also witnessed the self-healing power of the Crossplane reconciliation loop.

However, writing MRs by hand is not Platform Engineering. If a developer needs a production database, you do not want them writing 15 different MR YAML files for Subnets, Route Tables, Security Groups, and RDS Instances. You want them to write one file.

In Episode 4: Composite Resources (XR), we will learn how to hide this complexity by creating our own custom Kubernetes APIs.

crossplane - This article is part of a series.
Part 3: This Article