Llegamos al momento que el curso lleva prometiendo desde el módulo 1: publicar Rutas Norte en internet. Tenemos cinco componentes desplegados, todos con Services ClusterIP que solo existen dentro del clúster, y ningún cliente puede comprar un billete. En 04-02 vimos que LoadBalancer resolvería el problema al precio de un balanceador y una IP pública por servicio, sin enrutado por dominio ni por ruta. Esta lección presenta la respuesta correcta: un único punto de entrada HTTP que reparte el tráfico según el dominio y la ruta pedidos. Al terminar, www.rutasnorte.example servirá la tienda y api.rutasnorte.example servirá la API, ambos desde el mismo balanceador, y sabrás distinguir con claridad entre el recurso Ingress —una regla que no hace nada por sí sola— y el controlador de Ingress, el programa que la convierte en configuración real.

Contenido

  1. El problema: varios servicios HTTP, un solo punto de entrada
  2. Recurso frente a controlador: la distinción esencial
  3. Panorama de controladores
  4. IngressClass y la anotación heredada
  5. Anatomía de un Ingress
  6. pathType: los tres valores y los casos que confunden
  7. Caso práctico: publicar la tienda y la API
  8. Enrutado por host frente a enrutado por ruta y reescritura
  9. Anotaciones del día a día
  10. Depuración
  11. Gateway API: la sucesora

  1. El problema: varios servicios HTTP, un solo punto de entrada

Rutas Norte necesita publicar, como mínimo:

Destino público Componente Notas
www.rutasnorte.example tienda-web Tráfico masivo en puentes
api.rutasnorte.example api-reservas Consumida por la SPA y por agencias
www.rutasnorte.example/api api-reservas Alternativa sin dominio aparte
admin.rutasnorte.example Panel interno Solo desde la red de oficina

Con type: LoadBalancer serían cuatro balanceadores, cuatro IPs públicas y cuatro certificados TLS gestionados por separado, multiplicado por los tres entornos. Y aun así faltaría lo esencial: un balanceador L4 no entiende HTTP. No puede leer la cabecera Host para decidir a qué servicio va la petición, ni mirar la ruta, ni reescribirla, ni añadir cabeceras. El Ingress traslada la decisión a capa 7:

flowchart TB
    C1["Cliente<br/>www.rutasnorte.example"] --> LB
    C2["Cliente<br/>api.rutasnorte.example"] --> LB
    LB["UN Service LoadBalancer<br/>IP publica unica"] --> IC["Controlador de Ingress<br/>(pods nginx en el cluster)"]
    IC -->|"Host: www.rutasnorte.example"| S1["Service tienda-web<br/>ClusterIP"]
    IC -->|"Host: api.rutasnorte.example"| S2["Service api-reservas<br/>ClusterIP"]
    IC -->|"/admin"| S3["Service panel-admin<br/>ClusterIP"]
    S1 --> P1["Pods tienda-web"]
    S2 --> P2["Pods api-reservas"]

Un balanceador, una IP, un sitio donde terminar el TLS (04-05), y todos los servicios internos siguen siendo ClusterIP.

  1. Recurso frente a controlador: la distinción esencial

Esta es la confusión número uno de la lección y hay que fijarla antes de escribir nada.

Recurso Ingress Controlador de Ingress
Qué es Un objeto de la API, networking.k8s.io/v1 Un programa que corre en pods del clúster
Qué hace Nada. Declara una intención Observa los Ingress y enruta el tráfico real
Quién lo crea Tú, con kubectl apply El administrador, una vez por clúster
Analogía La señal de tráfico dibujada en un plano El asfalto, los carriles y el guardia
Si falta el otro Se crea sin error y no pasa nada No sabe adónde enviar nada

El síntoma de crear un Ingress sin controlador engaña: kubectl apply responde created, kubectl get ingress lo lista y el dominio no responde. La pista está en la columna ADDRESS, vacía para siempre.

NAME          CLASS   HOSTS                          ADDRESS   PORTS   AGE
rutas-norte   nginx   www.rutasnorte.example,api...             80      12m

