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).
Scan to join WeChat group