Scaffolding the Project
Scaffolding the Project
Operator SDK builds on Kubebuilder and controller-runtime. A typical session:
kubebuilder init --domain example.com --repo github.com/acme/db-operator
kubebuilder create api --group db --version v1 --kind Database
This generates the API types (api/v1/database_types.go with Spec, Status, and +kubebuilder:object:root markers), a stub internal/controller/database_controller.go, CRD/RBAC/webhook manifests under config/, and Makefile targets. make generate runs controller-gen for DeepCopy methods; make manifests renders CRDs and RBAC from markers — both are mandatory after every type or permission change.
Writing the Reconciler
Writing the Reconciler
// +kubebuilder:rbac:groups=db.example.com,resources=databases,verbs=get;list;watch
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;create;update
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var db dbv1.Database
if err := r.Get(ctx, req.NamespacedName, &db); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// converge: create/update the StatefulSet, then...
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
The manager registers the controller with SetupWithManager, wiring watches back to the CR. The client reads come from an informer cache (cheap, eventually consistent); writes go straight to the API server. Update db.Status through the status subresource and return errors so the workqueue applies exponential backoff.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Stale generated code. Adding a spec field without make generate leaves DeepCopy blind to it — fields silently vanish on serialization.
RBAC drift. Developing with an admin kubeconfig hides missing permissions; run as the restricted ServiceAccount from config/rbac before shipping, or the operator 403s in production.
Status conflicts. Two writers to status cause the object has been modified; patch or re-fetch and retry instead of blind Update.
Hot reconcile loops. Mutating status unconditionally, or comparing cache data that lags, produces endless work — guard writes with equality checks.
Interview angle: describe controller-runtime's split (manager, controller, cached client, workqueue) and explain why your operator still needs its own RBAC manifests.
Quiz
1. Which command pair regenerates code and manifests after a type change?
2. Why might a locally working operator fail with 403 in-cluster?
3. Where do controller-runtime client reads come from?
Flashcards
Question
kubebuilder create api
Click to reveal answer
Answer
Scaffolds types + controller + manifests for a new CRD kind; domain/repo flags fix group paths.
Question
RBAC markers
Click to reveal answer
Answer
// +kubebuilder:rbac:... comments compiled by controller-gen into config/rbac Role rules.
Question
Reconcile return contract
Click to reveal answer
Answer
Return error to requeue with backoff; RequeueAfter for timed follow-ups; zero values mean done until the next event.
Question
envtest
Click to reveal answer
Answer
Starts a real API server (no controllers) so you can test reconciliation logic without a full cluster.
Revision Notes
Key Takeaways
- 1. Scaffold with Kubebuilder, regenerate with make generate/manifests after every schema change
- 2. Reconcilers are idempotent, use the cached client for reads, and report status through the subresource
- 3. Test RBAC and behavior under the real ServiceAccount — admin kubeconfigs hide failures
Interview Tips
- • Explain controller-runtime's manager/controller/cache/workqueue architecture in one breath
- • Name the exact failure you hit when markers or generated code fall behind the types
Cheat Sheet
kubebuilder init/create api → types + controller + config/. After edits: make generate && make manifests. RBAC from markers; cached reads, direct writes; envtest/make run; requeue with backoff.