Controlador vs operador en Kubernetes: qué los separa de verdad

Tarde o temprano, en cualquier equipo de plataforma alguien propone «escribir un operador» y otra persona pregunta si no bastaría con un controlador. La respuesta corta es que todo operador es un controlador, pero no todo controlador es un operador. La frontera no es técnica: está en lo que sabe el bucle.

Un controlador es un bucle que observa objetos de Kubernetes y lleva el estado real del clúster hacia el estado deseado que declaran esos objetos. Un operador es un controlador que gestiona una aplicación concreta a través de sus propios recursos personalizados y que incorpora el conocimiento operativo que, de otro modo, tendría una persona de guardia: cómo instalarla, actualizarla, hacer copias de seguridad, conmutarla ante un fallo y recuperarla.

En este artículo vemos las definiciones con precisión, una tabla comparativa, cómo es un bucle de reconciliación mínimo, los frameworks que tiene sentido usar en 2026 y, quizá lo más útil, los casos en los que no deberías construir ninguno de los dos. Si prefieres empezar por una introducción más pausada al patrón operador, tienes Operadores de Kubernetes: lo básico.

Qué es un controlador de Kubernetes

Kubernetes se apoya en una idea: tú declaras el estado que quieres y algo se encarga de que se cumpla continuamente. Ese «algo» es un controlador.

Un controlador ejecuta un bucle de reconciliación:

  1. Observa (watch) uno o varios tipos de recurso a través del API server.
  2. Cuando algo cambia, lee el estado deseado (el spec del objeto).
  3. Observa el estado real (otros objetos o el mundo exterior).
  4. Actúa para cerrar la diferencia: crea, modifica o borra cosas.
  5. Escribe lo que ha observado en el status del objeto.
  6. Vuelve a empezar, indefinidamente.

Hay dos propiedades que hacen que este modelo funcione.

Los controladores se disparan por nivel, no por flanco (level-triggered, no edge-triggered). Un controlador no trata «se ha borrado un pod» como un evento que haya que procesar exactamente una vez. Lo trata como «puede que el mundo haya cambiado, ve a mirar». Si se pierde un evento, se reinicia o recibe cinco cambios de golpe, la siguiente reconciliación sigue dando el resultado correcto porque compara el estado deseado completo con el estado real completo. Por eso la función de reconciliación tiene que ser idempotente: ejecutarla dos veces seguidas no puede hacer daño.

Cada controlador se ocupa de una parcela pequeña. El controlador de Deployments gestiona ReplicaSets. El de ReplicaSets gestiona Pods. El scheduler asigna Pods a nodos. El kubelet arranca contenedores. Ninguno hace el trabajo de otro; lo que ves al hacer kubectl apply de un Deployment es el resultado emergente de varios bucles pequeños que cooperan a través del API server.

Los controladores integrados

La mayoría de los controladores que usas a diario viven en un único binario, kube-controller-manager, que corre en el plano de control. Agrupa decenas de bucles, entre ellos:

  • Los controladores de Deployment y ReplicaSet, que despliegan y mantienen las réplicas.
  • Los de StatefulSet, DaemonSet, Job y CronJob.
  • El de EndpointSlice, que mantiene al día los backends de cada Service.
  • El de Namespace, que limpia todo cuando se borra un namespace.
  • El recolector de basura (garbage collector), que borra los objetos dependientes a través de ownerReferences.
  • El de ciclo de vida de nodos, que marca nodos no sanos y desaloja sus pods.
  • El del autoescalado horizontal (HPA), que ajusta réplicas a partir de métricas (tienes cómo se comporta ese bucle en HPA en Kubernetes: mejores prácticas).
  • Los de enlace y expansión de PersistentVolumes.

Los bucles específicos de cada nube (balanceadores, direcciones de nodo, rutas) corren aparte, en el cloud-controller-manager. Y muchos componentes de terceros también son controladores: los ingress controllers, el application controller de ArgoCD, external-dns, cluster-autoscaler o Karpenter.

A ninguno de ellos se le suele llamar operador. Son infraestructura genérica: gestionan primitivas de Kubernetes o una única preocupación externa, sin codificar cómo se opera un software concreto.

Qué es un operador de Kubernetes

El término «operador» lo acuñó CoreOS en 2016 para describir un uso concreto del patrón controlador: meter en software el conocimiento de un operador humano. La documentación oficial de Kubernetes define los operadores como «extensiones de software de Kubernetes que usan recursos personalizados para gestionar aplicaciones y sus componentes».

