Building and Managing Cloud-Native Applications with Kubernetes Operators

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 schemacontrollers/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
Subscribe to my newsletter
Read articles from John Abioye directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
