Skip to main content

Crossplane Ep 8: Managing Dependencies Between Resources

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 8: This Article
Crossplane is extremely aggressive. By default, if a Composition contains 5 Managed Resources, Crossplane will attempt to create all 5 simultaneously. If Resource B depends on Resource A, Resource B will crash immediately. We must explicitly instruct the reconciliation loop how to handle dependencies and map IDs.

1. The Dependency Problem
#

Let’s assume our Developer Platform offers an XNetwork API. When a developer creates a claim, our Composition provisions two resources:

  1. An AWS VPC.
  2. An AWS Subnet.

The AWS REST API requires that you provide a vpcId when creating a Subnet.

However, before the XNetwork is deployed, the VPC ID doesn’t exist yet! AWS will generate the vpc-xxxxx string dynamically during boot. How do we pass that unknown ID from the VPC MR down into the Subnet MR?


2. Using Selectors (The Modern Way)
#

Crossplane provides a native, elegant solution for this called Selectors.

Selectors allow you to bind resources together using Kubernetes labels, exactly like how a Kubernetes Service binds to a Pod.

Let’s write our XNetwork Composition.

Step 1: Label the Parent Resource (VPC)
#

We must ensure that the VPC MR is created with a unique label so the Subnet can find it.

  resources:
    # 1. The VPC
    - name: my-vpc
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: VPC
        metadata:
          labels:
            # We assign a hardcoded label to this specific VPC MR
            network-name: internal-core
        spec:
          forProvider:
            cidrBlock: 10.0.0.0/16
            region: us-east-1

Step 2: Configure the Child Resource (Subnet) to Select
#

In the Subnet MR, instead of providing a hardcoded vpcId, we provide a vpcIdSelector.

    # 2. The Subnet
    - name: my-subnet
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: Subnet
        spec:
          forProvider:
            cidrBlock: 10.0.1.0/24
            region: us-east-1
            
            # Instead of vpcId, we use vpcIdSelector!
            vpcIdSelector:
              matchLabels:
                network-name: internal-core

The Execution Flow
#

Here is exactly how the Crossplane Engine handles this:

  1. It creates the VPC MR and the Subnet MR simultaneously in the Kubernetes API.
  2. The AWS Provider tries to provision the Subnet, but realizes vpcIdSelector is configured.
  3. The Provider looks across the cluster for a VPC with the label network-name: internal-core.
  4. It finds the VPC MR.
  5. It checks if the VPC MR is READY: True. If it is False (because AWS is still booting the VPC), the Subnet Provider safely pauses and waits.
  6. Once the VPC is READY: True, the Provider automatically extracts the physical vpc-12345 string, injects it into the Subnet, and provisions the Subnet!

You have just orchestrated a strict dependency graph without writing a single line of script.


3. Dealing with Dynamic Labels
#

In the example above, we hardcoded network-name: internal-core. If two developers deploy an XNetwork claim at the exact same time, both claims will spawn VPCs with the exact same label. The Subnet Selector will find both VPCs, get confused, and throw a cannot resolve selector: multiple resources matched error.

To fix this, we must dynamically generate unique labels for every deployment based on the XR’s name.

We use the Patcher to inject the dynamic labels!

    - name: my-vpc
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: VPC
        # (Leave metadata empty in the base)
      patches:
        # Patch the XR name into the label!
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.labels[network-name]

    - name: my-subnet
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: Subnet
        # (Leave vpcIdSelector matchLabels empty in the base)
      patches:
        # Patch the XR name into the selector!
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.forProvider.vpcIdSelector.matchLabels[network-name]

Now, if a developer creates a claim named auth-network, the VPC is labeled network-name: auth-network, and the Subnet specifically searches for network-name: auth-network. Absolute isolation is guaranteed.


4. Alternative: Cross-Resource Patching (ToComposite -> FromComposite)
#

While Selectors are the recommended way to handle parent-child relationships (like VPC -> Subnet), sometimes you need to pass data between two completely unrelated resources in a Composition that do not support Selectors natively.

To do this, you use the XR itself as an “in-memory message bus.”

  1. Resource A: Uses a ToCompositeFieldPath patch to write its generated ID up to a hidden status field on the XR.
  2. Resource B: Uses a FromCompositeFieldPath patch to read that ID from the XR status field.

This is technically a race condition. When Crossplane first executes, the XR status field is empty, so Resource B will fail. However, because Crossplane is a continuous loop, it will simply retry Resource B 30 seconds later. By that time, Resource A will have booted and updated the XR status, and Resource B will succeed.

This is the beauty of “Eventually Consistent” architectures.


Troubleshooting & Common Errors
#

  1. cannot resolve selector: resource not found

    • Root Cause: The Subnet is looking for a VPC with specific labels, but no VPC exists with those exact labels.
    • Solution: kubectl describe vpc and verify that the Patches correctly injected the labels onto the parent MR.
  2. The Subnet is stuck in SYNCED: False indefinitely

    • Root Cause: The VPC is created, and the Selector found it, but the VPC is stuck in a failing state (READY: False). Crossplane is deliberately refusing to provision the Subnet until the VPC becomes healthy.
    • Solution: Fix the underlying error on the VPC. Once the VPC goes green, the Subnet will automatically resume provisioning.

Conclusion & Next Steps
#

You can now build complex, multi-layered architectures. By utilizing Selectors and dynamic patching, you ensure that physical cloud resources boot in the exact correct order without race conditions or manual intervention.

However, as Compositions grow to 10 or 20 resources, debugging them when they fail becomes incredibly difficult. If a developer submits a claim and it stays READY: False for 20 minutes, where do you look? The XR? The MR? The Provider Logs?

In Episode 9: Troubleshooting Crossplane Sync Errors, we will cover the essential commands and debugging strategies every Platform Engineer must know to maintain a production Crossplane cluster.

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