En la lección anterior desmontamos la ficción del ClusterIP: una dirección que no existe en ninguna interfaz y que solo vive como reglas de iptables programadas por kube-proxy. Pero ese descubrimiento deja intacto el problema que arrastramos desde el final del módulo 3: un ClusterIP solo es alcanzable desde dentro del clúster. Un cliente que quiere comprar un billete en www.rutasnorte.example no tiene ninguna forma de llegar a tienda-web. Esta lección recorre los cuatro tipos de Service —ClusterIP, NodePort, LoadBalancer y ExternalName— más dos variantes que no son tipos pero se comportan como si lo fueran: los servicios headless y los servicios sin selector. Al terminar sabrás exactamente cuál usar en cada situación, por qué LoadBalancer no es la solución para publicar la plataforma, y cómo dar un nombre interno del clúster a la pasarela de pagos externa.
Contenido
- Panorama: seis formas de declarar un Service
- Cómo cada tipo se construye sobre el anterior
NodePorten detalleLoadBalancery su costeexternalTrafficPolicy:Clusterfrente aLocalExternalName: la pasarela de pagos- Servicios sin selector: endpoints a mano
- Servicios headless
sessionAffinity, puertos con nombre y multipuerto- Qué usa Rutas Norte y por qué
- Panorama: seis formas de declarar un Service
Antes de entrar en detalle, la tabla que conviene tener delante durante toda la lección.
| Forma | spec clave |
Alcance | Quién lo implementa | Cuándo usarlo |
|---|---|---|---|---|
| ClusterIP | type: ClusterIP (por defecto) |
Solo dentro del clúster | kube-proxy | Comunicación entre componentes. El 90 % de los casos |
| NodePort | type: NodePort |
IP de cualquier nodo, puerto 30000-32767 | kube-proxy | Pruebas, laboratorio, o como cimiento de un balanceador externo |
| LoadBalancer | type: LoadBalancer |
IP pública/externa | Un controlador del proveedor de nube | Exponer un servicio TCP/UDP al exterior en la nube |
| ExternalName | type: ExternalName + externalName |
Redirección de nombre DNS | CoreDNS (registro CNAME) | Dar un nombre interno a un servicio de fuera |
| Headless | clusterIP: None |
Solo DNS, sin IP virtual | CoreDNS (una A por pod) | StatefulSets, clientes que balancean solos |
| Sin selector | Sin selector, con EndpointSlice manual |
Igual que su tipo | Tú, a mano | Servicio externo con IPs fijas, migraciones |
Las dos últimas filas no son valores de type: son modificaciones que se combinan con él. Un Service puede ser ClusterIP y headless, o ClusterIP y sin selector.
- Cómo cada tipo se construye sobre el anterior
Los tres primeros tipos no son alternativas: son capas acumulativas. Un NodePort sigue teniendo su ClusterIP y sigue funcionando desde dentro. Un LoadBalancer sigue teniendo su NodePort.
flowchart TB
subgraph LB["type: LoadBalancer"]
subgraph NP["type: NodePort"]
subgraph CIP["type: ClusterIP"]
A["IP virtual interna<br/>10.96.x.x:puerto<br/>reglas de kube-proxy"]
end
B["Ademas: puerto 30000-32767<br/>abierto en TODOS los nodos"]
end
C["Ademas: IP externa aprovisionada<br/>por el proveedor, que apunta<br/>a los NodePorts de los nodos"]
end
Compruébalo con la salida de kubectl:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-reservas ClusterIP 10.96.201.14 <none> 3000/TCP 9d
tienda-web NodePort 10.96.88.7 <none> 80:31080/TCP 4m
demo-lb LoadBalancer 10.96.44.190 10.0.0.51 80:32410/TCP 2mFíjate en la columna PORT(S): 80:31080/TCP significa "puerto 80 del Service, publicado también en el 31080 de cada nodo". Y los tres tienen CLUSTER-IP, incluido el LoadBalancer. Esa acumulación es la razón de que el diagnóstico se haga siempre de dentro hacia fuera: si el ClusterIP no funciona, el NodePort tampoco lo hará.
NodePort en detalle
NodePort en detalleUn NodePort abre el mismo puerto en todos los nodos del clúster, independientemente de dónde estén los pods. Si llamas a ese puerto en un nodo que no aloja ninguna réplica, kube-proxy reenvía el tráfico al nodo que sí la tiene.
# k8s/entornos/dev/tienda-web-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: tienda-web
namespace: rutas-norte-dev
labels:
app: tienda-web
app.kubernetes.io/part-of: rutas-norte
entorno: dev
spec:
type: NodePort
selector: # recuerda: solo app y entorno en los selectores
app: tienda-web
entorno: dev
ports:
- name: http
port: 80 # puerto del ClusterIP
targetPort: 8080 # puerto del contenedor nginx
nodePort: 31080 # OPCIONAL: si se omite, lo asigna el clústerPuntos a tener claros:
- El rango por defecto es 30000-32767 (unos 2.700 puertos). Se puede cambiar con
--service-node-port-rangeen el apiserver, pero es una decisión del clúster, no del equipo de aplicación. - Si omites
nodePort, Kubernetes elige uno libre. Es lo recomendable: fijarlo introduce un conflicto potencial entre equipos y unInvalid value: provided port is already allocatedcuando alguien se adelanta. - El puerto se abre en todos los nodos, aunque solo uno tenga pods. Con
externalTrafficPolicy: Localesto cambia (apartado 5). - Prueba en minikube:
Por qué no se usa en producción
| Problema | Consecuencia |
|---|---|
| Puertos feos y arbitrarios | Nadie va a escribir http://rutasnorte.example:31080 en el navegador |
| Sin nombre de dominio ni TLS | Habría que resolver el HTTPS por fuera |
| Sin alta disponibilidad | Apuntas a la IP de un nodo; si ese nodo cae, el servicio deja de responder aunque el pod viva |
| Requiere abrir el cortafuegos | 2.700 puertos potencialmente accesibles en todos los nodos |
| No agrupa servicios | Un puerto por Service, sin enrutado por host ni por ruta |
El uso legítimo de NodePort es como cimiento: los balanceadores de las nubes y muchos controladores de Ingress se apoyan precisamente en él. Para desarrollo y demostraciones rápidas, es perfecto.
LoadBalancer y su coste
LoadBalancer y su costetype: LoadBalancer le dice al clúster: "pide al proveedor de infraestructura un balanceador externo que apunte a este servicio". Kubernetes no lo materializa: lo hace un controlador que forma parte del cloud-controller-manager de la nube, o un complemento como MetalLB en instalaciones propias.
apiVersion: v1
kind: Service
metadata:
name: tienda-web
namespace: rutas-norte-pro
spec:
type: LoadBalancer
selector:
app: tienda-web
entorno: pro
ports:
- name: http
port: 80
targetPort: 8080En un clúster sin proveedor de nube, el resultado es este:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tienda-web LoadBalancer 10.96.44.190 <pending> 80:32410/TCP 5mEse <pending> eterno es una de las preguntas más repetidas por quien empieza. No es un fallo: es que nadie ha recogido la petición. El objeto está bien creado, pero no existe ningún controlador capaz de aprovisionar una IP externa. En minikube se resuelve con:
# En otra terminal; pide contraseña de administrador y hay que dejarlo abierto
minikube -p rutas-norte tunnelNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
tienda-web LoadBalancer 10.96.44.190 10.96.44.190 80:32410/TCPminikube tunnel crea rutas en tu máquina para que las IPs asignadas sean alcanzables. Es una emulación local, no un balanceador real.
El argumento económico que lleva a Ingress
Cada Service de tipo LoadBalancer provoca el aprovisionamiento de un balanceador independiente en la nube, con su IP pública y su factura. Para Rutas Norte:
| Servicio a publicar | ¿Balanceador propio? | Coste mensual aproximado |
|---|---|---|
tienda-web |
Sí | 1 balanceador |
api-reservas |
Sí | 1 balanceador |
| Panel de administración interno | Sí | 1 balanceador |
| Métricas de Grafana (07-04) | Sí | 1 balanceador |
Cuatro balanceadores, cuatro IPs públicas, cuatro certificados TLS que gestionar por separado y ninguna posibilidad de enrutar por ruta o por cabecera. Multiplicado por tres entornos, doce. La alternativa es un único LoadBalancer delante de un controlador de Ingress, que reparte internamente por dominio y por ruta. De ahí viene 04-04.
Además, LoadBalancer opera en L4: no entiende HTTP, no puede enrutar por Host, no puede reescribir rutas. Para servicios que no son HTTP (un broker MQTT, un postgres expuesto a otra red) sigue siendo la respuesta correcta.
externalTrafficPolicy: Cluster frente a Local
externalTrafficPolicy: Cluster frente a LocalAplica a NodePort y LoadBalancer, y decide qué hace un nodo cuando le llega tráfico externo para un Service.
flowchart TB
subgraph CL["externalTrafficPolicy: Cluster (por defecto)"]
direction LR
C1["Cliente 203.0.113.9"] --> N1["Nodo A<br/>(sin pods)"]
N1 -->|"SNAT: origen pasa a IP de nodo A"| N2["Nodo B<br/>pod tienda-web"]
N2 --> R1["El pod ve origen = 192.168.49.2"]
end
subgraph LO["externalTrafficPolicy: Local"]
direction LR
C2["Cliente 203.0.113.9"] --> M1["Nodo A<br/>(sin pods)"]
M1 -->|"DESCARTA"| X1["Sin respuesta"]
C2 --> M2["Nodo B<br/>pod tienda-web"]
M2 --> R2["El pod ve origen = 203.0.113.9"]
end
Cluster (por defecto) |
Local |
|
|---|---|---|
| Salto extra entre nodos | Sí, si el nodo no tiene pods | No, nunca |
| IP de origen del cliente | Se pierde (SNAT al nodo) | Se conserva |
| Reparto de carga | Uniforme entre todos los pods | Proporcional a los pods de ese nodo |
| Comprobación de salud del balanceador | Todos los nodos responden | Solo los que tienen pods (por healthCheckNodePort) |
| Si un nodo no tiene pods | Reenvía | Descarta el paquete |
El desequilibrio de Local merece un ejemplo. Si tienda-web tiene 3 réplicas y el nodo A aloja 2 y el nodo B aloja 1, un balanceador que reparta 50/50 entre nodos hará que cada pod del nodo A reciba un 25 % del tráfico y el del nodo B un 50 %. Se corrige con antiafinidad de pods (06-05).
Para Rutas Norte, la IP de origen importa: los registros de acceso, la limitación de peticiones en los picos de puentes y el bloqueo de direcciones abusivas dependen de ella. Con Cluster todas las peticiones parecerían venir de los nodos.
Cuando el tráfico llega por un Ingress HTTP hay una alternativa: el controlador añade la cabecera X-Forwarded-For con la IP real. Pero eso solo sirve para HTTP y solo si la aplicación la lee. Existe también internalTrafficPolicy, con la misma idea aplicada al tráfico interno del clúster: Local obliga a que un pod se conecte solo a réplicas de su propio nodo.
ExternalName: la pasarela de pagos
ExternalName: la pasarela de pagosapi-reservas tiene que cobrar los billetes contra pagos.proveedorexterno.example, que está fuera del clúster. Se podría poner ese dominio en un ConfigMap y listo, pero ExternalName da una capa de indirección muy útil.
# k8s/entornos/pro/pasarela-pagos-externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: pasarela-pagos
namespace: rutas-norte-pro
labels:
app.kubernetes.io/part-of: rutas-norte
entorno: pro
spec:
type: ExternalName
externalName: pagos.proveedorexterno.exampleQué ocurre por debajo: CoreDNS crea un registro CNAME. Cuando api-reservas resuelve pasarela-pagos.rutas-norte-pro.svc.cluster.local, la respuesta es un alias hacia pagos.proveedorexterno.example, que se resuelve por el DNS externo (04-03).
kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
--image=nicolaka/netshoot -- nslookup pasarela-pagosServer: 10.96.0.10
Address: 10.96.0.10#53
pasarela-pagos.rutas-norte-pro.svc.cluster.local
canonical name = pagos.proveedorexterno.example.
Name: pagos.proveedorexterno.example
Address: 198.51.100.44Ventajas para nuestro caso:
- La aplicación siempre apunta a
pasarela-pagos, en los tres entornos. Lo que cambia es elexternalNamede cada namespace: enrutas-norte-devapuntaría apagos-sandbox.proveedorexterno.example, enproal de producción. Cambiar de proveedor no toca ni una línea de código. - No hay proxy: el tráfico va directo del pod al destino externo, sin pasar por kube-proxy.
Y sus tres limitaciones, que causan sorpresas:
- No traduce puertos. El
portsde unExternalNamees decorativo. Si el proveedor escucha en el 8443, tu cliente debe pedir el 8443. - No hace TLS. El certificado del proveedor será el de
pagos.proveedorexterno.example, no el depasarela-pagos, así que la validación SNI/hostname usará el nombre real. Con HTTP yHostreescrito puede fallar la comprobación en el otro extremo. - Depende del DNS externo. Si CoreDNS no puede reenviar al resolutor del nodo, esto no funciona.
Nota importante para 04-06: un ExternalName no cuenta como excepción en una política de red. Para permitir la salida hacia el proveedor habrá que autorizar su rango de IPs con ipBlock.
- Servicios sin selector: endpoints a mano
Un Service normal averigua sus destinos mediante el selector. Si omites el selector, el controlador de endpoints no crea nada y puedes escribir tú mismo el EndpointSlice. El resultado es un ClusterIP normal, con su nombre DNS y su balanceo, que apunta a direcciones que no son pods.
Caso realista de Rutas Norte: durante la migración, la base de datos de facturación sigue en una máquina virtual de la oficina, en 10.20.30.40:5432.
# k8s/entornos/pro/facturacion-externa.yaml
apiVersion: v1
kind: Service
metadata:
name: facturacion-legado
namespace: rutas-norte-pro
spec:
ports:
- name: postgres
port: 5432
targetPort: 5432
protocol: TCP
# SIN selector: nadie rellena los endpoints automaticamente
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: facturacion-legado-manual
namespace: rutas-norte-pro
labels:
# OBLIGATORIA: enlaza el slice con el Service
kubernetes.io/service-name: facturacion-legado
addressType: IPv4
ports:
- name: postgres # debe coincidir con el name del Service
port: 5432
protocol: TCP
endpoints:
- addresses:
- "10.20.30.40"
conditions:
ready: trueDetalles críticos:
- La etiqueta
kubernetes.io/service-namees lo que une el slice con el Service. Sin ella, el Service se queda sin endpoints. - El campo
ports[].namedel EndpointSlice debe coincidir con elports[].namedel Service. - Nadie comprueba la salud de esa dirección. Si la máquina cae, el Service sigue enviando tráfico. La responsabilidad de mantener la lista es tuya.
- Las direcciones no pueden ser IPs de Services (
10.96.x.x) ni de bucle local.
Ventaja principal: la aplicación conecta a facturacion-legado:5432 desde el primer día. El día que esa base de datos se migre a un pod del clúster, se añade el selector, se borra el slice manual y la aplicación no se entera. Es el patrón de indirección que hace las migraciones tolerables.
- Servicios headless
Un Service con clusterIP: None es headless: no tiene IP virtual, no crea reglas en kube-proxy y no balancea nada. Lo único que hace es publicar en el DNS una entrada A por cada pod que casa con el selector.
apiVersion: v1
kind: Service
metadata:
name: postgres-reservas-headless
namespace: rutas-norte-pro
spec:
clusterIP: None # esto lo convierte en headless
selector:
app: postgres-reservas
entorno: pro
ports:
- name: postgres
port: 5432| Service normal | Service headless | |
|---|---|---|
CLUSTER-IP |
10.96.x.x |
None |
| Reglas de kube-proxy | Sí | No |
| Respuesta DNS | Una A: la IP virtual | Una A por pod listo |
| Balanceo | Lo hace el kernel | Lo hace el cliente |
| Uso típico | Todo lo demás | Bases de datos en clúster, StatefulSets |
$ nslookup postgres-reservas-headless.rutas-norte-pro.svc.cluster.local
Name: postgres-reservas-headless... Address: 10.244.0.14
Name: postgres-reservas-headless... Address: 10.244.0.18
Name: postgres-reservas-headless... Address: 10.244.0.23¿Para qué sirve esto? Para los casos en que el cliente necesita distinguir los pods entre sí:
- Una réplica de PostgreSQL con un primario y dos secundarios: el cliente escribe en el primario y lee de los secundarios. Un ClusterIP que reparta al azar lo rompería.
- Bases de datos distribuidas (Cassandra, Elasticsearch, Kafka) donde cada nodo debe conocer a los demás por su nombre.
- Clientes con balanceo propio (gRPC), que quieren la lista de destinos para repartir por petición y no por conexión.
El detalle completo de los registros DNS que genera está en 04-03, y su uso real —con identidades estables tipo postgres-reservas-0.postgres-reservas-headless— llega con los StatefulSets de 06-01, donde por fin postgres-reservas dejará de ser un Deployment.
Un matiz: un Service headless y sin selector no genera registros A; en su lugar CoreDNS devuelve los nombres del externalName si lo hubiera, o los endpoints manuales que definas.
sessionAffinity, puertos con nombre y multipuerto
sessionAffinity, puertos con nombre y multipuertosessionAffinity: ClientIP
Por defecto kube-proxy reparte las conexiones al azar. Con afinidad, todas las conexiones de la misma IP de origen van al mismo pod durante un tiempo.
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 horas; por defecto también 10800Es un martillo tosco y hay que decirlo claro:
- Funciona por IP, no por usuario. Cien clientes detrás del NAT de una empresa son una sola IP y caen todos en el mismo pod.
- Rompe el reparto de carga y complica los despliegues progresivos.
- Con
externalTrafficPolicy: Cluster, la IP de origen ya viene enmascarada, así que la afinidad se aplica sobre la IP del nodo: inútil.
Para Rutas Norte no lo usamos: api-reservas es sin estado, y la sesión del comprador va en un JWT y en redis-cache. Esa es la solución correcta; la afinidad de sesión es un parche para aplicaciones que guardan estado en memoria.
Puertos con nombre y multipuerto
Cuando un Service expone más de un puerto, name es obligatorio en cada uno.
apiVersion: v1
kind: Service
metadata:
name: api-reservas
namespace: rutas-norte-pro
spec:
selector:
app: api-reservas
entorno: pro
ports:
- name: http # obligatorio al haber varios
port: 3000
targetPort: api # apunta a un puerto CON NOMBRE del contenedor
protocol: TCP
- name: metrics # lo consumirá Prometheus en 07-03
port: 9090
targetPort: metrics
protocol: TCPCon el contenedor declarando:
Ventajas de usar targetPort con nombre: si mañana la aplicación cambia de puerto, se toca solo el Deployment. Y los nombres de puerto son los que habilitan los registros SRV del DNS (04-03). Restricción: el nombre debe tener 15 caracteres o menos y ser minúsculas, dígitos y guiones.
- Qué usa Rutas Norte y por qué
| Componente | Tipo elegido | Motivo |
|---|---|---|
tienda-web |
ClusterIP |
Se publicará por Ingress (04-04), no directamente |
api-reservas |
ClusterIP multipuerto (http + metrics) |
Igual; las métricas nunca se exponen fuera |
postgres-reservas |
ClusterIP (y headless en 06-01) |
Nunca debe salir del clúster |
redis-cache |
ClusterIP |
Uso interno exclusivo |
worker-notificaciones |
Sin Service | No recibe conexiones entrantes; solo consume de la cola |
pasarela-pagos |
ExternalName |
Indirección hacia el proveedor externo |
facturacion-legado |
Sin selector + EndpointSlice manual | Base de datos aún fuera del clúster |
| Controlador de Ingress | LoadBalancer (uno solo) |
Un balanceador para toda la plataforma |
Esa última fila es la conclusión práctica de la lección: la plataforma entera acaba teniendo un solo punto de entrada de pago, y todo lo demás vive en ClusterIP.
Un detalle sobre worker-notificaciones que conviene interiorizar: no todo Deployment necesita un Service. El Service existe para recibir conexiones. Un proceso que solo hace conexiones salientes no necesita ninguno. (Sí necesitará uno si más adelante expone métricas para Prometheus.)
Errores Comunes y Consejos
- Poner
type: LoadBalanceren un clúster local y esperar una IP. El<pending>es lo esperado sin proveedor. En minikube,minikube tunnel; en un servidor propio, MetalLB. - Fijar
nodePorta mano "para que sea fácil de recordar". Genera colisiones entre equipos y despliegues que fallan conprovided port is already allocated. Deja que lo asigne el clúster. - Cambiar de
ClusterIPaNodePorty creer que se pierde el ClusterIP. No se pierde: los tipos son acumulativos. El tráfico interno sigue igual. - Usar
ExternalNamecon puertos distintos. No traduce puertos. Si el proveedor escucha en 8443, tu cliente debe pedir 8443 explícitamente. - Un Service sin selector sin EndpointSlice. Se crea sin ningún error y no responde nunca. Comprueba siempre con
kubectl get endpointslices -l kubernetes.io/service-name=<svc>. - Olvidar
nameen un Service multipuerto. El error es claro (must specify name for multiple ports), pero llega tarde: acostúmbrate a nombrar siempre los puertos, incluso con uno solo. - Usar
sessionAffinitypara arreglar una aplicación con estado en memoria. Estás tapando el problema. Saca el estado aredis-cache. - Esperar que un Service headless balancee. No lo hace. El cliente recibe todas las IPs y decide él. Muchas bibliotecas HTTP cogen solo la primera.
- Consejo:
kubectl get svc -A -o custom-columns=NS:.metadata.namespace,NOMBRE:.metadata.name,TIPO:.spec.typees una auditoría de un vistazo. CualquierLoadBalancerinesperado es dinero saliendo. - Consejo: al depurar, ve siempre de dentro afuera. ClusterIP → NodePort → LoadBalancer. Un fallo en la capa interna se manifiesta en todas las externas.
Ejercicios
Ejercicio 1: Publicar tienda-web con NodePort y comprobar las capas
En rutas-norte-dev, cambia el Service de tienda-web a NodePort sin fijar el puerto. Comprueba que sigue funcionando desde dentro por su ClusterIP y desde fuera por la IP del nodo. Explica qué columna de kubectl get svc te dice ambos puertos.
Ejercicio 2: La pasarela de pagos con ExternalName
Crea el Service pasarela-pagos en rutas-norte-dev apuntando a un sandbox (pagos-sandbox.proveedorexterno.example) y en rutas-norte-pro al dominio real. Verifica desde un pod efímero que la resolución devuelve un CNAME distinto en cada namespace. Explica por qué esto permite que el código de api-reservas sea idéntico en ambos entornos.
Ejercicio 3: Base de datos de facturación fuera del clúster
Crea facturacion-legado como Service sin selector con un EndpointSlice manual apuntando a 10.20.30.40:5432. Comprueba que el Service tiene endpoints y que el nombre resuelve. Después, provoca el fallo típico: borra la etiqueta kubernetes.io/service-name del slice y observa qué pasa.
Soluciones
Ejercicio 1
kubectl patch svc tienda-web -n rutas-norte-dev -p '{"spec":{"type":"NodePort"}}'
kubectl get svc tienda-web -n rutas-norte-dev# Desde dentro: el ClusterIP sigue vivo
kubectl run t --rm -it --restart=Never -n rutas-norte-dev \
--image=nicolaka/netshoot -- curl -s -o /dev/null -w '%{http_code}\n' http://tienda-web
# Desde fuera
curl -s -o /dev/null -w '%{http_code}\n' http://$(minikube -p rutas-norte ip):30417La columna PORT(S) con formato 80:30417/TCP da los dos: a la izquierda el puerto del ClusterIP, a la derecha el NodePort. Demuestra que NodePort añade una capa sobre ClusterIP.
Ejercicio 2
for NS in dev pro; do
DEST=$( [ $NS = pro ] && echo pagos.proveedorexterno.example \
|| echo pagos-sandbox.proveedorexterno.example )
kubectl create service externalname pasarela-pagos \
--external-name=$DEST -n rutas-norte-$NS
done
kubectl run t --rm -it --restart=Never -n rutas-norte-dev \
--image=nicolaka/netshoot -- nslookup pasarela-pagospasarela-pagos.rutas-norte-dev.svc.cluster.local
canonical name = pagos-sandbox.proveedorexterno.example.El código de api-reservas usa siempre http://pasarela-pagos/cobros. El namespace determina a qué proveedor real se traduce, sin variables de entorno distintas ni ramas en el código. Cambiar de proveedor es editar un manifiesto.
Ejercicio 3
kubectl apply -f k8s/entornos/pro/facturacion-externa.yaml
kubectl get endpointslices -n rutas-norte-pro \
-l kubernetes.io/service-name=facturacion-legado# Provocar el fallo: quitar la etiqueta que une slice y Service
kubectl label endpointslice facturacion-legado-manual \
-n rutas-norte-pro kubernetes.io/service-name-
kubectl get endpointslices -n rutas-norte-pro \
-l kubernetes.io/service-name=facturacion-legado
# No resources foundEl Service sigue existiendo, su ClusterIP sigue asignado, kubectl get svc no muestra nada raro, y toda conexión se queda colgada hasta agotar el tiempo de espera. Es el mismo cuadro clínico que un selector que no casa, visto en 02-05: el Service parece sano y no tiene destinos. Por eso la primera comprobación ante "el Service no responde" es siempre mirar sus endpoints.
Conclusión
Ya conoces el catálogo completo. ClusterIP es el tipo por defecto y el que usa casi toda la plataforma: solo alcanzable desde dentro. NodePort añade encima un puerto del rango 30000-32767 abierto en todos los nodos, útil para probar y como cimiento de otros mecanismos, pero inaceptable en producción por sus puertos arbitrarios, su falta de TLS y su dependencia de la IP de un nodo concreto. LoadBalancer añade encima una IP externa aprovisionada por el proveedor —de ahí el <pending> eterno cuando no hay ninguno, y minikube tunnel para emularlo—, y su coste de un balanceador por servicio es exactamente el argumento que justifica el Ingress. ExternalName no enruta tráfico: hace que CoreDNS devuelva un CNAME, y con él hemos dado a la pasarela de pagos un nombre interno estable, distinto por entorno, sin tocar el código.
Sabes que los tres primeros tipos son capas acumulativas, lo que impone diagnosticar siempre de dentro hacia fuera. Entiendes externalTrafficPolicy: Cluster reenvía entre nodos pero destruye la IP de origen del cliente, mientras que Local la conserva a cambio de un reparto de carga desigual. Y dominas las dos variantes que no son tipos: los servicios sin selector, donde escribes tú el EndpointSlice para apuntar a una base de datos externa y donde la etiqueta kubernetes.io/service-name es el eslabón que todo el mundo olvida; y los servicios headless, que renuncian a la IP virtual para publicar una entrada A por pod, imprescindibles cuando el cliente necesita distinguir réplicas.
Hay un hilo que ha atravesado toda la lección sin desarrollarse: el DNS. ExternalName funciona porque CoreDNS crea un CNAME. Headless funciona porque CoreDNS crea una A por pod. api-reservas encuentra a postgres-reservas porque hay un nombre que se resuelve. La siguiente lección, 04-03, abre esa caja: qué es CoreDNS, qué formas cortas de nombre funcionan y cuándo, por qué ndots: 5 puede multiplicar por cinco las consultas a la pasarela de pagos, y cómo diagnosticar los tres fallos de DNS que verás una y otra vez en tu carrera.
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