Un operador tiene tres ingredientes:

  1. Una Custom Resource Definition (CRD) que modela la aplicación con su propio vocabulario. No creas un StatefulSet, un Service, un ConfigMap, un PodDisruptionBudget y un CronJob para los backups: creas un objeto PostgresCluster o Kafka con campos como instances, version o backup.schedule.
  2. Un controlador que reconcilia esos recursos personalizados en los objetos de Kubernetes subyacentes y en el estado externo.
  3. Conocimiento del dominio integrado en ese controlador: las operaciones de día 2 propias de la aplicación. Promocionar una réplica cuando cae el primario. Ejecutar una migración de esquema antes de desplegar una versión nueva. Reequilibrar particiones al escalar los brokers. Hacer un backup consistente. Rotar certificados sin cortar el servicio.

El tercer ingrediente es el que importa. Un controlador que convierte un recurso WebApp en un Deployment más un Service tiene CRD y bucle de reconciliación, así que técnicamente encaja en la definición, pero no sabe nada que no sepa un chart de Helm. Un operador de PostgreSQL que detecta que el primario no responde, elige la réplica más actualizada, la promociona, redirige el Service de lectura-escritura y reincorpora el antiguo primario como réplica cuando vuelve está haciendo algo que ninguna plantilla puede hacer.

Controlador vs operador: comparativa

AspectoControladorOperador
MecanismoBucle de reconciliaciónBucle de reconciliación (un operador es un controlador)
Qué observaTipos integrados (Pods, Deployments, Services, Nodes) o cualquier tipoSus propios recursos personalizados y los tipos integrados que crea
Necesita CRDNoSí, por definición
AlcanceUna preocupación genérica (réplicas, endpoints, DNS, nodos)Una aplicación o sistema concreto (PostgreSQL, Kafka, Prometheus)
Conocimiento que codificaCómo debe comportarse una primitiva de KubernetesCómo instalar, actualizar, respaldar, recuperar y escalar un software concreto
Quién lo escribeEl proyecto Kubernetes, fabricantes de infraestructuraEl fabricante o la comunidad de la aplicación, o un equipo de plataforma
Dónde correA menudo en kube-controller-manager, o como Deployment independienteSiempre como Deployment independiente en el clúster
Ciclo de vidaNormalmente «mantén N copias de X»Día 1 (instalación) y día 2 (actualización, backup, failover, ajuste)
EjemplosDeployment, ReplicaSet, EndpointSlice, HPA, ingress-nginx, external-dnsPrometheus Operator, CloudNativePG, Strimzi, cert-manager (frontera)

En una frase: el controlador es el patrón; el operador es un uso de ese patrón para gestionar una aplicación con recursos personalizados y experiencia operativa incorporada.

La zona gris: ¿cert-manager es un operador?

La frontera es difusa y discutirla suele ser poco productivo, pero algunos ejemplos ayudan a calibrar.

cert-manager introduce CRDs (Certificate, Issuer, ClusterIssuer, CertificateRequest) y las reconcilia. Codifica conocimiento de dominio real: el protocolo ACME, los retos HTTP-01 y DNS-01, las ventanas de renovación, la rotación de claves. Aun así, su propia documentación lo llama controlador, y no gestiona una aplicación en el sentido de «instálame y opérame esta base de datos», sino una preocupación transversal: los certificados. Casi todo el mundo lo llama operador igualmente, y no pasa nada.

ArgoCD tiene CRDs (Application, ApplicationSet, AppProject) y un application controller. Es un controlador de entrega GitOps, no un operador de una carga concreta. Existe, eso sí, un ArgoCD Operator que instala y actualiza el propio ArgoCD, lo que muestra cómo se usa la palabra en la práctica: «lo que te opera X». (Sobre ArgoCD en sí, la guía completa de ArgoCD.)

Una CRD WebApp propia que se expande en Deployment, Service e Ingress es un controlador con CRD. Llamarlo operador no es incorrecto, pero sería un operador de nivel 1, que es justo lo que explica la siguiente sección.

Niveles de capacidad de un operador

El Operator Framework define un modelo de madurez de cinco niveles, que es la forma más útil de distinguir un operador «de verdad» de un controlador que solo rellena plantillas:

