
1. One-line hook
A Kubernetes Operator in Go that automates the lifecycle of PostgreSQL/Valkey database clusters through a custom resource.
Status: in progress — actively building. CRD + reconciler + idempotent scaling working today.
Source: github.com/vaibhavmahiwal/mini_everest
2. The problem
Normally, deploying a production database on Kubernetes means hand-writing and maintaining YAML for StatefulSets, storage, networking, and backups. This project lets someone declare replicas: 3 and have the system provision and continuously maintain everything underneath automatically.
In other words: the user declares what they want (a DatabaseCluster), the operator handles how it stays that way.
3. Architecture
DatabaseCluster CR │ ▼ Reconciler (controller-runtime) │ ├── Deployment / StatefulSet ├── Service ├── PVC (in progress) └── CronJob for backups (planned)
Flow: CR → Reconciler watches → CreateOrUpdate child resources → update status → requeue on drift. A clean box diagram beats a wall of text — this is the 5-second version.
4. What's actually working right now
Currently implemented:
- Custom Resource Definition (
DatabaseCluster) - controller-runtime-based reconciler watching the CR
- Automated Deployment provisioning and scaling from
spec - Verified idempotent reconciliation
In progress:
- StatefulSet + PVC-based storage
- Multi-engine support (PostgreSQL / Valkey)
- Automated backups via CronJob
5. One real technical decision
Why controllerutil.CreateOrUpdate instead of hand-rolled Get/Create/Update.
Hand-rolled logic splits into three branches (get → not-found → create → else-update), each with its own error handling and race windows. CreateOrUpdate collapses that into a single mutate function that is fetched, mutated, and patched atomically — fewer drift bugs, less code, and idempotency for free on every requeue.
Related call: I built the raw client-go version (Pod Label Syncer in learn/) before reaching for the framework, so the controller-runtime abstractions (informers, caches, workqueues) map to something concrete instead of magic. And spec stays user-writable while status is controller-owned — never letting users write status avoids conflicting sources of truth.
6. A real bug / challenge story
Early on, scaling replicas: 2 → 3 worked once, then fought itself on the next loop — the reconciler kept issuing updates even when nothing changed, causing hot-loop requeues.
Root cause: the mutate function was setting fields unconditionally (e.g. defaulting + pointer churn), so each pass looked "dirty" to the API server. Fix: make mutation declarative and compare-first — only set fields from spec, leave server-defaulted fields alone, and let CreateOrUpdate return unchanged when there is no diff. That one fix taught me the core operator lesson: a reconciler must converge to a no-op.
Use this story in interviews: problem → hot loop → unconditional mutation → declarative diff → idempotent no-op.