Escribiendo un Reconciliador: ctrl.Request y Bucle de Reconciliación
El reconciliador es el corazón de un operador: una función que Kubernetes llama cada vez que un objeto observado necesita atención.
Busca en todas las páginas de la documentación
El reconciliador es el corazón de un operador: una función que Kubernetes llama cada vez que un objeto observado necesita atención.
ctrl.Request te dice qué objeto obtener; tu código impulsa el clúster hacia la especificación y retorna cuándo volver a ejecutar.
Reconcile(ctx, req) debe ser idempotente y seguro para ejecutar muchas veces para el mismo objeto.
Obtén el CR más reciente con r.Get, compara spec con los recursos hijos, crea o aplica parches a las brechas, luego actualiza status.
Retorna ctrl.Result{} en caso de éxito, ctrl.Result{RequeueAfter: duration} para esperar, o un error para activar el backoff exponencial.
Trata NotFound como éxito cuando el objeto fue eliminado.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var gb webappv1.Guestbook
if err := r.Get(ctx, req.NamespacedName, &gb); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if err := r.ensureFrontend(ctx, &gb); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}Cuándo usar esto:
package controller
import (
"context"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
webappv1 "example.com/guestbook-operator/api/v1"
)
const guestbookFinalizer = "webapp.example.com/finalizer"
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var gb webappv1.Guestbook
if err := r.Get(ctx, req.NamespacedName, &gb); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if gb.DeletionTimestamp.IsZero() {
if !controllerutil.ContainsFinalizer(&gb, guestbookFinalizer) {
controllerutil.AddFinalizer(&gb, guestbookFinalizer)
if err := r.Update(ctx, &gb); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
} else {
if err := r.teardown(ctx, &gb); err != nil {
return ctrl.Result{}, err
}
controllerutil.RemoveFinalizer(&gb, guestbookFinalizer)
if err := r.Update(ctx, &gb); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
dep := &appsv1.Deployment{}
depName := types.NamespacedName{Name: gb.Name + "-frontend", Namespace: gb.Namespace}
err := r.Get(ctx, depName, dep)
if apierrors.IsNotFound(err) {
dep = r.desiredDeployment(&gb)
if err := controllerutil.SetControllerReference(&gb, dep, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Create(ctx, dep); err != nil {
return ctrl.Result{}, err
}
logger.Info("created Deployment", "name", dep.Name)
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
}
if err != nil {
return ctrl.Result{}, err
}
gb.Status.Ready = dep.Status.ReadyReplicas >= gb.Spec.FrontendSize
if err := r.Status().Update(ctx, &gb); err != nil {
return ctrl.Result{}, err
}
if !gb.Status.Ready {
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
return ctrl.Result{}, nil
}
func (r *GuestbookReconciler) desiredDeployment(gb *webappv1.Guestbook) *appsv1.Deployment {
replicas := gb.Spec.FrontendSize
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: gb.Name + "-frontend", Namespace: gb.Namespace},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": gb.Name}},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": gb.Name}},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "frontend",
Image: "nginx:1.27",
}},
},
},
},
}
}
func (r *GuestbookReconciler) teardown(ctx context.Context, gb *webappv1.Guestbook) error {
return nil // delete external DB rows, revoke IAM, etc.
}Lo que esto demuestra:
RequeueAfter mientras se espera la preparación del Deploymentctrl.Request{NamespacedName}; no pasa el cuerpo del objeto.Get porque las cachés y otros controladores pueden haber modificado los objetos en tránsito.error no nulo vuelve a encolar con backoff exponencial a menos que uses RequeueAfter con un error nulo.SetupWithManager conecta For(&Guestbook{}) para que los cambios de especificación/estado encolen el padre.| Retorno | Efecto |
|---|---|
ctrl.Result{}, nil | Éxito; volver a encolar solo en futuros eventos observados |
ctrl.Result{Requeue: true}, nil | Reintento inmediato |
ctrl.Result{RequeueAfter: d}, nil | Reintento retrasado sin métrica de error |
ctrl.Result{}, err | Error registrado; backoff para reintento |
// Parchea las condiciones de estado sin sobrescribir otros campos
meta.SetStatusCondition(&gb.Status.Conditions, metav1.Condition{
Type: "Ready",
Status: metav1.ConditionTrue,
Reason: "DeploymentReady",
Message: "réplicas de frontend listas",
})
// Agrega múltiples errores de las garantías de hijos
return ctrl.Result{}, errors.Join(errDeploy, errSvc)spec del CR únicamente mediante webhooks de valores predeterminados o ediciones del usuario.client.IgnoreNotFound(err) en Get.Update en lugar de Status().Update - fallos de RBAC o conflictos de especificación. Solución: usar el cliente del subrecurso de estado.RequeueAfter con límites razonables y mostrar condiciones Degraded.SetControllerReference antes de Create.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| controller-runtime Reconciler | Operadores de kubebuilder tipados | Solo necesitas una única devolución de llamada de informer |
| Cola de trabajo cruda + manejadores de informer | Control granular de la cola | Quieres ayudantes de estado y el patrón de constructor |
| Anotaciones de Kopf / Java para reconciliación | Piles no-Go | Necesitas controller-gen y envtest |
| Job por evento de CR | Transformaciones de lotes raras | Se requiere convergencia continua |
NamespacedName con campos Namespace y Name.
Obtén el objeto completo tú mismo con r.Get.
Usa la aplicación del lado del servidor o parches de fusión estratégicos cuando solo poseas una parte de un objeto compartido.
Para Deployments poseídos que controlas completamente, compara la especificación y usa Update o Patch de forma idempotente.
Devuelve error para fallos inesperados de la API que merezcan una alerta.
Usa RequeueAfter para esperar lo esperado (el Deployment aún no está listo).
Registra Owns(&appsv1.Deployment{}) en SetupWithManager para que los cambios de Deployment encolen el Guestbook propietario.
Cada pasada lee la especificación actual y el estado completo del clúster, luego converge.
No asume que viste cada evento individual que causó la deriva.
Mantén la reconciliación rápida; delega tareas largas a Jobs o goroutines con un seguimiento cuidadoso del estado.
Bloques de reconciliación largos que bloquean hilos de trabajo y retrasan otros objetos.
Usa envtest o un cliente falso, llama a Reconcile con un ctrl.Request construido, y afirma los objetos hijos y el estado.
Pasa ctx a todas las llamadas al cliente.
La cancelación del gestor al apagar debería detener las llamadas a la API salientes de inmediato.
Usa predicados para filtrar actualizaciones de estado ruidosas, no para omitir pasadas necesarias impulsadas por el estado en cambios de especificación.
El paralelismo predeterminado suele ser suficiente.
Aumenta MaxConcurrentReconciles solo cuando el perfilado muestre un backlog en la cola y los límites de la API lo permitan.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (GC por defecto Green Tea, go fix modernizers - verifica el parche en la compilación), chi (última - verifica en la compilación), gin (última - verifica en la compilación), echo (última - verifica en la compilación), google.golang.org/grpc (última - verifica en la compilación), sigs.k8s.io/controller-runtime (última - verifica en la compilación), kubebuilder (última - verifica en la compilación), tinygo (última - verifica los objetivos de la placa en la compilación), wazero (última - verifica en la compilación), y golangci-lint (última - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026