Graph Inference
kro automatically infers dependencies from CEL expressions. You don't specify the order - you describe relationships, and kro figures out the rest.
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
- Edges are dependencies (created by CEL references)
- Directed means dependencies have direction (A depends on B)
- Acyclic means no circular dependencies
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.
- Linear Chain
- Diamond
- Parallel Branches
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.
In a diamond pattern, multiple independent resources converge into a single dependent resource. This is typical for applications that need multiple backing services - like an app that requires both a database and a cache before it can start.
┌──────────┐
│ schema │
└────┬──── ─┘
│
┌──────┴──────┐
▼ ▼
┌─────────┐ ┌─────────┐
│database │ │ cache │
└────┬────┘ └────┬────┘
│ │
└──────┬──────┘
▼
┌─────────┐
│ app │
└─────────┘
resources:
- id: database
template:
apiVersion: databases.example.com/v1
kind: PostgreSQL
spec:
name: ${schema.spec.name}-db
- id: cache
template:
apiVersion: caches.example.com/v1
kind: Redis
spec:
name: ${schema.spec.name}-cache
- id: app
template:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- env:
- name: DATABASE_URL
value: ${database.status.endpoint}
- name: CACHE_URL
value: ${cache.status.endpoint}
Both database and cache only reference schema, so kro creates them in parallel. The app references both ${database.status.endpoint} and ${cache.status.endpoint}, so it waits for both to be ready before being created.
When resources only reference schema (not each other), they have no interdependencies and kro creates them concurrently. This maximizes deployment speed for independent components like multiple microservices or worker pools.
┌──────────┐
│ schema │
└────┬─────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
┌─────┐ ┌───────┐ ┌──────┐
│ api │ │worker │ │ cron │
└─────┘ └───────┘ └──────┘
resources:
- id: api
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.name}-api
- id: worker
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.name}-worker
- id: cron
template:
apiVersion: batch/v1
kind: CronJob
metadata:
name: ${schema.spec.name}-cron
All three resources only reference ${schema.spec.name}, meaning they have no dependencies on each other. kro creates all of them simultaneously, reducing overall deployment time.
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
View the computed order:
kubectl get rgd my-app -o jsonpath='{.status.topologicalOrder}'
Example output:
status:
topologicalOrder:
- configmap
- deployment
- service
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 using schema.spec instead:
resources:
- id: serviceA
template:
spec:
targetPort: ${schema.spec.portA} # Use schema
- id: serviceB
template:
spec:
targetPort: ${serviceA.spec.port} # This is fine