Un controlador es, por dentro, un bucle idéntico al del módulo 1: observa la API, lee los Ingress, Services y EndpointSlices, genera un fichero de configuración (un nginx.conf, en ingress-nginx) y recarga su proxy, usando una ServiceAccount con permisos (03-06).

  1. Panorama de controladores

Controlador Base Puntos fuertes A tener en cuenta
ingress-nginx NGINX El más extendido; mantenido por el proyecto Kubernetes; enorme catálogo de anotaciones Recarga la configuración al cambiar; muchas funciones viven en anotaciones no estandarizadas
Traefik Go, propio Configuración dinámica sin recargas; integración nativa con ACME; buen panel Sus funciones avanzadas usan CRDs propios (IngressRoute)
HAProxy Ingress HAProxy Rendimiento y estabilidad en L4/L7; excelente para carga alta Comunidad más pequeña
AWS ALB / GKE / AGIC Balanceador de la nube El tráfico ni siquiera entra en un pod; se integra con WAF y certificados gestionados Atado al proveedor; menos control fino
Istio Gateway / Cilium Malla / eBPF mTLS, políticas L7, telemetría profunda Complejidad notable; suele venir con una malla completa

Cuidado con un detalle histórico: existen dos controladores basados en NGINX. ingress-nginx es el del proyecto Kubernetes; "NGINX Ingress Controller" es el de F5/NGINX Inc. Sus anotaciones no son compatibles, y muchas horas se han perdido copiando la del controlador equivocado. Aquí usamos ingress-nginx, el del addon de minikube.

minikube -p rutas-norte addons enable ingress
kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-9k2lm        0/1     Completed   0          70s
ingress-nginx-admission-patch-x4dpq         0/1     Completed   0          70s
ingress-nginx-controller-7d4b8f6c8d-2vq7z   1/1     Running     0          70s

Los dos Completed son Jobs (06-03) que instalan el webhook de validación: gracias a él, un Ingress con sintaxis inválida se rechaza en el apply en lugar de romper la configuración del proxy.

  1. IngressClass y la anotación heredada

Un clúster puede tener varios controladores: uno público para tienda-web y api-reservas, otro interno para el panel de administración. IngressClass dice qué controlador atiende a qué Ingress.

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"   # atiende los Ingress sin clase
spec:
  controller: k8s.io/ingress-nginx

En el Ingress se referencia con el campo spec.ingressClassName: nginx. Antes de Kubernetes 1.18 esto se hacía con la anotación kubernetes.io/ingress.class: "nginx", que sigue funcionando en muchos controladores por compatibilidad pero está desaconsejada y desaparecerá. Verás muchísimos ejemplos en internet con ella: son antiguos. Usa ingressClassName.

Si no indicas clase y ninguna está marcada como predeterminada, ningún controlador recoge el Ingress: ADDRESS vacío y silencio.

  1. Anatomía de un Ingress

apiVersion: networking.k8s.io/v1     # OJO: v1, no las obsoletas extensions/v1beta1
kind: Ingress
metadata:
  name: rutas-norte
  namespace: rutas-norte-pro         # debe ser el MISMO que el de los Services
spec:
  ingressClassName: nginx            # qué controlador lo atiende

  defaultBackend:                    # opcional: a dónde va lo que no casa ninguna regla
    service:
      name: tienda-web
      port:
        number: 80

  rules:
    - host: www.rutasnorte.example   # opcional: sin host, la regla casa cualquier dominio
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: tienda-web     # nombre de un Service del MISMO namespace
                port:
                  number: 80         # o name: http, si el puerto tiene nombre

Restricciones que provocan la mayoría de los fallos:

  • El Ingress y los Services deben estar en el mismo namespace. Un Ingress de rutas-norte-pro no puede apuntar a un Service de rutas-norte-dev: es diseño de seguridad, no limitación técnica.
  • El port puede ser number o name, pero no ambos. Referenciarlo por nombre sobrevive a un cambio de número.
  • host acepta un comodín en el primer nivel (*.rutasnorte.example), que casa api.rutasnorte.example pero no www.api.rutasnorte.example ni el dominio desnudo. Las reglas sin host casan cualquier dominio: útil para pruebas por IP, peligroso en producción.

defaultBackend es el destino de lo que no casa nada: sin él, el controlador devuelve su 404 genérico; con él, un cliente que escriba mal el subdominio acaba en la tienda.

  1. pathType: los tres valores y los casos que confunden

