kro (Kube Resource Orchestrator)
Create custom Kubernetes APIs that deploy groups of Snowplane resources as a single unit using kro.
What is kro?
kro is a Kubernetes-native project (kubernetes-sigs) that lets you define ResourceGraphDefinitions (RGDs) — declarative blueprints that bundle multiple resources into a single, reusable custom API. kro uses CEL expressions (the same language Snowplane uses for CRD validation) to wire values between resources and automatically determines the correct creation order via a DAG.
Why use kro with Snowplane?
| Concern | Without kro | With kro |
|---|---|---|
| Provisioning a full stack | Apply 5-10 individual CRs in order | Apply 1 custom resource |
| Dependency ordering | Manual dependsOn / sync waves |
Automatic DAG resolution |
| Self-service | Developers need to know every CRD | Expose a simplified API |
| Standardisation | Copy-paste YAML across teams | Single RGD, many instances |
| Cleanup | Delete resources in reverse order | Delete one instance |
Prerequisites
- kro installed in your cluster (installation guide)
- Snowplane operator running with a valid
ProviderConfig
# Install kro
helm install kro oci://registry.k8s.io/kro/charts/kro \
--namespace kro-system \
--create-namespace
# Verify
kubectl get pods -n kro-system
Ready-to-Use Compositions
Snowplane ships ready-to-use RGDs in the kro/ directory. Install all of them at once:
kubectl apply -f kro/
kubectl get rgd -owide # All should show State: Active
| RGD | Custom Kind | Resources Created | Key Feature |
|---|---|---|---|
snowflake-project.yaml |
SnowflakeProject |
Database, Schema, Warehouse, AccountRole, GrantPrivilegesToAccountRole | Project bootstrap |
tagged-table.yaml |
TaggedTable |
Table, N × TagAssociation | forEach collections |
data-pipeline.yaml |
SnowflakePipeline |
Table, StreamOnTable, Task | Conditional resources |
rbac-stack.yaml |
SnowflakeRBAC |
AccountRole, N × GrantPrivilegesToAccountRole, AccountRoleAssignment | forEach grants |
Example: Snowflake Project Stack
The SnowflakeProject RGD (source) provisions a complete Snowflake project environment — Database, Schema, Warehouse, Role, and an access Grant — from a single custom resource.
Create a Project Instance
apiVersion: kro.run/v1alpha1
kind: SnowflakeProject
metadata:
name: analytics
namespace: data-team
spec:
name: analytics
environment: prod
warehouseSize: MEDIUM
retentionDays: 7
providerRef:
name: default
kubectl apply -f analytics-project.yaml
# Watch all five resources come up together
kubectl get databases,schemas,warehouses,accountroles,grantprivilegestoaccountroles -n data-team
Clean Up
Deleting the instance removes all managed resources in the correct order:
kubectl delete snowflakeproject analytics -n data-team
Example: Data Pipeline Stack
The SnowflakePipeline RGD (source) creates a streaming data pipeline — Table, StreamOnTable, and Task — with conditional resource inclusion via includeWhen. When enableStream is false, only the landing table is created.
Create a Pipeline Instance
apiVersion: kro.run/v1alpha1
kind: SnowflakePipeline
metadata:
name: orders-pipeline
namespace: data-team
spec:
name: ORDERS_PIPELINE
providerRef:
name: default
databaseRef:
name: analytics-db
schemaRef:
name: analytics-schema
warehouseRef:
name: analytics-wh
columns:
- name: ID
type: NUMBER(38,0)
- name: PAYLOAD
type: VARIANT
enableStream: true
taskSchedule: "5 MINUTES"
taskSQL: |
INSERT INTO ANALYTICS.PUBLIC.ORDERS_PROCESSED
SELECT * FROM ANALYTICS.PUBLIC.ORDERS_PIPELINE_STREAM
WHERE METADATA$ACTION = 'INSERT'
kubectl apply -f orders-pipeline.yaml
kubectl get tables,streamontables,tasks -n data-team
Example: Tagged Table
The TaggedTable RGD (source) provisions a Table with N tag associations using forEach collections. Tags are specified as a simple list — kro fans out to one TagAssociation per entry automatically.
Create a Tagged Table
apiVersion: kro.run/v1alpha1
kind: TaggedTable
metadata:
name: orders
namespace: data-team
spec:
name: ORDERS
providerRef:
name: default
databaseRef:
name: analytics-db
schemaRef:
name: analytics-schema
comment: "Order events table with governance tags"
columns:
- name: ID
type: NUMBER(38,0)
nullable: false
- name: CUSTOMER_ID
type: VARCHAR(255)
- name: CREATED_AT
type: TIMESTAMP_NTZ
tags:
- tagRef:
name: cost-center
value: engineering
- tagRef:
name: environment
value: production
- tagRef:
name: pii-level
value: "none"
kubectl apply -f orders.yaml
# kro creates: 1 Table + 3 TagAssociations
kubectl get tables,tagassociations -n data-team
How
forEachworks here: kro iterates overspec.tagsand creates oneTagAssociationper entry. The${table.status.fullyQualifiedName}expression in the RGD ensures tag associations wait for the Table to be ready — kro resolves this as a DAG dependency automatically. Adding or removing entries from thetagslist causes kro to create or delete the correspondingTagAssociationresources.
Example: RBAC Stack
The SnowflakeRBAC RGD (source) creates an account role with N privilege grants (via forEach) and an optional role assignment (via includeWhen).
Create an RBAC Stack
apiVersion: kro.run/v1alpha1
kind: SnowflakeRBAC
metadata:
name: analytics-reader
namespace: data-team
spec:
roleName: ANALYTICS_READER
comment: "Read-only access to analytics database"
providerRef:
name: default
grants:
- privilege: USAGE
on:
accountObject:
objectType: DATABASE
objectName: ANALYTICS
- privilege: USAGE
on:
schema:
schemaName: '"ANALYTICS"."PUBLIC"'
- privilege: SELECT
on:
schemaObject:
future:
objectTypePlural: TABLES
inSchema: '"ANALYTICS"."PUBLIC"'
assignToRole: SYSADMIN
kubectl apply -f analytics-reader.yaml
# kro creates: 1 AccountRole + 3 GrantPrivilegesToAccountRoles + 1 AccountRoleAssignment
kubectl get accountroles,grantprivilegestoaccountroles,accountroleassignments -n data-team
How
forEachworks here: kro iterates overspec.grantsand creates oneGrantPrivilegesToAccountRoleper entry, each linked to the role viaaccountRoleRef. TheassignToRolefield usesincludeWhento conditionally create anAccountRoleAssignment— leave it empty to skip the assignment.
How It Works
flowchart TD
U["Developer applies<br/>SnowflakeProject CR"] --> K["kro controller"]
K --> DAG["DAG resolution<br/>(CEL expressions)"]
DAG --> DB["Database"]
DAG --> WH["Warehouse"]
DAG --> R["AccountRole"]
DB --> S["Schema<br/>(depends on Database)"]
R --> G["GrantPrivilegesToAccountRole<br/>(depends on Role)"]
S --> SP["Snowplane reconcilers"]
G --> SP
WH --> SP
DB --> SP
R --> SP
SP --> SF["Snowflake"]
- Developer applies a single SnowflakeProject (or any custom kind)
- kro resolves CEL expressions and builds a DAG of dependencies
- Resources are created in topological order — Database before Schema, Role before Grant
- Snowplane reconcilers handle each resource independently
- kro monitors all resources and reports aggregate status
Tips
ProviderConfig passthrough — Thread
providerConfigRefthrough the RGD schema so each instance can target a different Snowflake account.
Namespace isolation — Each RGD instance lives in a namespace. Combine with Snowplane’s namespace-scoped mode (
--namespace-scope) for multi-tenant setups.
CRD ordering — Snowplane CRDs must be installed before you create an RGD that references them. kro validates resource templates against the cluster’s API schema at RGD creation time.
Comparison with Other Approaches
| Approach | Dependency Management | Self-Service API | Drift Correction |
|---|---|---|---|
| kro | Automatic DAG | Yes (custom CRD) | kro + Snowplane |
| Argo CD | Sync waves / hooks | No | Argo + Snowplane |
| Flux | dependsOn |
No | Flux + Snowplane |
| Helm | Template ordering | No | Manual |
kro is complementary to GitOps tools — you can manage RGD manifests and instances via Argo CD or Flux and get the best of both worlds.
Further Reading
- kro documentation
- kro GitHub repository
- ResourceGraphDefinition concepts
- Shipped RGDs — ready-to-use compositions
- GitOps with Argo CD — combine kro with Argo CD
- GitOps with Flux — combine kro with Flux