NivelNombreQué hace el operador
1Basic InstallAprovisiona la aplicación a partir del recurso personalizado e informa del estado
2Seamless UpgradesActualiza la aplicación (y a sí mismo) entre versiones, incluidos pasos de datos o esquema
3Full LifecycleBackup y restauración, recuperación ante fallos, escalado, reconfiguración
4Deep InsightsExpone métricas, alertas y logs de la aplicación gestionada y refleja su salud en el status
5Auto PilotAutoescalado, autoajuste, detección de anomalías y autorreparación sin intervención

Un operador de nivel 1 hace más o menos lo que hace helm install. La razón de ser de los operadores son los niveles 2 y 3. Si tu diseño se queda en el nivel 1, lo que probablemente necesitas es un chart de Helm.

Un bucle de reconciliación mínimo en Go

La forma más habitual de escribir un controlador u operador en Go es controller-runtime, la librería que hay debajo de Kubebuilder y de Operator SDK. Este es el esqueleto de una función de reconciliación para un recurso personalizado hipotético Website que es dueño de un Deployment:

func (r *WebsiteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // 1. Leer el estado deseado.
    var site webv1.Website
    if err := r.Get(ctx, req.NamespacedName, &site); err != nil {
        // Borrado entre el evento y ahora: no hay nada que hacer.
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. Construir el objeto que queremos que exista.
    deploy := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{Name: site.Name, Namespace: site.Namespace},
    }

    // 3. Crearlo o actualizarlo de forma idempotente, con el Website como dueño.
    op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deploy, func() error {
        deploy.Spec.Replicas = ptr.To(site.Spec.Replicas)
        deploy.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"app": site.Name}}
        deploy.Spec.Template.Labels = map[string]string{"app": site.Name}
        deploy.Spec.Template.Spec.Containers = []corev1.Container{{
            Name:  "web",
            Image: site.Spec.Image,
        }}
        return controllerutil.SetControllerReference(&site, deploy, r.Scheme)
    })
    if err != nil {
        return ctrl.Result{}, err // controller-runtime reencola con backoff
    }
    log.Info("deployment reconciliado", "operacion", op)

    // 4. Informar del estado observado.
    site.Status.ReadyReplicas = deploy.Status.ReadyReplicas
    site.Status.ObservedGeneration = site.Generation
    if err := r.Status().Update(ctx, &site); err != nil {
        return ctrl.Result{}, err
    }
    return ctrl.Result{}, nil
}

func (r *WebsiteReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&webv1.Website{}).      // reconciliar cuando cambia un Website
        Owns(&appsv1.Deployment{}). // ...o un Deployment que le pertenece
        Complete(r)
}

Fíjate en lo que la función no hace: nunca pregunta «¿qué ha cambiado?». Lee el estado deseado completo, calcula el objeto objetivo completo y deja que CreateOrUpdate lo materialice. La owner reference hace que al borrar el Website el recolector de basura se lleve el Deployment, y Owns() hace que una edición manual del Deployment dispare una reconciliación que lo devuelve a su sitio.

Esto es un controlador con CRD. Para convertirlo en un operador digno de ese nombre, la función tendría que crecer con lógica de dominio: detectar que spec.version ha pasado de 3 a 4 y lanzar antes un Job de migración, retener el despliegue hasta que termine, poner una condición Degraded si falla, hacer un backup antes de cualquier cambio destructivo. Es en esa lógica, y no en el andamiaje, donde se van los meses de trabajo.

Hay piezas que acaba necesitando cualquier operador no trivial:

  • Finalizers, para que el controlador pueda limpiar recursos externos (un bucket, un registro DNS, un usuario de base de datos) antes de que desaparezca el recurso personalizado.
  • Condiciones de status con la forma estándar type/status/reason/message y observedGeneration, para que usuarios y herramientas sepan si el último spec ya se ha procesado.
  • Reencolado diferido (ctrl.Result{RequeueAfter: ...}) para lo que hay que consultar periódicamente, como una API externa que no emite eventos de Kubernetes.
  • Elección de líder, para que solo actúe una réplica del operador a la vez.
  • RBAC acotado y watches limitados a namespaces cuando sea posible, porque un operador es un componente con muchos privilegios.

Frameworks para escribir controladores y operadores en 2026

Prácticamente nunca se escribe a mano la maquinaria de watches, caché y colas de trabajo. Estas son las opciones principales, todas con mantenimiento activo a septiembre de 2026.

controller-runtime y Kubebuilder (Go)

