Skip to content

Kubernetes Operator with License-Aware Feature Gating

Running a database on Kubernetes means managing StatefulSets, PDBs, ConfigMaps, and scaling policies. ZeptoDB’s operator translates a single ZeptoDBCluster custom resource into a fully configured Helm release — with license gating that prevents unlicensed multi-node deployments at the orchestration layer.


apiVersion: zeptodb.com/v1alpha1
kind: ZeptoDBCluster
metadata:
name: trading-prod
spec:
replicas: 3
version: "0.9.0"
storage:
size: 100Gi
storageClass: gp3
license:
secretName: zeptodb-license
status:
phase: Running
readyReplicas: 3
edition: Enterprise
message: "All nodes healthy"

kubectl get zdb shows printer columns: name, phase, ready replicas, edition, and age.


Single Node

replicas == 1 → Community, always allowed. No license required.

Multi-Node

replicas > 1 → requires spec.license.secretName pointing to a K8s Secret.

Missing License

CR status set to Failed: “Multi-node requires Enterprise license”.

Separation of Concerns

Operator checks secret existence. Binary does RS256 JWT validation at startup.


The operator is a ~120-line bash script polling every 10 seconds:

┌─────────────────────────────────────────────────┐
│ Reconciler Loop │
│ │
│ 1. kubectl get zeptodb --all-namespaces │
│ 2. For each CR: │
│ ├── Read spec (replicas, version, storage) │
│ ├── License gate check (replicas > 1?) │
│ │ ├── No secret → status=Failed, skip │
│ │ └── Secret exists → continue │
│ ├── helm upgrade --install with --set flags │
│ └── Update CR status │
│ 3. Detect deletions → helm uninstall │
│ 4. Sleep 10s │
└─────────────────────────────────────────────────┘
CR FieldHelm Flag
spec.replicas--set replicaCount=N
spec.version--set image.tag=VERSION
spec.storage.size--set persistence.size=SIZE
spec.storage.storageClass--set persistence.storageClass=CLASS
spec.license.secretName--set extraEnv[0].name=ZEPTODB_LICENSE_KEY,...

The license secret is mounted as ZEPTODB_LICENSE_KEY env var via Helm’s extraEnv.


FactorBash OperatorGo Operator
Build dependencyNone (kubectl + helm)Go toolchain + operator-sdk
ModifiabilityAny engineer can editRequires Go + controller-runtime knowledge
ReconnectionSimple poll loop, no edge casesWatch stream needs reconnection handling
BackendHelm chart reuse (StatefulSet, PDB, HPA)Must reimplement or embed Helm

Minimal permissions — the operator only reads secrets (for license checks) and manages workloads:

rules:
- apiGroups: ["zeptodb.com"]
resources: ["zeptodbclusters", "zeptodbclusters/status"]
verbs: ["get", "list", "watch", "patch"]
- apiGroups: ["apps"]
resources: ["statefulsets", "deployments"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]

Enterprise (3-node): replicas: 3, license: { secretName: zeptodb-license }, storage: { size: 100Gi, storageClass: gp3 }

Community (single-node): replicas: 1, storage: { size: 10Gi } — no license field needed.


DecisionRationale
No JWT validation in operatorSeparation of concerns — operator handles K8s, binary handles crypto
Poll interval: 10sFast enough for config changes, low API server load
Helm as backendAvoids reimplementing StatefulSet/PDB/HPA logic
Single-replica operatorSufficient for CRD→Helm translation; no leader election needed