Write a no-code Kubernetes Operator with KRO

Aniket KanadeAniket Kanade
5 min read

Containers are beautiful, only if there was a way to deploy them at scale and manage in automated way to serve the varying traffic requirements - and Kubernetes was born. Kubernetes is beautiful, only if there was a way to deploy all Kubernetes resources packaged together as a unit - and Helm was born. Helm is beautiful, only if there was a way to watch the resources deployed so they don’t drift from desired configuration - and operators were born. Operators are beautiful, only if there was a simple way to code them without having to learn golang or any complex framework - and KRO is born.

Kubernetes Resource Orchestrator (KRO), the way I understand it, is essentially a super operator that helps you create your own custom CRDs from simple templates and manages instances (CRs) of those CRDs for you. This is essentially aimed at simplifying Platform Engineering to provide dev teams easy way to configure applications directly with kubernetes-native simple declarative ‘kubectl apply’, abstracting all the kubernetes-specific complexity like deployments, pods, configmaps, secrets etc. in the application specific CRD, which itself is created and managed by KRO.

How Does KRO Work?

(Ref: https://kro.run/docs/overview)

This is the reference image of KRO from KRO-docs. Everything under ‘Group of Resources’ are essentially kubernetes resources created with single yaml file of configurable inputs fed to ‘kubectl apply’ command.

To get started with KRO, very first thing you do is deploy KRO operator on your kubernetes cluster with helm chart. From that point, all of the application deployments can designed and handled by KRO. Everything in KRO starts with defining instance of ‘ResourceGraphDefinition’ CRD created by CRO.

export KRO_VERSION=$(curl -sL \
    https://api.github.com/repos/kro-run/kro/releases/latest | \
    jq -r '.tag_name | ltrimstr("v")'
  )

echo $KRO_VERSION

helm install kro oci://ghcr.io/kro-run/kro/kro \
  --namespace kro \
  --create-namespace \
  --version=${KRO_VERSION}

Once kro is installed, up and running. Let’s understand how to create our first app with the help of example.

Let’s say you have a ‘greet-sleep-repeat’ application that prints a custom greeting, sleeps for configured duration and repeats.

Let’s define our application using ResourceGraphDefinition.

Step-1] Create the Boilerplate of ResourceGraphDefinition (RGD) for our app

apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: greet-app
spec:
  schema:
  resources:

Spec of RGD has two main elements
schema:
This defines the input variables, their datatype and yaml structure. This is much like defining values.yaml for helm chart, but also enriched with datatypes.
resources:
This defines template for kubernetes resources to be deployed. This section closely maps to ‘templates’ directory of helm charts.

Step-2] Define ‘schema’ for the app

apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: greet-app
spec:
  schema:
    apiVersion: v1alpha1
    kind: GreetSleepRepeat
    spec:
      # Basic types
      name: string | required=true description="Name of App instance being deployed"
      my_greeting: string | required=true description="How would I like to be greeted?"
      my_name: string | required=true description="My Name"
      duration: integer | default=60 description="Duration"
      replicas: integer | default=1 minimum=1 maximum=100
    status:
      # Status fields with auto-inferred types
      availableReplicas: ${deployment.status.availableReplicas}
  resources:

As shown in the schema, KRO will define a new CRD for our app with the name ‘GreetSleepRepeat’. In the schema, we have also outlined the required and optional inputs needed to correctly deploy a CR of the kind GreetSleepRepeat. Additionally, there is a ‘status’ field that determines the output of ‘kubectl status GreetSleepRepeat my-greet-app’ command.

Step-3] Define the resources deployed as a part of the CRD ‘GreetSleepRepeat’

So here is the full YAML

apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: greet-app
spec:
  schema:
    apiVersion: v1alpha1
    kind: GreetSleepRepeat
    spec:
      # Basic types
      name: string | required=true description="Name of App instance being deployed"
      my_greeting: string | required=true description="How would I like to be greeted?"
      my_name: string | required=true description="My Name"
      duration: integer | default=60 description="Duration"
      replicas: integer | default=1 minimum=1 maximum=100
    status:
      # Status fields with auto-inferred types
      availableReplicas: ${deployment.status.availableReplicas}

  resources:
  - id: deployment
    template:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: ${schema.spec.name} # Use the name provided by user
      spec:
        replicas: ${schema.spec.replicas}
        selector:
          matchLabels:
            app: ${schema.spec.name}
        template:
          metadata:
            labels:
              app: ${schema.spec.name}
          spec:
            containers:
            - name: ${schema.spec.name}
              image: busybox:latest # Use the image provided by user
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do echo '${schema.spec.my_greeting} ${schema.spec.my_name}';sleep ${schema.spec.duration}; done;" ]
              ports:
                - containerPort: 80
  - id: service
    template:
      apiVersion: v1
      kind: Service
      metadata:
        name: ${schema.spec.name}-svc
      spec:
        selector: ${deployment.spec.selector.matchLabels} # Use the deployment selector
        ports:
          - protocol: TCP
            port: 80
            targetPort: 80

So here we have two kubernetes resources viz. Deployment and Service deployed and monitored by KRO based on the template defined under resources.

Once we have this yaml file ready, we just register out RGD with KRO and our CRD is ready for application developers to use.

$ kubectl apply -f greetapp-rgd.yaml

Step-4] Verify and create out first app

Ok, so now that we have registered our rgd with KRO, let’s verify if we have our CRD is deployed and create first app of our kind

#Get on RGD to see how our app is registered
$ kubectl get rgd greet-app -owide

#Get our specific CRD created by greet-app rgd
#Notice that the crd created has plural of 'greetsleeprepeat' 
$ kubectl get crd | grep -i greet

#Let's get resources of type our CRD, there should be nothing right now
$ kubectl get GreetSleepRepeat -A

#Let's create our first app
$ cat <<EOF > greetapp.yaml
apiVersion: kro.run/v1alpha1
kind: GreetSleepRepeat
metadata:
  name: my-greet-app
spec:
  name: "my-greet-app"
  my_greeting: "Hi there,"
  my_name: "Aniket!"
  duration: 30
EOF

$ kubectl apply -f greetapp.yaml

#Verify the status of our CR, deployment, service and logs of pod to see it working

Why KRO?

So we know what is KRO and how it works? Now the most important question that we need to ask is - why? Why KRO?

What is in it that we don’t get with helm?

I’d say, right now it is nowhere near helm in terms of maturity. But it has a lot of promise. The reason is, helm is not kubernetes-native and helm does not help with post install config management.

So while helm provides same level of packaging, KRO provides us with simple yaml to configure our own operator that maintains the state of all the resources deployed without having to write it ourselves with golang and kube-builder etc

References

KRO Docs: https://kro.run/docs/overview
Github for the examples: https://github.com/kanadean/ExampleKRO

1
Subscribe to my newsletter

Read articles from Aniket Kanade directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aniket Kanade
Aniket Kanade