controller-runtime (kubernetes-sigs, v0.25 en el momento de escribir esto) es la librería: manager, cliente con caché, informers, colas de trabajo, elección de líder y webhooks. Kubebuilder (v4.16) es el generador que va encima: crea la estructura del proyecto, los tipos de la CRD con marcadores, los manifiestos RBAC, los webhooks y un entorno de pruebas basado en envtest. Si programas en Go y no tienes restricciones especiales, es la opción por defecto y la que usan la mayoría de los operadores del ecosistema CNCF.

Operator SDK (Go, Helm, Ansible)

Operator SDK (Operator Framework, v1.42) usa los plugins de Kubebuilder para los proyectos Go, así que la experiencia en Go es prácticamente la misma. Lo que añade es la integración con Operator Lifecycle Manager (OLM) para empaquetar y distribuir operadores como bundles, un scorecard para validar buenas prácticas, y dos variantes sin Go: operadores basados en Helm, que reconcilian un recurso personalizado renderizando un chart, y operadores basados en Ansible, que ejecutan playbooks. Tiene sentido si apuntas a OpenShift u OperatorHub, o si tu equipo prefiere escribir Ansible antes que Go.

Java Operator SDK (Java)

Java Operator SDK (v5.6, publicada en septiembre de 2026) lleva el mismo modelo a la JVM, con una extensión de Quarkus que genera imágenes nativas. Las últimas versiones han añadido informers compartidos entre controladores y un registrador de eventos integrado. Encaja de forma natural en equipos cuya lógica de dominio y librerías cliente ya viven en Java; los operadores de Strimzi y de Keycloak son ejemplos conocidos del patrón en la JVM.

kopf (Python)

kopf (Kubernetes Operator Pythonic Framework, 1.44) permite escribir los handlers como funciones Python decoradas, por ejemplo @kopf.on.create('websites'). Es excelente para operadores «pegamento», prototipos y equipos de SRE que viven en Python. Es menos habitual en operadores grandes y sensibles al rendimiento, pero mucha automatización interna corre sin problema sobre él.

Metacontroller (cualquier lenguaje, vía webhooks)

Metacontroller (v4.17) le da la vuelta al modelo: se encarga él de observar y reconciliar, y llama a tu webhook con el objeto padre y sus hijos actuales; tu webhook devuelve los hijos deseados. Tu «controlador» es una función HTTP sin estado en el lenguaje que quieras. Es ideal para recursos compuestos sencillos y decoradores, y poco adecuado para operadores con máquinas de estados complejas de varios pasos.

Alternativas declarativas: kro y Crossplane

Si lo que quieres es «un recurso personalizado que se expanda en un conjunto de recursos», puede que no necesites escribir ningún controlador. kro (Kube Resource Orchestrator, subproyecto de SIG Cloud Provider) permite definir un ResourceGraphDefinition que genera una CRD nueva y su reconciliación a partir de un grafo declarativo; ojo, sus APIs siguen por debajo de la 1.0 y el proyecto no se declara listo para producción. Las composiciones de Crossplane hacen algo parecido, con foco en infraestructura cloud. Ambos te dan un «operador» de nivel 1 sin escribir Go.

Cuándo escribir un controlador, un operador o ninguno

Esta es la decisión que importa, y la respuesta por defecto debería ser «ninguno».

Usa un chart de Helm (o Kustomize) cuando la aplicación no tiene estado o su estado se gestiona fuera, la dificultad está en la instalación y actualizar consiste en desplegar una imagen nueva. Plantillas, helm upgrade y una estrategia de despliegue razonable cubren la gran mayoría de servicios internos. Los hooks de Helm resuelven la migración puntual previa a una actualización (lo tienes en la guía de Helm hooks).

Usa un operador existente cuando vas a ejecutar software con estado conocido. PostgreSQL, Kafka, Redis, Elasticsearch, RabbitMQ y Prometheus tienen operadores maduros cuyos autores ya se han topado con los modos de fallo que tú aún no conoces. Escribir tu propio operador de PostgreSQL para no aprender CloudNativePG es un error de varios años.

Escribe un controlador simple cuando necesitas mantener algo sincronizado de forma continua y ninguna herramienta existente lo hace: etiquetar namespaces a partir de una CMDB externa, crear registros DNS o permisos IAM a partir de anotaciones, aplicar una política de limpieza. Puede que ni siquiera haga falta una CRD: el controlador puede observar recursos integrados y actuar sobre anotaciones.

