gw-10 step 01 — A CRD and a level-triggered reconcile loop

Goal

Define a CRD, write a controller-runtime reconciler that converges actual → desired state idempotently, and report status. This is the operator pattern in its smallest correct form.

Scaffold (kubebuilder)

mkdir gw10-operator && cd gw10-operator
kubebuilder init --domain 10xdev.io --repo github.com/10xdev/gw10
kubebuilder create api --group net --version v1 --kind EdgeRoute \
  --resource --controller

Code — the CRD type (api/v1/edgeroute_types.go)

package v1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// EdgeRouteSpec is the DESIRED state (what the user writes).
type EdgeRouteSpec struct {
	Hostname string `json:"hostname"`
	Prefix   string `json:"prefix"`
	Service  string `json:"service"` // target k8s Service name
	Port     int32  `json:"port"`
}

// EdgeRouteStatus is CONTROLLER-written outcome (the receipt).
type EdgeRouteStatus struct {
	Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type EdgeRoute struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec   EdgeRouteSpec   `json:"spec,omitempty"`
	Status EdgeRouteStatus `json:"status,omitempty"`
}

Code — the reconciler (internal/controller/edgeroute_controller.go)

package controller

import (
	"context"

	netv1 "github.com/10xdev/gw10/api/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type EdgeRouteReconciler struct {
	client.Client
	Programmer DataPlane // pushes config to the proxy (step 02 / gw-08)
}

// Reconcile is LEVEL-TRIGGERED: it derives the right state from scratch
// every call. It must be idempotent and survive being called any number
// of times in any order.
func (r *EdgeRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	var er netv1.EdgeRoute
	if err := r.Get(ctx, req.NamespacedName, &er); err != nil {
		// NotFound = deleted. Level-triggered: nothing to do here; the
		// data plane is reconciled from the full set of EdgeRoutes.
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// 1. Observe ACTUAL state: does the target Service exist / have endpoints?
	var svc corev1.Service
	svcErr := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: er.Spec.Service}, &svc)
	if errors.IsNotFound(svcErr) {
		r.setCondition(ctx, &er, "Accepted", metav1.ConditionFalse,
			"ServiceNotFound", "target Service does not exist")
		return ctrl.Result{}, nil
	}

	// 2. Converge: program the data plane for THIS route (idempotent).
	if err := r.Programmer.Program(ctx, &er); err != nil {
		r.setCondition(ctx, &er, "Programmed", metav1.ConditionFalse,
			"PushFailed", err.Error()) // the CRD analog of an xDS NACK
		return ctrl.Result{Requeue: true}, nil
	}

	// 3. Report status: only Programmed=true AFTER the push succeeded.
	r.setCondition(ctx, &er, "Accepted", metav1.ConditionTrue, "Valid", "route accepted")
	r.setCondition(ctx, &er, "Programmed", metav1.ConditionTrue, "Synced", "data plane updated")
	return ctrl.Result{}, nil
}

func (r *EdgeRouteReconciler) setCondition(ctx context.Context, er *netv1.EdgeRoute,
	t string, s metav1.ConditionStatus, reason, msg string) {
	meta.SetStatusCondition(&er.Status.Conditions, metav1.Condition{
		Type: t, Status: s, Reason: reason, Message: msg,
		ObservedGeneration: er.Generation,
	})
	_ = r.Status().Update(ctx, er)
}

// SetupWithManager wires the watch: reconcile on EdgeRoute changes AND
// on changes to the Services they reference (so endpoint churn re-triggers).
func (r *EdgeRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&netv1.EdgeRoute{}).
		Owns(&corev1.Service{}).
		Complete(r)
}

Tasks

  1. Scaffold, define the CRD, install it (make install), run the controller (make run).
  2. kubectl apply an EdgeRoute pointing at a missing Service; confirm status.conditions shows Accepted=False / ServiceNotFound. Create the Service; confirm it flips to Accepted=True / Programmed=True without you touching the EdgeRoute — that's level-triggered self-healing.
  3. Kill and restart the controller; confirm it reconciles all existing EdgeRoutes back to correct status on startup (no events needed).

Acceptance

  • A working CRD + reconciler that reports accurate status conditions.
  • Demonstrated self-healing: fixing the underlying Service flips status with no change to the CR.
  • Controller restart reconverges from scratch (proves level-triggered).

Discussion prompts

  • Why must Reconcile be idempotent, and what breaks if it isn't?
  • Why report status after the data-plane push, not before? (Status must reflect reality — the xDS ACK/NACK analogy.)
  • Why does watching the referenced Service matter? (Endpoint churn in gw-09 must re-trigger reconcile so the data plane stays current.)