pathType es obligatorio desde networking.k8s.io/v1 y su semántica sorprende a casi todo el mundo.

Valor Cómo compara Recomendación
Exact Coincidencia literal exacta, sensible a mayúsculas Rutas concretas, sin descendientes
Prefix Por segmentos de ruta separados por /, no por caracteres El valor por defecto de facto
ImplementationSpecific Lo decide el controlador; en ingress-nginx habilita expresiones regulares Solo cuando necesites regex, y sabiendo que no es portable

La clave de Prefix está en las tres palabras "por segmentos de ruta". /api no es un prefijo de caracteres: casa /api y /api/loquesea, pero no casa /apificado.

Regla Petición ¿Casa? Por qué
/api Prefix /api Exacto
/api Prefix /api/ Segmento completo
/api/ Prefix /api La barra final se ignora al comparar
/api Prefix /api/reservas/33 Descendiente por segmentos
/api Prefix /apificado No apificado es otro segmento distinto
/api Exact /api
/api Exact /api/ No La barra final la hace distinta
/api Exact /api/reservas No No hay descendientes
/API Exact /api No Distingue mayúsculas

Regla de desempate cuando varias casan: gana la ruta más larga, sin importar el pathType.

paths:
  - path: /              # casa todo
    pathType: Prefix
    backend: { service: { name: tienda-web,   port: { number: 80 } } }
  - path: /api           # mas larga: gana para /api/*
    pathType: Prefix
    backend: { service: { name: api-reservas, port: { number: 3000 } } }

Una petición a /api/reservas casa las dos reglas, y va a api-reservas porque /api es más larga que /. Este orden por longitud, y no por posición en el YAML, evita el error de "he puesto la regla más específica arriba y no funciona".

  1. Caso práctico: publicar la tienda y la API

Un solo Ingress publica los dos dominios de Rutas Norte.

# k8s/entornos/pro/ingress-rutas-norte.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rutas-norte
  namespace: rutas-norte-pro
  labels:
    app.kubernetes.io/part-of: rutas-norte
    entorno: pro
  annotations:
    # Tamaño de cuerpo: los justificantes en PDF que sube atencion al cliente
    nginx.ingress.kubernetes.io/proxy-body-size: "8m"
    # La pasarela de pagos puede tardar; damos margen
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  rules:
    - host: www.rutasnorte.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: tienda-web
                port:
                  name: http           # referencia por nombre de puerto
    - host: api.rutasnorte.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-reservas
                port:
                  name: http

Fíjate en lo que no hay que hacer: no se cambian los Services a NodePort ni a LoadBalancer. Siguen siendo ClusterIP, porque quien les habla es el controlador, que está dentro del clúster.

Aplicación y comprobación:

kubectl apply -f k8s/entornos/pro/ingress-rutas-norte.yaml
kubectl get ingress -n rutas-norte-pro

# Los dominios .example no existen en el DNS publico: se resuelven a mano
echo "$(minikube -p rutas-norte ip) www.rutasnorte.example api.rutasnorte.example" \
  | sudo tee -a /etc/hosts

curl -s -o /dev/null -w 'tienda: %{http_code}\n' http://www.rutasnorte.example/
curl -s -w '\n' http://api.rutasnorte.example/salud
NAME          CLASS   HOSTS                                          ADDRESS        PORTS   AGE
rutas-norte   nginx   www.rutasnorte.example,api.rutasnorte.example   192.168.49.2   80      40s

tienda: 200
{"estado":"ok","version":"2.4.1","entorno":"pro"}

ADDRESS con la IP del nodo significa que el controlador ha recogido el Ingress. Ese campo tarda entre unos segundos y un par de minutos en aparecer; si sigue vacío pasados cinco, algo va mal (apartado 10).

Los clientes de Rutas Norte ya pueden llegar a la plataforma: es la primera vez en el curso que el tráfico entra de verdad desde fuera del clúster. Que el enrutado se hace por la cabecera Host y no por la IP lo comprobaremos en el ejercicio 1: misma IP, mismo puerto, resultados distintos según el Host. Eso es enrutado L7.

  1. Enrutado por host frente a enrutado por ruta y reescritura

