Kubernetes no siempre contiene todos tus workloads.

Una plataforma puede ejecutar la mayoría de sus aplicaciones dentro de Kubernetes y seguir dependiendo de:

  • aplicaciones legacy en máquinas virtuales
  • servidores de aplicaciones que todavía no pueden migrarse
  • bases de datos alojadas fuera del cluster
  • appliances internos
  • workloads sobre bare metal
  • servicios que viven en otro entorno de infraestructura

El problema de red normalmente no es difícil. Si el cluster puede enrutar hacia una IP externa, sus workloads pueden alcanzarla.

El problema operativo aparece después:

¿Quién mantiene Kubernetes sincronizado
cuando cambian esos endpoints externos?

Ese es el problema que quise resolver con External Service Discovery Operator.

Es un operador ligero de Kubernetes que descubre workloads externos mediante DNS o direcciones IPv4 estáticas y los proyecta como recursos nativos:

DiscoveredService
        ↓
Service sin selector
        ↓
EndpointSlice
        ↓
consumidores nativos de Kubernetes

No introduce un proxy, sidecar, agente ni service mesh en el tráfico. Solo mantiene sincronizado el estado de Kubernetes con los endpoints externos descubiertos.

La idea central es sencilla: los workloads externos pueden seguir viviendo fuera del cluster y, al mismo tiempo, presentarse a los consumidores como endpoints nativos de Kubernetes.


La pregunta obvia: ¿por qué no usar ExternalName?

Kubernetes ya dispone de Services ExternalName:

apiVersion: v1
kind: Service
metadata:
  name: legacy-payments
spec:
  type: ExternalName
  externalName: payments.internal.example.com

Para muchos casos es la solución correcta. Kubernetes publica un alias DNS y la resolución permanece en el camino del consumidor.

Con ExternalName, las direcciones resueltas no se materializan como backends de un EndpointSlice.

La diferencia es arquitectónica. Un ExternalName puede resolver varios registros A, pero Kubernetes no expone esos registros como backends individuales de EndpointSlice; la resolución y el comportamiento de las conexiones quedan fuera del modelo de endpoints del Service.

Necesidad ExternalName DiscoveredService
Alias DNS de Kubernetes hacia un hostname externo No es su objetivo principal
IP externas concretas representadas en EndpointSlice No
Varios backends visibles como endpoints de Kubernetes No
Destinos mediante IP externas estáticas No
Sincronización periódica DNS → EndpointSlice No
Requiere un controlador adicional No

ExternalName suele ser la opción más sencilla y adecuada. DiscoveredService resulta útil cuando las máquinas externas deben participar en el modelo de endpoints de un Service de Kubernetes.


¿Por qué querría EndpointSlices concretos?

Supongamos que la misma aplicación Tomcat se ejecuta en tres VMs externas y un hostname estable resuelve las tres direcciones:

tomcat.internal.example.com
        ├── 10.140.0.11
        ├── 10.140.0.12
        └── 10.140.0.13

El operador materializa esas direcciones como backends individuales detrás de un Service. Un consumidor compatible con Kubernetes puede utilizar ese conjunto de endpoints al distribuir conexiones entre las réplicas.

El operador no actúa como proxy ni balancea el tráfico. Solo descubre, normaliza y publica los endpoints.

Arquitectura de External Service Discovery Operator: workloads externos descubiertos mediante DNS o IPv4 estática y proyectados como Services y EndpointSlices
External Service Discovery Operator convierte nombres DNS o direcciones estáticas externas en recursos Service y EndpointSlice nativos de Kubernetes.

El caso de uso original

El proyecto nació de un escenario híbrido: aplicaciones nuevas dentro de Kubernetes consumiendo un sistema legacy ejecutado en VMs.

La infraestructura externa podía cambiar direcciones, pero ofrecía nombres DNS estables. Mantener listas de IP manuales en manifiestos era frágil:

la infraestructura cambia
        ↓
el manifiesto queda obsoleto
        ↓
el Service apunta a endpoints antiguos
        ↓
la aplicación falla

Lo que necesitaba era una declaración estable:

apiVersion: discovery.k8sready.com/v1alpha1
kind: DiscoveredService
metadata:
  name: legacy-payments
spec:
  discovery:
    dns:
      names:
        - payments.internal.example.com
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP

El operador resuelve los nombres y mantiene un Service sin selector y un EndpointSlice con las direcciones IPv4 descubiertas.


Descubrimiento DNS

El proveedor DNS permite declarar uno o varios nombres:

spec:
  discovery:
    dns:
      names:
        - app-01.internal.example.com
        - app-02.internal.example.com
  ports:
    - name: http
      port: 80
      targetPort: 8080

El flujo es:

nombres DNS
     ↓
resolución A/AAAA
     ↓
filtrado a IPv4 utilizable
     ↓
deduplicación y orden estable
     ↓
EndpointSlice

Actualmente el operador materializa solo IPv4 y crea EndpointSlice con addressType: IPv4. Esta limitación es deliberadamente explícita.

La resolución DNS se refresca cada minuto por defecto. El intervalo es configurable para todo el controlador mediante --discovery-refresh-interval.


