KubeBlocks: Running Databases on Kubernetes Without Building Your Own Operator
Kubernetes was built for stateless workloads, and it shows the moment you try to run a database on it.
A web server pod dying is a non-event: the ReplicaSet notices, spins up a replacement, and the Service routes around the gap without anyone waking up. A Postgres primary dying is a very different problem. Kubernetes has no idea what a "primary" is, doesn't know that a replacement pod needs to restore from a specific WAL position before it can serve traffic, and has no concept of promoting a replica or updating whichever secrets and connection strings pointed at the pod that just disappeared.
StatefulSets get you partway, then stop
StatefulSets solve the easy 20% of running a database on Kubernetes: stable network identities, ordered pod names, and persistent volumes that follow a pod across rescheduling. What they don't give you is anything close to what a real production database needs day to day:
- Automated failover when the primary goes down
- Point-in-time backups and restore
- Safe, coordinated version upgrades across a replica set
- Horizontal and vertical scaling that respects replication topology
- Connection routing that knows which pod is currently primary
Historically, teams filled that gap by hand: Patroni for failover, pgBackRest or WAL-G for backups, custom sidecars for connection routing, homegrown operators for everything else glued together with shell scripts and cron jobs. It works, but every team ends up rebuilding a slightly different, under-tested version of the same operator.
What KubeBlocks actually is
KubeBlocks is a Kubernetes operator, built by ApeCloud, whose job is to be that missing layer - a generic control plane for running stateful workloads declaratively, instead of a one-off operator per database engine. Rather than write a new operator every time you add MySQL, Redis, MongoDB, or Kafka to your stack, KubeBlocks defines a common data model and a common reconciliation loop, then plugs each engine in as an "addon."
The core abstractions are CRDs:
- ClusterDefinition - describes the topology and lifecycle of a database engine in the abstract: what components make it up (a Postgres cluster has a primary/replica component, for example), what actions are needed for common lifecycle events (start, stop, switchover, backup, restore, scale), and how those actions get invoked.
- ComponentDefinition - a more granular building block that newer ClusterDefinitions compose, describing a single replicated component's container image, config templates, and lifecycle scripts.
- Cluster - the object you actually create. It references a ClusterDefinition and a version, and declares the shape you want: how many replicas, what resources, what storage, what backup policy.
When you apply a Cluster, KubeBlocks' controller reconciles it into StatefulSets, Services, ConfigMaps, and Jobs under the hood - the same building blocks you'd hand-assemble yourself, but driven by an engine-specific playbook that already knows how to run a switchover on that particular database instead of guessing.
Trying it
Installing KubeBlocks itself is a normal Helm install:
opsquiz@devops-essentialsopsquiz@devops:~$ helm repo add kubeblocks https://apecloud.github.io/helm-chartsopsquiz@devops:~$ helm repo updateopsquiz@devops:~$ helm install kubeblocks kubeblocks/kubeblocks \--namespace kb-system --create-namespace \--version 0.9.0
Each database engine ships as its own addon chart, so you install the Postgres addon separately:
opsquiz@devops-essentialsopsquiz@devops:~$ helm install kb-addon-postgresql kubeblocks/postgresql \--namespace kb-system
Then you request an actual database by applying a Cluster manifest:
opsquiz@devops-essentialsopsquiz@devops:~$ kubectl apply -f - <<EOFapiVersion: apps.kubeblocks.io/v1alpha1kind: Clustermetadata:name: pg-clusternamespace: defaultspec:clusterDefinitionRef: postgresqlclusterVersionRef: postgresql-14.8.0terminationPolicy: DeletecomponentSpecs:- name: postgresqlcomponentDefRef: postgresqlreplicas: 3resources:requests: { cpu: "0.5", memory: 0.5Gi }limits: { cpu: "1", memory: 1Gi }volumeClaimTemplates:- name: dataspec:accessModes: ["ReadWriteOnce"]resources:requests: { storage: 20Gi }EOF
opsquiz@devops-essentialsopsquiz@devops:~$ kubectl get cluster pg-clusterNAME CLUSTER-DEFINITION VERSION TERMINATION-POLICY STATUS AGEpg-cluster postgresql postgresql-14.8.0 Delete Running 118s
That single Cluster object gets you a three-node Postgres replica set with failover already wired up. Killing the primary pod triggers an automated switchover instead of an outage; kbcli cluster backup pg-cluster (or the equivalent Backup CRD) takes a scheduled or on-demand backup; scaling is a kubectl patch or kbcli cluster vscale/hscale away.
Why this matters if you're the one on call
If your team runs databases on Kubernetes today without something like KubeBlocks, you're almost certainly running one of two setups: a managed cloud database sitting outside the cluster, which works but means your stateful and stateless workloads live in two different operational worlds with two different sets of tooling, or a hand-rolled StatefulSet-plus-Patroni-plus-scripts setup that one or two people on the team actually understand and everyone else is scared to touch.
KubeBlocks doesn't eliminate the complexity of running a database - failover, backups, and replication are still hard problems - but it moves that complexity into a tested, declarative, version-controlled Cluster manifest instead of institutional knowledge and bash scripts. It's still a relatively young project and multi-engine support varies in maturity, so it's worth validating failover and backup/restore behavior for your specific engine and version before trusting it in production. But as an operating model - treat the database like any other Kubernetes-native resource, reconciled the same way your Deployments are - it's a meaningfully better starting point than building the equivalent tooling yourself.