La alternativa a los dos dominios es servir la API bajo /api del dominio principal.

Por host (api.rutasnorte.example) Por ruta (www.rutasnorte.example/api)
Certificados TLS Uno por dominio (o comodín) Uno solo
Registros DNS Uno por subdominio Uno
CORS en la SPA Sí: origen distinto No: mismo origen
Cookies No se comparten entre subdominios (salvo configuración) Compartidas
Reescritura de ruta No necesaria Casi siempre necesaria
Escalar por separado Muy fácil Fácil

Rutas Norte usa ambas: api.rutasnorte.example para las agencias externas, y www.rutasnorte.example/api para que la SPA no tenga que lidiar con CORS.

Reescritura de rutas

El problema: la SPA pide /api/reservas pero api-reservas espera /reservas; sin reescritura recibiría /api/reservas y devolvería 404.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rutas-norte-api-por-ruta
  namespace: rutas-norte-pro
  annotations:
    # $2 = segundo grupo de captura de la expresion regular del path
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
    - host: www.rutasnorte.example
      http:
        paths:
          - path: /api(/|$)(.*)          # grupo 1: /(o fin), grupo 2: el resto
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-reservas
                port:
                  name: http

Cómo leer esa expresión: /api casa el prefijo literal; (/|$) es el grupo 1 (una barra o el final de la cadena, para que casen tanto /api como /api/...); (.*) es el grupo 2, todo lo que venga después; y rewrite-target: /$2 reconstruye la ruta con solo el grupo 2.

Petición del navegador Grupo 2 Lo que recibe api-reservas
/api/reservas reservas /reservas
/api/reservas/33 reservas/33 /reservas/33
/api (vacío) /
/api/salud?v=2 salud /salud?v=2 (la query se conserva)

Y el aviso: pathType pasa a ImplementationSpecific porque estamos usando una expresión regular, algo que Prefix no contempla. Ese Ingress no es portable a Traefik ni al ALB de AWS. Es un compromiso consciente.

Advertencia muy repetida: la reescritura afecta a la ruta de la petición, no a las URLs que la aplicación genera en su HTML o en sus redirecciones. Si api-reservas responde Location: /reservas/33, el navegador irá ahí y no a /api/reservas/33. Las aplicaciones servidas bajo un prefijo deben conocerlo (típicamente con una variable BASE_PATH).

  1. Anotaciones del día a día

Las anotaciones son el mecanismo por el que cada controlador expone sus funciones; estas son las de ingress-nginx que se usan en cualquier plataforma real.

Anotación Para qué Valor típico en Rutas Norte
proxy-body-size Tamaño máximo del cuerpo. 413 Request Entity Too Large si se supera 8m
proxy-read-timeout / proxy-send-timeout Segundos de espera hacia el backend. 504 al agotarse 60
proxy-connect-timeout Espera para establecer conexión 5
limit-rps / limit-connections Limitación de peticiones y conexiones por IP de cliente 20 rps
limit-whitelist IPs exentas del límite Rango de la oficina
whitelist-source-range Solo estas IPs pueden acceder Panel de administración
configuration-snippet Fragmento de configuración NGINX a medida Cabeceras de seguridad
enable-cors / cors-allow-origin Cabeceras CORS gestionadas por el proxy Para las agencias externas
backend-protocol HTTP, HTTPS, GRPC HTTP

Ejemplo aplicado a los picos de tráfico de puentes y vacaciones:

metadata:
  annotations:
    # Limitacion: 20 peticiones por segundo y cliente, con margen de rafaga
    nginx.ingress.kubernetes.io/limit-rps: "20"
    nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
    # La red de la oficina no se limita nunca
    nginx.ingress.kubernetes.io/limit-whitelist: "10.20.30.0/24"
    # Cabeceras de seguridad hacia el cliente
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-Frame-Options: SAMEORIGIN";

Dos advertencias importantes:

  • configuration-snippet está desactivado por defecto en las versiones recientes de ingress-nginx por seguridad (permitía inyectar configuración arbitraria del proxy desde cualquier namespace). Hay que habilitarlo en el ConfigMap del controlador, y conviene pensarlo dos veces.
  • La limitación se aplica por réplica del controlador. Con tres réplicas, un límite de 20 rps es en la práctica 60.

