Skip to main content
Version: 0.10.0-rc.0

Dependencies & Ordering

kro automatically infers dependencies from CEL expressions. You don't specify the order - you describe relationships, and kro figures out the rest. This applies equally to the resources of a ResourceGraphDefinition and the nodes of a Graph.

How It Works

When you reference one resource from another using a CEL expression, you create a dependency:

resources:
  - id: configmap
    template:
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: app-config
      data:
        DATABASE_URL: ${schema.spec.dbUrl}

  - id: deployment
    template:
      apiVersion: apps/v1
      kind: Deployment
      spec:
        containers:
          - env:
              - name: DATABASE_URL
                value: ${configmap.data.DATABASE_URL}

The expression ${configmap.data.DATABASE_URL} creates a dependency: deployment depends on configmap. kro will create the configmap first, wait for the expression to be resolvable, then create the deployment.

Dependency Graph (DAG)

kro builds a Directed Acyclic Graph (DAG) where:

  • Nodes are resources (or, in a Graph, nodes of any kind)
  • Edges are dependencies (created by CEL references)
  • Directed means dependencies have direction (A depends on B)
  • Acyclic means no circular dependencies

Every field reference creates a dependency, including references that use the optional operator (?.). Writing ${deployment.?status.?readyReplicas} still makes the resource depend on deployment; the operator only changes what happens when the field is missing, not whether the dependency exists.

Common Patterns

Dependency graphs follow common patterns that you'll encounter in most RGDs. Understanding these patterns helps you design effective resource relationships. Real-world RGDs often combine multiple patterns - a complex application might have parallel branches that converge into a diamond, followed by a linear chain.

In a linear chain, each resource depends on the previous one. This pattern is common when you have a sequence of resources where each step requires data from the previous step - like a ConfigMap that feeds into a Deployment, which then informs a Service.

configmap ──▶ deployment ──▶ service
resources:
  - id: configmap
    template:
      apiVersion: v1
      kind: ConfigMap
      data:
        key: ${schema.spec.value}

  - id: deployment
    template:
      apiVersion: apps/v1
      kind: Deployment
      spec:
        template:
          spec:
            containers:
              - envFrom:
                  - configMapRef:
                      name: ${configmap.metadata.name}

  - id: service
    template:
      apiVersion: v1
      kind: Service
      spec:
        selector: ${deployment.spec.selector.matchLabels}

The deployment references ${configmap.metadata.name}, creating a dependency. The service references ${deployment.spec.selector.matchLabels}, adding another link. kro creates them in sequence: configmap first, then deployment, then service.

Combining Patterns

Real-world RGDs typically combine multiple patterns. A complex application might have parallel branches (independent microservices), a diamond (services converging into a gateway), and linear chains (each service with its own config → deployment → service sequence). kro handles any valid DAG structure - these patterns are just common building blocks.

Topological Order

kro computes a topological order - the sequence resources can be processed such that all dependencies are satisfied.

Creation: Resources created in topological order Deletion: Resources deleted in reverse order

For an RGD, view the computed order:

kubectl get rgd my-app -o jsonpath='{.status.topologicalOrder}'

Example output:

status:
  topologicalOrder:
    - configmap
    - deployment
    - service

A Graph does not publish a topological order in its status. The apply order of each managed resource is recorded on the resource itself in the internal.kro.run/apply-order annotation.

Circular Dependencies

Circular dependencies are not allowed and will cause validation to fail:

# ✗ This will fail
resources:
  - id: serviceA
    template:
      spec:
        targetPort: ${serviceB.spec.port}  # A → B

  - id: serviceB
    template:
      spec:
        targetPort: ${serviceA.spec.port}  # B → A (circular!)

Fix: Break the cycle by taking one side of the loop from the input (in an RGD, schema.spec; in a Graph, a def node or a literal) instead:

resources:
  - id: serviceA
    template:
      spec:
        targetPort: ${schema.spec.portA}  # Use schema

  - id: serviceB
    template:
      spec:
        targetPort: ${serviceA.spec.port}  # This is fine

What Happens at Runtime

When kro reconciles an instance or a Graph:

  1. Evaluate static expressions - Expressions that reference no other resource are evaluated once
  2. Process in topological order - For each resource:
    • Wait for all dependency expressions to be resolvable
    • Create or update the resource
    • Move to next resource in order
  3. Delete in reverse order - During deletion, process resources backwards

kro waits for CEL expressions to be resolvable before proceeding. This means the referenced resource exists and has the field being accessed.

Whether a dependent also waits for its dependencies to be ready (their readyWhen conditions) differs between the two APIs. See Readiness.

Next Steps

Brought to you with ♥ by SIG Cloud Provider