Kubernetes Operator Scaffolder
Generate a complete, production-ready Kubernetes operator project from a high-level resource description. Produces Custom Resource Definitions (CRDs), reconciliation controllers, RBAC manifests, admission webhooks, Dockerfiles, and CI scaffolding — following the Operator Framework and controller-runtime best practices so you skip weeks of boilerplate.
Use when: "scaffold a kubernetes operator", "create a CRD and controller", "generate operator boilerplate", "build a k8s operator for X", or when you need to extend the Kubernetes API with custom resources.
Prerequisites
Before scaffolding, the agent checks for:
# Go operator (kubebuilder path)
go version # Go 1.22+
kubebuilder version # kubebuilder 4.x
controller-gen --version
kustomize version
# Python operator (kopf path)
python3 --version # 3.11+
pip show kopf # kopf framework
pip show kubernetes # k8s client
If tools are missing, the agent provides install commands before proceeding.
Usage
Provide the following inputs:
- Resource name — the noun your operator manages (e.g.,
Database,CacheCluster,MLPipeline) - API group — the Kubernetes API group (e.g.,
infra.example.com) - API version — typically
v1alpha1for new operators - Language —
go(kubebuilder) orpython(kopf) - Spec fields — the fields users will set in the custom resource (name, type, default, validation)
- Reconciliation behavior — what the controller should do when the resource is created, updated, or deleted
Example invocation:
Scaffold a Go operator for a
PostgresClusterresource in thedb.example.comgroup. Spec fields: replicas (int, default 3), version (string, default "16"), storageSize (string, default "10Gi"). On create, it should provision a StatefulSet with PVCs. On delete, clean up PVCs.
How It Works
Step 1: Project Structure Generation
Create the full directory tree:
operator-name/
├── api/
│ └── v1alpha1/
│ ├── types.go # CRD Go types with markers
│ ├── groupversion_info.go # scheme registration
│ └── zz_generated.deepcopy.go
├── cmd/
│ └── main.go # entrypoint with manager setup
├── internal/
│ └── controller/
│ ├── reconciler.go # main reconcile loop
│ ├── reconciler_test.go # envtest-based tests
│ └── finalizer.go # cleanup logic
├── config/
│ ├── crd/
│ │ ├── kustomization.yaml
│ │ └── bases/
│ │ └── resource_crd.yaml # generated CRD manifest
│ ├── rbac/
│ │ ├── role.yaml # ClusterRole
│ │ ├── role_binding.yaml # ClusterRoleBinding
│ │ ├── service_account.yaml
│ │ └── kustomization.yaml
│ ├── manager/
│ │ ├── manager.yaml # Deployment
│ │ └── kustomization.yaml
│ ├── webhook/ # if webhooks requested
│ │ ├── manifests.yaml
│ │ └── kustomization.yaml
│ └── default/
│ └── kustomization.yaml # ties everything together
├── hack/
│ └── boilerplate.go.txt
├── Dockerfile
├── Makefile
├── go.mod
├── go.sum
├── PROJECT # kubebuilder project metadata
└── README.md
For Python (kopf) operators, the structure mirrors this with src/handlers.py (kopf decorators), src/resources.py (resource builders), deploy/ (CRD + RBAC + Deployment + kustomize), tests/, Dockerfile, Makefile, and pyproject.toml.
Step 2: CRD Definition
Generate the Custom Resource Definition with:
- OpenAPI v3 schema validation — every spec field gets proper types, defaults, min/max constraints, enum values, and descriptions
- Status subresource — with conditions following the
metav1.Conditionstandard (Type, Status, Reason, Message, LastTransitionTime) - Printer columns — so
kubectl get <resource>shows useful information at a glance - Short names — for convenience (e.g.,
pgforPostgresCluster) - Categories — group with
kubectl get all
Example CRD type definition (Go):
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=`.spec.replicas`
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:resource:shortName=pg;pgc
type PostgresCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PostgresClusterSpec `json:"spec,omitempty"`
Status PostgresClusterStatus `json:"status,omitempty"`
}
type PostgresClusterSpec struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=10
// +kubebuilder:default=3
Replicas int32 `json:"replicas,omitempty"`
// +kubebuilder:validation:Pattern=`^\d+$`
// +kubebuilder:default="16"
Version string `json:"version,omitempty"`
// +kubebuilder:default="10Gi"
StorageSize string `json:"storageSize,omitempty"`
}
type PostgresClusterStatus struct {
Phase string `json:"phase,omitempty"`
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
Step 3: Controller / Reconciler
Generate the reconciliation loop with these patterns:
Idempotent reconciliation — every reconcile call converges toward the desired state without side effects on repeated runs:
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. Fetch the custom resource
var cluster dbv1alpha1.PostgresCluster
if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. Handle deletion with finalizers
if !cluster.DeletionTimestamp.IsZero() {
return r.handleDeletion(ctx, &cluster)
}
if err := r.ensureFinalizer(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
// 3. Reconcile owned resources (create-or-update pattern)
if err := r.reconcileStatefulSet(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
if err := r.reconcileService(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
// 4. Update status
if err := r.updateStatus(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
Key patterns included:
- Owner references — child resources (StatefulSet, Service, ConfigMap) are owned by the CR so garbage collection works automatically
- Finalizers — for cleanup of external resources (e.g., PVCs, cloud resources) that don't get garbage-collected
- Status conditions — update conditions using
meta.SetStatusConditionfollowing KEP-1623 - Event recording — emit Kubernetes events for important state transitions
- Exponential backoff — on transient failures, requeue with increasing delay
- Watches — watch owned resources so changes to child objects trigger reconciliation
Step 4: RBAC Generation
Generate least-privilege RBAC from the controller's actual API calls:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: operator-manager-role
rules:
# Custom resource
- apiGroups: ["db.example.com"]
resources: ["postgresclusters"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["db.example.com"]
resources: ["postgresclusters/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["db.example.com"]
resources: ["postgresclusters/finalizers"]
verbs: ["update"]
# Owned resources
- apiGroups: ["apps"]
resources: ["statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "configmaps", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Events
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
The agent reviews each verb and resource group, removing anything the controller doesn't actually need.
Step 5: Dockerfile and Build
Generate a multi-stage Dockerfile:
FROM golang:1.22 AS builder
ARG TARGETOS TARGETARCH
WORKDIR /workspace
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
go build -a -o manager cmd/main.go
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
Step 6: Testing Scaffold
Generate test files using envtest (Go) or pytest with a fake k8s client (Python). Tests cover: CR creation triggers child resource creation with correct spec, spec updates propagate to child resources, deletion triggers finalizer cleanup, status conditions are set correctly, and error cases requeue with backoff.
Step 7: Makefile
Generate a Makefile with standard targets: manifests (CRD generation), generate (deepcopy), test (envtest), build, docker-build, install (CRDs into cluster), and deploy (full operator deployment via kustomize).
Output
The agent produces:
- Complete project directory — ready to
go build/pip installanddocker build - CRD YAML — with full OpenAPI schema, ready to
kubectl apply - RBAC manifests — least-privilege ClusterRole, ClusterRoleBinding, ServiceAccount
- Controller code — idempotent reconciler with finalizers, status updates, event recording
- Test scaffold — envtest or pytest setup with example test cases
- Dockerfile — multi-stage, distroless, non-root
- Makefile — standard build, test, deploy targets
- Sample CR — an example custom resource YAML for users to try
Best Practices Enforced
- No cluster-admin — RBAC is scoped to exactly the resources the controller touches
- Finalizers before external resources — prevents orphaned cloud resources
- Status conditions, not status strings — follows the standard Condition type for interoperability
- Leader election — enabled by default for HA deployments
- Health probes — readiness and liveness endpoints on the manager
- Metrics — Prometheus metrics endpoint exposed via controller-runtime
- Structured logging — uses
logr/structlog, nofmt.Println - Owner references on all child resources — garbage collection works correctly
- Distroless container image — minimal attack surface
- Non-root user — container runs as UID 65532
Supported Operator Patterns
The agent recognizes and scaffolds these patterns: level-triggered reconciliation (desired state convergence), finalizer-based cleanup for external resources, status aggregation from child resources, config drift detection and correction, dependent resource ordering (e.g., Service after StatefulSet), and external resource management (cloud APIs, DNS).