Los ajustes globales (cabeceras por defecto, tamaños de búfer, formato de log) no van en anotaciones sino en el ConfigMap del controlador, ingress-nginx-controller del namespace ingress-nginx.

  1. Depuración

kubectl describe ingress

La primera parada siempre:

kubectl describe ingress rutas-norte -n rutas-norte-pro
Name:             rutas-norte
Namespace:        rutas-norte-pro
Address:          192.168.49.2
Ingress Class:    nginx
Rules:
  Host                      Path  Backends
  ----                      ----  --------
  www.rutasnorte.example
                            /     tienda-web:http (10.244.0.22:8080,10.244.0.29:8080)
  api.rutasnorte.example
                            /     api-reservas:http (10.244.0.31:3000)
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ------------------------  -------
  Normal  Sync    35s   nginx-ingress-controller  Scheduled for sync

Lo que hay que mirar, en este orden: Address relleno (alguien ha recogido el Ingress); Ingress Class correcta; los backends con IPs de pod entre paréntesis —si pone <error: endpoints "tienda-web" not found> o una lista vacía, el problema no es del Ingress, es el Service, y vuelves al diagnóstico de 02-05—; y Events con Sync, que confirma que el controlador aplicó la configuración.

Logs del controlador

kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f
192.168.49.1 - - [05/Aug/2026:11:20:14 +0000] "GET /reservas/33 HTTP/1.1" 200 812
  "-" "curl/8.5.0" 141 0.043 [rutas-norte-pro-api-reservas-http] [] 10.244.0.31:3000 812 0.042 200

Los campos entre corchetes son oro: [rutas-norte-pro-api-reservas-http] es el upstream elegido, y 10.244.0.31:3000 el pod concreto. Si el upstream aparece vacío, ninguna regla casó.

Cuadro de fallos

Síntoma Causa probable Comprobación
ADDRESS vacío tras 5 min No hay controlador, o ingressClassName no casa kubectl get pods -n ingress-nginx; kubectl get ingressclass
404 del controlador (nginx) Ninguna regla casa: Host o path mal curl -H 'Host: ...'; revisa pathType
503 Service Temporarily Unavailable El Service no tiene endpoints kubectl get endpointslices -n <ns>
502 Bad Gateway El pod rechaza la conexión o el targetPort es incorrecto kubectl exec y curl al pod directamente
504 Gateway Time-out El backend tarda más que proxy-read-timeout Sube el tiempo de espera o arregla la lentitud
413 Cuerpo mayor que proxy-body-size Ajusta la anotación
Funciona por IP y no por dominio /etc/hosts o DNS mal getent hosts www.rutasnorte.example
El Ingress no se crea El webhook de validación lo rechaza Lee el mensaje: suele ser regex inválida o ruta mal formada

La distinción merece memorizarse: 503 es que no hay a quién enviar (endpoints vacíos) y 502 es que hay a quién enviar pero no responde bien. Capas distintas.

La verificación definitiva: la configuración generada

Ver el nginx.conf que el controlador ha generado a partir de tu Ingress cierra cualquier discusión sobre qué está pasando: kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A5 "server_name api.rutasnorte.example".

  1. Gateway API: la sucesora

El Ingress ha envejecido mal. Su especificación es tan mínima que todo lo interesante acabó en anotaciones propietarias: reescritura, CORS, limitación, cabeceras... Nada de eso es portable. Además mezcla responsabilidades: quien administra la infraestructura y quien publica una aplicación editan el mismo objeto.

La Gateway API (gateway.networking.k8s.io) es la respuesta oficial, ya estable para sus recursos principales.

Concepto Recurso Quién lo gestiona
Qué implementación se usa GatewayClass Administrador del clúster
El punto de entrada: puertos, TLS, dominios Gateway Equipo de plataforma
Las reglas HTTP: hosts, rutas, filtros, pesos HTTPRoute Equipo de aplicación
Otros protocolos TCPRoute, GRPCRoute, TLSRoute Equipo de aplicación