Escribe un operador cuando se cumplen todas estas condiciones:

  • El software tiene operaciones de día 2 con estado que hoy hacen personas siguiendo un runbook: failover, resharding, actualizaciones ordenadas, backup y restauración.
  • Esas operaciones son lo bastante frecuentes o arriesgadas como para que automatizarlas compense la inversión.
  • No existe un operador maduro, o los que hay están abandonados o no se pueden adaptar.
  • Tienes un equipo para mantenerlo a largo plazo. Un operador es software de producción con su propio ciclo de versiones, versionado de API, webhooks de conversión y CVEs.

Operadores reales que merece la pena estudiar

Leer buenos operadores es la forma más rápida de entender la distancia entre un controlador y un operador.

Prometheus Operator gestiona Prometheus y Alertmanager mediante CRDs como Prometheus, Alertmanager, ServiceMonitor, PodMonitor y PrometheusRule. Su gran acierto es convertir la configuración de scraping en objetos de Kubernetes que cada equipo mantiene junto a su aplicación; el operador regenera la configuración de Prometheus y la recarga. Si has usado kube-prometheus-stack, lo has usado. La diferencia entre sus dos CRDs de descubrimiento la explico en ServiceMonitor vs PodMonitor.

CloudNativePG ejecuta PostgreSQL con una CRD Cluster y gestiona directamente los pods primario y réplicas en lugar de usar un StatefulSet, precisamente porque necesita un control más fino sobre qué instancia es primaria, cómo ocurre el failover y cómo se manejan los volúmenes. Cubre switchover, failover automático, backup continuo y recuperación a un punto en el tiempo contra almacenamiento de objetos, y actualizaciones progresivas de versión menor. Es un operador de nivel 3 o superior de manual.

Strimzi ejecuta Apache Kafka con un cluster operator más los operadores de topics y de usuarios, y modela brokers, node pools, topics, usuarios y conectores como recursos personalizados. Hace los reinicios progresivos en el orden que Kafka necesita, gestiona los certificados entre brokers y clientes, y escala con reasignación de particiones a través de Cruise Control.

cert-manager, que ya hemos visto, es el ejemplo canónico de proyecto con forma de controlador y conocimiento profundo de un protocolo, más que de una carga de trabajo.

El Logging Operator es otro buen ejemplo de diseño basado en CRDs: cada equipo declara sus flujos y salidas de logs por namespace, y el operador gestiona los despliegues de Fluent Bit y Fluentd que hay detrás.

Antipatrones

Las formas habituales en que un operador sale mal:

Un operador que solo ejecuta Helm. Un controlador cuya reconciliación renderiza un chart y lo aplica añade una CRD, un Deployment, RBAC y un nuevo modo de fallo, y no te da nada que no te dé helm upgrade en un pipeline GitOps. Si no hay lógica de día 2, sobra.

Pensar por eventos. Escribir la reconciliación como «al crear haz A, al actualizar haz B» acaba en deriva la primera vez que se pierde un evento. Calcula siempre a partir del estado deseado completo.

Efectos secundarios no idempotentes. Enviar un correo, cobrar o crear un recurso cloud sin comprobar antes si ya existe acabará pasando dos veces. Registra lo que has hecho en el status o usa nombres deterministas.

Sin status. Un operador que nunca escribe condiciones obliga a los usuarios a hacer kubectl logs del pod del operador para adivinar qué ha pasado. El status es la interfaz de usuario del operador.

RBAC de cluster-admin. Los operadores son objetivos muy valiosos. Pide solo los verbos y recursos que usas y, si el software lo permite, instálalo limitado a namespaces.

Bucles de reencolado en caliente. Devolver un error o Requeue: true en cada reconciliación por una condición que no va a cambiar en minutos machaca el API server. Usa RequeueAfter con un intervalo razonable.

Finalizers que bloquean. Un finalizer que nunca puede completarse (porque el sistema externo ya no existe) deja objetos atascados en Terminating para siempre. Prevé siempre un timeout o una vía de escape.

Versionar la CRD a posteriori. En cuanto hay objetos guardados como v1alpha1, cambiar el esquema implica webhooks de conversión y migración del almacenamiento. Diseña la API con el mismo cuidado que una API REST pública, porque lo es.

Preguntas frecuentes

¿Cuál es la diferencia entre un controlador de Kubernetes y un operador?