Un fallo no debería eliminar endpoints que funcionaban

Los fallos DNS transitorios son normales. Si una resolución falla, borrar inmediatamente endpoints válidos puede convertir un problema temporal en una caída.

Por eso el operador conserva el último estado conocido como válido:

resolución correcta
        ↓
Service + EndpointSlice actualizados
        ↓
fallo DNS temporal
        ↓
se conservan los endpoints existentes
        ↓
Ready=False / DiscoveryFailed

El estado informa del fallo, pero el Service, el EndpointSlice y el número observado de endpoints permanecen sin cambios hasta que vuelva a existir un resultado válido.


Descubrimiento estático

También pueden declararse direcciones IPv4 fijas:

apiVersion: discovery.k8sready.com/v1alpha1
kind: DiscoveredService
metadata:
  name: external-database
spec:
  discovery:
    static:
      addresses:
        - 10.140.0.21
        - 10.140.0.22
  ports:
    - name: postgres
      port: 5432
      protocol: TCP

El proveedor valida, normaliza, deduplica y ordena las direcciones. Es útil cuando las IP son estables o cuando otro sistema genera los manifiestos mediante GitOps.


¿Por qué EndpointSlice y no Endpoints?

EndpointSlice es la API moderna de Kubernetes para representar endpoints de red. Es la base utilizada por el ecosistema actual y evita construir una nueva abstracción paralela.

El operador crea recursos deterministas con el mismo nombre que el DiscoveredService. Ambos hijos incluyen referencias de propietario y el controlador restaura cualquier drift en los campos que gestiona. Si ya existe un recurso con ese nombre que no le pertenece, informa de un conflicto y no lo adopta.


Independiente del proveedor de infraestructura

El diseño separa el descubrimiento de la reconciliación:

proveedor de descubrimiento
        ↓
lista normalizada de endpoints
        ↓
reconciliación Kubernetes

La versión actual incluye proveedores DNS y estático. En el futuro podrían añadirse inventarios de cloud, service directories o APIs internas sin cambiar el modelo consumido por Kubernetes.


Lo que el operador no intenta hacer

El alcance es intencionadamente pequeño. No es:

  • una service mesh
  • un proxy de tráfico
  • un controlador de DNS
  • un sistema de health checks externos
  • un gestor de infraestructura cloud
  • un sustituto de Service Discovery específico de cada aplicación

Su responsabilidad es mantener Service y EndpointSlice sincronizados con una fuente externa de endpoints.


Cuándo ExternalName sigue siendo mejor

ExternalName suele ser preferible cuando:

  • solo necesitas un alias DNS
  • el consumidor resuelve DNS correctamente
  • no necesitas endpoints visibles en la API
  • quieres la solución más pequeña posible

DiscoveredService encaja mejor cuando:

  • un nombre representa varios backends
  • las direcciones cambian tras un hostname estable
  • los consumidores esperan EndpointSlice
  • quieres observar y reconciliar las direcciones desde Kubernetes

No se trata de reemplazar ExternalName. Se trata de cubrir el espacio donde un alias DNS no es suficiente.


Un puente útil para migraciones

Durante una migración de VMs a Kubernetes, las aplicaciones internas pueden consumir el mismo nombre de Service mientras sus backends continúan fuera del cluster. La ubicación del workload y la forma de consumo quedan desacopladas.

estado deseado estable
        ↓
descubrimiento externo
        ↓
direcciones runtime cambiantes
        ↓
endpoints nativos de Kubernetes

Arquitectura

El proveedor de descubrimiento está separado de la reconciliación. Tanto DNS como el proveedor estático producen un conjunto normalizado de endpoints; el controlador lo proyecta después en los mismos recursos nativos de Kubernetes.


Instalación

La versión 0.1.1 está disponible como chart Helm OCI:

helm upgrade --install external-service-discovery-operator \
  oci://ghcr.io/pierinho13/charts/external-service-discovery-operator \
  --version 0.1.1 \
  --namespace external-service-discovery-operator-system \
  --create-namespace

La imagen del controlador también se publica en GitHub Container Registry.


Reflexiones finales

Resolver DNS, crear un Service o crear un EndpointSlice son tareas sencillas por separado. Lo interesante es mantenerlas conectadas de forma declarativa, observable y segura ante fallos.

A veces ExternalName es exactamente lo necesario. Otras veces, configurar el hostname directamente en la aplicación es aún más simple.

Pero cuando los consumidores necesitan que workloads externos se comporten como endpoints nativos de un Service, existe un pequeño hueco entre DNS y la gestión de EndpointSlice. Este proyecto está diseñado para cubrirlo.

El proyecto es open source:

https://github.com/pierinho13/external-service-discovery-operator

La versión actual soporta descubrimiento DNS y estático, EndpointSlices IPv4, refresco DNS periódico, reconciliación determinista y conservación del último estado válido durante fallos de descubrimiento.

Si trabajas con entornos híbridos Kubernetes/VM, migraciones de sistemas legacy o Services sin selector mantenidos manualmente, me interesa especialmente conocer tu experiencia.