Ventajas concretas frente a Ingress: separación de roles (el equipo de Rutas Norte crea su HTTPRoute sin tocar el punto de entrada); funciones en el modelo, no en anotaciones —reescritura, redirección, cabeceras y reparto por pesos son campos de la API, así que el canary de 11-04 deja de necesitar anotaciones específicas—; rutas entre namespaces con permiso explícito del propietario del Gateway; y portabilidad real entre implementaciones.

El Ingress no está obsoleto y seguirá funcionando muchos años: hay demasiado desplegado. Pero para una plataforma nueva en 2026 merece la pena evaluar la Gateway API, sobre todo si ya se usa Istio, Cilium o Traefik. Aquí seguimos con Ingress porque es lo que te vas a encontrar y lo que evalúan las certificaciones del módulo 12.

Errores Comunes y Consejos

  • Crear el Ingress sin controlador. El apply va bien y nada funciona. Comprueba kubectl get pods -n ingress-nginx antes.
  • Apuntar a un Service de otro namespace. No es posible. El Ingress vive en el namespace de sus Services.
  • Copiar anotaciones del controlador equivocado. nginx.ingress.kubernetes.io/* es de ingress-nginx; el de F5 usa nginx.org/*. No son intercambiables.
  • Usar kubernetes.io/ingress.class. Obsoleta. Usa ingressClassName.
  • Esperar que Prefix compare caracteres. /api no casa /apificado. Compara segmentos.
  • Confundir 503 con 502. El primero es "no hay endpoints"; el segundo, "el pod no responde bien". Diagnósticos distintos.
  • Reescribir rutas sin que la aplicación lo sepa. Los enlaces y redirecciones que genere seguirán sin el prefijo. Configura el BASE_PATH de la aplicación.
  • Olvidar que el enrutado depende de la cabecera Host. Probar con curl http://IP/ sin -H 'Host: ...' dará 404 aunque todo esté bien.
  • Consejo: un Ingress por aplicación, no uno gigante por clúster. Se despliegan y se revierten de forma independiente.
  • Consejo: las anotaciones de tiempo de espera y tamaño de cuerpo son de las primeras que se necesitan en producción. Ponlas desde el principio con valores razonables en lugar de esperar al primer 413 en un puente.

Ejercicios

Ejercicio 1: Publicar los dos dominios de Rutas Norte

Activa el addon ingress de minikube, crea el Ingress que publica www.rutasnorte.example contra tienda-web y api.rutasnorte.example contra api-reservas, resuelve los dominios en /etc/hosts y comprueba las dos rutas. Demuestra además que el enrutado se hace por cabecera Host.

Ejercicio 2: Servir la API bajo /api con reescritura

Añade al dominio www.rutasnorte.example una ruta /api que llegue a api-reservas sin el prefijo. Verifica en los logs del controlador qué ruta recibe realmente el backend y qué ocurre con /apificado.

Ejercicio 3: Diagnosticar tres Ingress rotos

Reproduce y diagnostica: (A) un Ingress con ingressClassName: traefik en un clúster con solo ingress-nginx; (B) un Ingress correcto cuyo Service apunta a un selector que no casa; (C) un Ingress cuyo backend.service.port.number es 8080 cuando el Service expone el 80. Indica el síntoma exacto de cada uno y el comando que lo identifica.

Soluciones

Ejercicio 1

minikube -p rutas-norte addons enable ingress
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/component=controller -n ingress-nginx --timeout=120s
kubectl apply -f k8s/entornos/pro/ingress-rutas-norte.yaml
kubectl get ingress -n rutas-norte-pro -w      # esperar a que aparezca ADDRESS

# Enrutado por Host: misma IP, tres resultados
IP=$(minikube -p rutas-norte ip)
for H in www.rutasnorte.example api.rutasnorte.example inventado.example; do
  printf '%-28s %s\n' "$H" "$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $H" http://$IP/)"
done
www.rutasnorte.example       200
api.rutasnorte.example       200
inventado.example            404

El 404 del tercero prueba que la decisión se toma leyendo la cabecera Host, no la IP de destino.

Ejercicio 2

# k8s/entornos/pro/ingress-api-por-ruta.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rutas-norte-api-por-ruta
  namespace: rutas-norte-pro
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
    - host: www.rutasnorte.example
      http:
        paths:
          - path: /api(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-reservas
                port:
                  name: http
kubectl apply -f k8s/entornos/pro/ingress-api-por-ruta.yaml
curl -s http://www.rutasnorte.example/api/salud
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=3
"GET /api/salud HTTP/1.1" 200 ... [rutas-norte-pro-api-reservas-http] 10.244.0.31:3000

El cliente pidió /api/salud; el backend recibió /salud. Con /apificado, la regex /api(/|$)(.*) casaría (porque use-regex compara caracteres, no segmentos) y el backend recibiría /ficado — un motivo más para preferir Prefix salvo cuando la reescritura sea imprescindible, y para anclar mejor la expresión si es un problema.

Ejercicio 3

Caso Síntoma Comando que lo identifica
A clase inexistente ADDRESS permanentemente vacío; ningún evento Sync; el dominio no responde kubectl get ingressclass (solo existe nginx) y kubectl describe ingress sin eventos
B selector que no casa El Ingress está bien y con ADDRESS, pero devuelve 503 kubectl describe ingress muestra el backend sin IPs; kubectl get endpointslices vacío
C puerto inexistente 503 y un error explícito en describe <error: endpoints "api-reservas" not found> o backend sin resolver; el port del Ingress debe coincidir con el port del Service, no con el del contenedor
kubectl describe ingress <nombre> -n rutas-norte-pro | sed -n '/Rules/,/Annotations/p'
kubectl get endpointslices -n rutas-norte-pro -l kubernetes.io/service-name=api-reservas

El error de C es especialmente frecuente: el port del Ingress es el del Service (port), no el del contenedor (targetPort). Confundirlos produce un Ingress que parece correcto y devuelve 503.

Conclusión

Rutas Norte ya está publicada. Entiendes la distinción que ordena todo lo demás: el recurso Ingress es una declaración de intenciones que por sí sola no hace absolutamente nada, y el controlador de Ingress es el programa que la observa, genera configuración de proxy y enruta el tráfico de verdad; si falta el segundo, el primero se crea sin errores y el ADDRESS se queda vacío para siempre. Conoces el panorama de controladores, el peligro de confundir ingress-nginx con el controlador homónimo de F5, y sabes seleccionar el tuyo con ingressClassName en lugar de la anotación obsoleta kubernetes.io/ingress.class.

Dominas la anatomía del objeto: rules con host y http.paths, backend.service.name con el puerto del Service (no el del contenedor), y el defaultBackend para lo que no casa nada. Tienes claro que pathType: Prefix compara segmentos de ruta y no caracteres —/api no casa /apificado— que Exact distingue la barra final y las mayúsculas, que ImplementationSpecific es la puerta a las expresiones regulares al precio de la portabilidad, y que cuando varias reglas casan gana la más larga.

Sobre todo, has hecho el trabajo: activaste el addon ingress, publicaste www.rutasnorte.example contra tienda-web y api.rutasnorte.example contra api-reservas con un solo Ingress y un solo punto de entrada, resolviste los dominios con /etc/hosts y demostraste que el reparto se hace leyendo la cabecera Host. Añadiste la ruta /api con rewrite-target entendiendo grupo a grupo la expresión regular, y conoces las anotaciones que se necesitan en producción desde el primer día: tamaño de cuerpo, tiempos de espera, limitación de peticiones y cabeceras de seguridad. Sabes depurar con describe, con los logs del controlador y, en último extremo, leyendo el nginx.conf generado, y distingues sin dudar un 503 (sin endpoints) de un 502 (backend que no responde). Y sabes que la Gateway API es la sucesora, con separación de roles y funciones en el modelo en lugar de en anotaciones.

Queda un problema grave, y es de los que no admiten demora: todo esto va por HTTP en claro. Los datos personales que los clientes escriben para reservar —nombre, DNI, teléfono, correo— y los datos que viajan hacia la pasarela de pagos están recorriendo internet sin cifrar. En 04-05 pondremos HTTPS en los dos dominios: primero con un certificado autofirmado para desarrollo, y después con cert-manager emitiendo y renovando automáticamente certificados de Let's Encrypt mediante los desafíos ACME HTTP-01 y DNS-01.

Curso de Kubernetes

Módulo 1: Introducción a Kubernetes

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados