Building and Managing Cloud-Native Applications with Kubernetes Operators

John AbioyeJohn Abioye
5 min read

Introduction

Kubernetes has become the cornerstone of modern cloud-native infrastructure. With its powerful orchestration capabilities, it manages everything from container deployment to scaling and networking. But as applications grow in complexity, simply managing deployments with YAML files and Helm charts becomes unwieldy. Enter Kubernetes Operators.

Operators are Kubernetes-native applications that extend the platform's capabilities by managing the entire lifecycle of complex applications automatically and declaratively. Think of an Operator as a controller infused with domain-specific knowledge: it watches for changes to a custom resource and acts accordingly.

This article will explore the world of Kubernetes Operators, demonstrate how to build one using Go and the Operator SDK, and manage a custom cloud-native application through Custom Resource Definitions (CRDs) and Helm charts.


What Are Kubernetes Operators?

Operators are software extensions to Kubernetes that use custom resources to manage applications and their components. They combine the declarative nature of Kubernetes with operational knowledge embedded into code.

Key Concepts

  • Custom Resource Definition (CRD): Defines a new resource type in Kubernetes, like MyApp, PostgresCluster, etc.

  • Controller: Watches the custom resource and takes actions to bring the current state of the system in line with the desired state.

  • Reconciliation Loop: The operator continuously checks for differences between the actual and desired states and makes necessary changes.


When to Use an Operator?

Operators shine in scenarios where an application:

  • Requires complex configuration

  • Needs consistent lifecycle management (backups, upgrades, scaling)

  • Demands integrations with other services

  • Needs self-healing or auto-recovery capabilities

Examples include database clusters (PostgreSQL, Cassandra), message queues, or any stateful application with intricate orchestration needs.


Tools for Building Operators

Several tools exist for creating Operators, including:

  • Operator SDK (Go): Officially supported and used for building powerful, performant Operators.

  • Kubebuilder: Library for building Kubernetes APIs using Go.

  • Operator SDK + Helm: Allows Operator logic to be built from existing Helm charts.

  • Kopf (Python): A Pythonic framework for creating Kubernetes Operators.

In this guide, we’ll use both Go with Operator SDK and Python (Kopf) for a balanced overview.


Building an Operator with Go (Using Operator SDK)

Prerequisites

  • Go 1.20+

  • Docker

  • Kubernetes cluster (e.g., Minikube, kind, or GKE)

  • kubectl and Operator SDK CLI

Step 1: Initialize the Operator

operator-sdk init \
  --domain example.com \
  --repo github.com/example/myapp-operator

Step 2: Create the API and Controller

operator-sdk create api \
  --group apps \
  --version v1alpha1 \
  --kind MyApp \
  --resource --controller

This creates:

  • api/v1alpha1/myapp_types.go: Custom resource schema

  • controllers/myapp_controller.go: Reconciliation logic

Step 3: Define the CRD

Edit api/v1alpha1/myapp_types.go:

// MyAppSpec defines the desired state
type MyAppSpec struct {
    Replicas int32 `json:"replicas"`
    Image    string `json:"image"`
}

// MyAppStatus defines the observed state
type MyAppStatus struct {
    AvailableReplicas int32 `json:"availableReplicas"`
}

Step 4: Implement the Controller Logic

Edit controllers/myapp_controller.go:

func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var myapp appsv1alpha1.MyApp
    if err := r.Get(ctx, req.NamespacedName, &myapp); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    deployment := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      myapp.Name,
            Namespace: myapp.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &myapp.Spec.Replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: map[string]string{"app": myapp.Name},
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{"app": myapp.Name},
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{{
                        Name:  "myapp",
                        Image: myapp.Spec.Image,
                    }},
                },
            },
        },
    }

    if err := r.Create(ctx, deployment); err != nil {
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}

Step 5: Deploy the Operator

make install
make docker-build docker-push IMG="your-dockerhub/myapp-operator:v0.1.0"
make deploy IMG="your-dockerhub/myapp-operator:v0.1.0"

Apply a MyApp resource:

apiVersion: apps.example.com/v1alpha1
kind: MyApp
metadata:
  name: myapp-sample
spec:
  replicas: 3
  image: nginx:latest

Building an Operator with Python (Kopf)

Prerequisites

  • Python 3.8+

  • pip install kopf kubernetes

  • Access to a Kubernetes cluster

Step 1: Define the Operator Logic

import kopf
import kubernetes

@kopf.on.create('example.com', 'v1', 'myapps')
def create_fn(spec, name, namespace, logger, **kwargs):
    replicas = spec.get('replicas', 1)
    image = spec.get('image', 'nginx:latest')

    deployment = {
        'apiVersion': 'apps/v1',
        'kind': 'Deployment',
        'metadata': {'name': name, 'namespace': namespace},
        'spec': {
            'replicas': replicas,
            'selector': {'matchLabels': {'app': name}},
            'template': {
                'metadata': {'labels': {'app': name}},
                'spec': {
                    'containers': [{'name': name, 'image': image}]
                }
            }
        }
    }

    api = kubernetes.client.AppsV1Api()
    api.create_namespaced_deployment(namespace=namespace, body=deployment)
    logger.info(f"Deployment for {name} created with image {image}")

Step 2: Define the CRD

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myapps.example.com
spec:
  group: example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                replicas:
                  type: integer
                image:
                  type: string
  scope: Namespaced
  names:
    plural: myapps
    singular: myapp
    kind: MyApp
    shortNames:
      - ma

Step 3: Run the Operator

kopf run my_operator.py

Apply the same CRD and a sample resource:

apiVersion: example.com/v1
kind: MyApp
metadata:
  name: myapp-sample
spec:
  replicas: 2
  image: httpd:latest

Using Helm Charts with Operators

If you already have a Helm chart for your application, the Operator SDK allows you to build an Operator around it:

operator-sdk init --plugins=helm --domain=example.com --repo=github.com/example/helm-operator
operator-sdk create api --group apps --version v1alpha1 --kind MyApp --helm-chart=./charts/myapp

Now you have an Operator that deploys your Helm chart every time the MyApp resource changes.


Best Practices for Operator Development

  • Use CRDs to expose only necessary knobs to users

  • Include validation through OpenAPI schemas

  • Implement proper error handling in the reconciliation loop

  • Make the Operator idempotent and stateless as much as possible

  • Include metrics and observability (e.g., Prometheus)

  • Watch only the necessary resources to reduce memory and CPU overhead


Conclusion

Kubernetes Operators provide a powerful abstraction for managing the full lifecycle of cloud-native applications. Whether you build one in Go for performance or Python for flexibility, Operators encode human operational knowledge into code, turning your Kubernetes cluster into a self-operating platform.

By using CRDs, Helm, and custom logic, you can automate installations, upgrades, backups, and failure recovery all declaratively and reliably. It’s not just about automation; it’s about intelligent automation.

Whether you're a DevOps engineer, SRE, or platform architect, mastering Operators equips you with a formidable toolset for next-gen Kubernetes-native app management.


Tags: #Kubernetes #Operators #DevOps #GoLang #Python #CRD #Helm #CloudNative

0
Subscribe to my newsletter

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

Written by

John Abioye
John Abioye