Un controlador es cualquier bucle que observa recursos de Kubernetes y reconcilia el estado real con el deseado; los controladores de Deployment y ReplicaSet son ejemplos. Un operador es un controlador que gestiona una aplicación concreta mediante sus propias Custom Resource Definitions e incorpora el conocimiento operativo necesario para ejecutarla: actualizaciones, backups, failover y recuperación. Todo operador es un controlador, pero solo se suele llamar operador a los controladores que combinan recursos personalizados con lógica de día 2 específica de una aplicación.

¿Todo operador es un controlador?

Sí. Un operador se implementa como uno o varios controladores que ejecutan un bucle de reconciliación sobre recursos personalizados. La diferencia no está en el mecanismo sino en el alcance y el conocimiento: los operadores gestionan el ciclo de vida completo de una aplicación, mientras que los controladores genéricos gestionan primitivas de Kubernetes o una única preocupación de infraestructura.

¿Un controlador necesita una CRD?

No. Muchos controladores solo observan recursos integrados: los de Deployment, EndpointSlice o el recolector de basura de kube-controller-manager, o un controlador externo que reacciona a anotaciones en los Services. La CRD solo es necesaria cuando hace falta un tipo de recurso nuevo para expresar el estado deseado, que es siempre el caso de los operadores.

¿Uso un chart de Helm o un operador?

Usa un chart de Helm cuando lo difícil es la instalación y la configuración, y las actualizaciones son simples despliegues, que es lo que ocurre con la mayoría de servicios sin estado. Usa un operador cuando el software necesita operaciones de día 2 automatizadas como failover, actualizaciones ordenadas con migración de datos, o backup y restauración. Muchos operadores se instalan a su vez con Helm, así que son herramientas complementarias, no excluyentes.

¿Qué framework uso para escribir un operador de Kubernetes?

En Go, Kubebuilder sobre controller-runtime; elige Operator SDK si necesitas empaquetado OLM para OpenShift u OperatorHub, o quieres operadores basados en Helm o Ansible. En Java, Java Operator SDK, y en Python, kopf. Para recursos compuestos sencillos en cualquier lenguaje, Metacontroller o una herramienta declarativa como kro evitan escribir un controlador completo.

¿Cuáles son los niveles de capacidad del Operator Framework?

El Operator Framework define cinco niveles de madurez: Basic Install, Seamless Upgrades, Full Lifecycle (backup, recuperación, escalado), Deep Insights (métricas, alertas, estado de salud) y Auto Pilot (autoescalado, autoajuste, autorreparación). Un operador que solo llega al nivel 1 hace más o menos lo mismo que un chart de Helm; el valor de un operador aparece a partir del nivel 2.

¿Dónde se ejecutan los controladores integrados de Kubernetes?

La mayoría se ejecutan dentro del proceso kube-controller-manager en el plano de control, entre ellos los de Deployment, ReplicaSet, StatefulSet, Job, Namespace, EndpointSlice, ciclo de vida de nodos y autoescalado horizontal. Los controladores específicos de cada proveedor cloud corren en el cloud-controller-manager, y el scheduler y el kubelet son componentes aparte que siguen el mismo modelo de observar y reconciliar.

¿cert-manager es un controlador o un operador?

Está en la zona gris. cert-manager define CRDs como Certificate e Issuer y codifica un conocimiento profundo de ACME y de la renovación de certificados, así que encaja en la definición de operador, pero gestiona una preocupación transversal en lugar de instalar y operar una aplicación, y su propia documentación lo describe como controlador. Se usan habitualmente las dos etiquetas.

Conclusión

El debate entre controlador y operador tiene menos que ver con definiciones que con intención. Un controlador es la pieza fundamental de Kubernetes: un bucle idempotente, disparado por nivel, que mantiene una parte del mundo alineada con un estado declarado. Un operador es lo que obtienes cuando apuntas esa pieza a una única aplicación, le das un vocabulario a través de recursos personalizados y le enseñas todo lo que saben tus ingenieros de guardia sobre cómo operarla.

Eso simplifica mucho la decisión práctica. Helm para instalar; un operador maduro existente para sistemas con estado conocidos; un controlador pequeño cuando necesitas una sincronización continua que nada más te da; y un operador completo solo cuando hay conocimiento de día 2 real que codificar y un equipo dispuesto a mantenerlo durante años. Si estás a punto de construir un operador de nivel 1, probablemente estés a punto de construir un chart de Helm con pasos extra.