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
- El problema: varios servicios HTTP, un solo punto de entrada
- Recurso frente a controlador: la distinción esencial
- Panorama de controladores
IngressClassy la anotación heredada- Anatomía de un Ingress
pathType: los tres valores y los casos que confunden- Caso práctico: publicar la tienda y la API
- Enrutado por host frente a enrutado por ruta y reescritura
- Anotaciones del día a día
- Depuración
- Gateway API: la sucesora
- 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.
- 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.
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).
- 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.
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 70sLos 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.
IngressClass y la anotación heredada
IngressClass y la anotación heredadaUn 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-nginxEn 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.
- 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 nombreRestricciones que provocan la mayoría de los fallos:
- El Ingress y los Services deben estar en el mismo namespace. Un Ingress de
rutas-norte-prono puede apuntar a un Service derutas-norte-dev: es diseño de seguridad, no limitación técnica. - El
portpuede sernumberoname, pero no ambos. Referenciarlo por nombre sobrevive a un cambio de número. hostacepta un comodín en el primer nivel (*.rutasnorte.example), que casaapi.rutasnorte.examplepero nowww.api.rutasnorte.exampleni el dominio desnudo. Las reglas sinhostcasan 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.
pathType: los tres valores y los casos que confunden
pathType: los tres valores y los casos que confundenpathType 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 |
Sí | Exacto |
/api Prefix |
/api/ |
Sí | Segmento completo |
/api/ Prefix |
/api |
Sí | La barra final se ignora al comparar |
/api Prefix |
/api/reservas/33 |
Sí | Descendiente por segmentos |
/api Prefix |
/apificado |
No | apificado es otro segmento distinto |
/api Exact |
/api |
Sí | |
/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".
- 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: httpFí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/saludNAME 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.
- 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: httpCó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).
- 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-snippetestá 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.
- Depuración
kubectl describe ingress
La primera parada siempre:
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 syncLo 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
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 200Los 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".
- 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
applyva bien y nada funciona. Compruebakubectl get pods -n ingress-nginxantes. - 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 usanginx.org/*. No son intercambiables. - Usar
kubernetes.io/ingress.class. Obsoleta. UsaingressClassName. - Esperar que
Prefixcompare caracteres./apino casa/apificado. Compara segmentos. - Confundir
503con502. 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_PATHde la aplicación. - Olvidar que el enrutado depende de la cabecera
Host. Probar concurl http://IP/sin-H 'Host: ...'dará404aunque 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
413en 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/)"
doneEl 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: httpkubectl 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=3El cliente pidió /api/salud; el backend recibió /salud. Con /apificado, la regex /api(/|$)(.*) sí 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-reservasEl 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
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
