Introduction

Kubernetes is not just a container orchestration platform. It is a platform that can be extended with custom APIs.

In this blog, the following topics will be covered:

  • what the Kubernetes API is and how it can be extended using a Custom Resource Definition (CRD for short),
  • what a Kubernetes operator is and how it can be implemented,
  • a practical example of extending the Kubernetes API using a CRD,
  • a practical example of implementing an operator that manages objects of the new CRD.

As part of the hands-on example, a Website CRD and an operator will be implemented. Based on Website objects, the operator will automatically create a Deployment, Service, and Ingress.

Kubernetes API

Kubernetes is an API-driven platform, meaning all resources in the cluster are managed through the Kubernetes API server.

Creating Kubernetes resources (e.g., Deployment, Service, Ingress, etc.) is done by sending a request to the Kubernetes API server, which:

  • validates the object
  • stores it in etcd
  • and makes it available to other Kubernetes components

It is important to understand that the API server does not execute changes directly. It only stores the desired state, while background controllers reconcile and try to align the system’s current state with it.

This model makes Kubernetes a declarative system so the user defines what they want, and Kubernetes tries to achieve it.

How CRDs Extend the Kubernetes API

Although Kubernetes comes with a large number of predefined resources, there is often a need to create custom types of objects, like:

  • Database
  • KafkaCluster
  • BackupPolicy
  • Website

A CRD allows you to create new types of resources without modifying the Kubernetes source code.

After creating a CRD, the new resource becomes part of the Kubernetes API and can be used through:

  • kubectl
  • YAML manifests
  • API calls

just like standard Kubernetes resources.

CRDs therefore, turn Kubernetes into an extensible platform capable of managing resources from any domain.

It is important to understand the difference between a CRD, which defines only the schema of a new Kubernetes resource, and a Custom Resource (CR for short), which represents an instance (an object) of that CRD.

Kubernetes Operator and reconciliation loop

A Kubernetes operator is a controller that, by observing a CRD and its instances, implements defined logic and automatically brings the system into the desired state.

Its main responsibility is the reconciliation loop, which continuously compares the desired state defined in the custom resource with the actual state of the system.

If there is a difference between the desired and actual state, the operator performs the necessary actions.

In the hands-on example, it will be shown how the operator creates the required objects based on a Website resource, and it is going to be:

  • Deployment
  • Service
  • Ingress

In addition to being event-driven, meaning it reacts to events related to the resources it watches, an operator is also state-based, meaning that every time it reacts, it tries to bring the system into the desired state.

Lifecycle from CRD to Managed Resource

The lifecycle of an operator can be described through the following steps:

  1. CRD registration
    The Kubernetes API is extended with a new resource type.
  2. Creating a custom resource
    The user defines the desired state through a YAML manifest.
  3. Validation and storage
    The API server validates the resource based on the CRD schema and stores it in etcd.
  4. Change detection
    The operator receives an event through the watch mechanism.
  5. Reconciliation loop
    The operator compares the desired state with the current state of the system.
  6. Creating or updating resources
    Required resources are created or updated, and they can be inside or outside the cluster.
  7. Status update
    The operator updates the observed state in the CR status section.

This process runs continuously throughout the entire lifecycle of the resource.

Kubernetes Operator Cluster diagram.

OwnerReferences and Finalizers

Operators not only manage resource creation, but also their lifecycle.

OwnerReferences

OwnerReferences define the relationship between the parent CR (e.g., Website) and child (Deployment, Service, Ingress …) resources.

If an operator creates a Deployment, Service, or Ingress and links them to a Website resource, the Kubernetes garbage collector will automatically delete the child resources when the parent is deleted (cascading deletion). However, if a child resource is deleted first, the operator will recreate it during the reconciliation loop, as it always aims to restore the systemto the desired state defined in the Website resource.

How it looks in the child resource definition:

ownerReferences:
- apiVersion: website.demo.atlantbh.com/v1
blockOwnerDeletion: true
controller: true
kind: Website
name: abh-demo-website
uid: 9be6fcb1-cf19-40ce-bfed-8f7c13c23057

Finalizers

Finalizers allow additional cleanup logic before a resource is permanently deleted.

When a resource contains a finalizer:

  • Kubernetes sets the deletionTimestamp
  • the resource remains in the cluster
  • the operator performs cleanup logic

Typical examples include:

  • deleting cloud resources
  • DNS cleanup
  • backing up data before deletion

Only after the cleanup is complete does the operator remove the finalizer, and Kubernetes permanently deletes the resource. A finalizer can be seen as a blocker during deletion, meaning the resource cannot be deleted until all finalizers are removed, i.e., until the entire cleanup process is finished.nalizer can be seen as a blocker during deletion, meaning the resource cannot be deleted until all finalizers are removed, i.e., until the entire cleanup process is finished.

Implementation Using Kubebuilder

For implementing a Kubernetes operator, one framework to consider  is Kubebuilder, which is built on top of the controller-runtime library.

Kubebuilder provides:

  • generation of CRD and RBAC manifest files
  • controller skeleton
  • controller-runtime integration
  • standardized project structure for operators

This keeps the focus on business logic rather than boilerplate code. 

The complete source code of the hands-on example of the blog is available in the GitHub repository.

Hands-on Example: Website CRD

A simple example demonstrates the implementation of a Kubernetes operator and CRD. The complete CRD is available at the following location.
Based on the previously defined CRD, the user defines a custom resource through a YAML file:


apiVersion: website.demo.atlantbh.com/v1
kind: Website
metadata:
name: abh-demo-website
spec:
image: nginx:latest
replicas: 3
port: 80
env:
- name: ENV
value: "production"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"

What Does the Operator Do?

After this custom resource is created, the operator runs a reconcile loop:

  • creates a Deployment (image, replicas, env, resources)
  • creates a Service (exposes the port)
  • creates an Ingress (exposing the application)

Then, through the reconciliation loop, it ensures:

  • the number of pods matches replicas
  • the resources are available
  • routing is functioning correctly

CRD Validation

CRDs use OpenAPI v3 schema validation.

In this example:

In this way, invalid resources are rejected by the Kubernetes API before they ever reach the operator.

Conclusion

Custom Resource Definitions enable extending the Kubernetes API and creating custom resource types within a cluster. 

When combined with the operator pattern, Kubernetes becomes a platform for automating complex systems, not just container orchestration.

Kubebuilder significantly simplifies operator development, allowing developers to focus on business logic rather than infrastructure boilerplate code.

This example demonstrates how a simple Website resource can become a declarative interface for automatically managing a web application.

Leave a comment

Your email address will not be published. Required fields are marked *