Módulo 4: Networking Ingress And Networkpolicy
4. Manos a la obra: instalando `ingress-nginx` en `kind`
Descripción
Esta es la lección donde contratas al recepcionista que la lección 3 describió en teoría: ingress-nginx, el Ingress controller de referencia de la comunidad de Kubernetes, corriendo de verdad dentro de andes-cargo-cluster. Antes de instalarlo, sin embargo, esta lección tiene que resolver algo que ningún módulo anterior necesitó: ingress-nginx en kind necesita que el clúster reserve, desde el momento en que se crea, dos puertos del host (80 y 443) hacia el nodo donde va a correr el controlador — una configuración que kind-config.yaml no tenía todavía. Eso significa recrear el clúster. Todo lo que sigue corrió de verdad, incluido un hallazgo real que no estaba en el plan: el propio manifiesto oficial de ingress-nginx dejó el Pod del controlador en el nodo equivocado la primera vez, y esta lección documenta cómo se diagnostica y se corrige.
Conexión con el módulo
Esta lección construye, por fin, el Ingress controller que la lección 3 explicó sin ejecutar nada. La lección 5 declara la primera regla de Ingress real contra este controlador, ya instalado y sano.
Paso 1 — Por qué esto exige recrear el clúster, no solo reconfigurarlo
ingress-nginx necesita, para funcionar dentro de kind, que el tráfico que llega a los puertos 80/443 de tu máquina (el host) se reenvíe hacia el contenedor Docker de un nodo específico del clúster — el campo extraPortMappings de kind-config.yaml, que ya viste mencionado en el Módulo 1 sin usarlo todavía. El problema: kind solo lee extraPortMappings en el momento de crear cada nodo — no existe ningún comando para agregarlo a un clúster que ya está corriendo, de la misma forma que no puedes cambiar el número de puerto que un contenedor Docker ya arrancado tiene mapeado sin recrearlo.
POR QUÉ extraPortMappings NO SE PUEDE "AGREGAR DESPUÉS"
kind create cluster --config kind-config.yaml
│
▼
Docker crea el contenedor del nodo
CON el mapeo de puertos ya decidido
(igual que "docker run -p 80:80")
│
▼
El contenedor ya existe — cambiar sus
mapeos de puerto exige recrearlo, no
hay ningún "kubectl edit" para esto
Esto no es un paso opcional de esta lección — es la razón técnica exacta por la que vas a recrear andes-cargo-cluster ahora mismo, con un kind-config.yaml actualizado.
Paso 2 — El kind-config.yaml actualizado
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: andes-cargo-cluster
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
Frente al kind-config.yaml del Módulo 1, el nodo control-plane gana dos campos nuevos:
extraPortMappings— dos entradas, una por cada puerto HTTP estándar (80,443). Cada una dice "el puertohostPortde mi máquina reenvía hacia elcontainerPortde este nodo" — el mismo concepto quedocker run -p 80:80, declarado en YAML en vez de en la línea de comandos.kubeadmConfigPatches— un mecanismo dekindpara inyectar configuración adicional dekubeadm(la herramienta quekindusa por debajo para inicializar cada nodo) al momento de crearlo. Aquí se usa para agregar la etiquetaingress-ready=trueal nodocontrol-plane— una etiqueta que no significa nada especial para Kubernetes por sí sola; es una convención que el propio manifiesto deingress-nginxreconoce, y que vas a usar tú mismo en el Paso 6 para resolver un hallazgo real de esta lección.
Nota algo importante: solo el nodo control-plane recibe ambos campos — los dos worker se quedan exactamente igual que antes. Esa decisión es intencional: extraPortMappings solo tiene sentido en el nodo específico donde vas a forzar que corra el controlador; declararlo en los tres nodos no agregaría ninguna capacidad, solo confusión sobre cuál puerto del host apunta a cuál contenedor.
Paso 3 — Recrea el clúster
kind delete cluster --name andes-cargo-cluster
Qué esperar:
Deleting cluster "andes-cargo-cluster" ...
Deleted nodes: ["andes-cargo-cluster-control-plane" "andes-cargo-cluster-worker" "andes-cargo-cluster-worker2"]
kind create cluster --config kind-config.yaml --name andes-cargo-cluster
Qué esperar (literal, ejecutado — la misma secuencia de pasos que el Módulo 1, lección 5, porque el mecanismo de creación no cambió, solo su configuración):
Creating cluster "andes-cargo-cluster" ...
• Ensuring node image (kindest/node:v1.36.1) 🖼 ...
✓ Ensuring node image (kindest/node:v1.36.1) 🖼
• Preparing nodes 📦 📦 📦 ...
✓ Preparing nodes 📦 📦 📦
• Writing configuration 📜 ...
✓ Writing configuration 📜
• Starting control-plane 🕹️ ...
✓ Starting control-plane 🕹️
• Installing CNI 🔌 ...
✓ Installing CNI 🔌
• Installing StorageClass 💾 ...
✓ Installing StorageClass 💾
• Joining worker nodes 🚜 ...
✓ Joining worker nodes 🚜
Set kubectl context to "kind-andes-cargo-cluster"
You can now use your cluster with:
kubectl cluster-info --context kind-andes-cargo-cluster
Antes de recrear el clúster en tu propia máquina, confirma que los puertos
80y443de tu host están libres (lsof -nP -iTCP:80 -sTCP:LISTENy el equivalente para443en macOS/Linux) — si algo más ya los usa (un servidor web local, por ejemplo),kind create clusterva a fallar al intentar reservarlos. La sección "Errores comunes" de esta lección profundiza en esto.
Paso 4 — Confirma la etiqueta ingress-ready
kubectl get nodes --show-labels
Qué esperar (recortado a las columnas relevantes; AGE es tu valor variable — fíjate en ingress-ready=true, presente solo en el control-plane, exactamente como declaraste en kind-config.yaml):
NAME STATUS ROLES AGE LABELS
andes-cargo-cluster-control-plane Ready control-plane 31s ...,ingress-ready=true,kubernetes.io/hostname=andes-cargo-cluster-control-plane,...
andes-cargo-cluster-worker Ready <none> 16s ...,kubernetes.io/hostname=andes-cargo-cluster-worker,...
andes-cargo-cluster-worker2 Ready <none> 16s ...,kubernetes.io/hostname=andes-cargo-cluster-worker2,...
Esta es la primera confirmación real de que kubeadmConfigPatches funcionó: una etiqueta que tú declaraste en un archivo YAML antes de que el nodo existiera, ahora visible como metadata real del nodo — el mismo tipo de etiqueta (app=, role=) que ya usas todos los días en selector/matchLabels, solo que esta vez a nivel de nodo, no de Pod.
Paso 5 — Recarga la imagen, y reconstruye el estado del Módulo 3
Recrear el clúster significa que perdiste todo lo que vivía dentro — la imagen cargada, el namespace, el Deployment, el Service, la configuración. Nada de eso se perdió de forma permanente (tu andes-cargo-status-api:latest sigue en el Docker de tu máquina, y tus manifiestos .yaml siguen en disco); solo hay que volver a aplicarlos, exactamente como ya hiciste en el Módulo 1 y el Módulo 3.
kind load docker-image andes-cargo-status-api:latest --name andes-cargo-cluster
Qué esperar (literal, ejecutado — un mensaje por nodo, los tres reciben la imagen porque ninguno la tenía todavía en este clúster nuevo):
Image: "andes-cargo-status-api:latest" with ID "sha256:d07c069076658570594753ad62b86b160588675400625a6c1179b5f4b5022b32" not yet present on node "andes-cargo-cluster-worker", loading...
Image: "andes-cargo-status-api:latest" with ID "sha256:d07c069076658570594753ad62b86b160588675400625a6c1179b5f4b5022b32" not yet present on node "andes-cargo-cluster-control-plane", loading...
Image: "andes-cargo-status-api:latest" with ID "sha256:d07c069076658570594753ad62b86b160588675400625a6c1179b5f4b5022b32" not yet present on node "andes-cargo-cluster-worker2", loading...
Ahora reaplica, en orden, los seis manifiestos que dejó el Módulo 3 —namespace.yaml, configmap.yaml, secret.yaml, deployment.yaml, service.yaml, hpa.yaml—, sin ningún cambio de contenido frente a los que ya conoces:
kubectl apply -f namespace.yaml
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f hpa.yaml
kubectl rollout status deployment/andes-cargo-status-api -n andes-cargo --timeout=120s
Qué esperar (literal, ejecutado):
namespace/andes-cargo created
configmap/andes-cargo-status-api-config created
secret/andes-cargo-status-api-secrets created
deployment.apps/andes-cargo-status-api created
service/status-api-service created
horizontalpodautoscaler.autoscaling/andes-cargo-status-api-hpa created
Waiting for deployment "andes-cargo-status-api" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "andes-cargo-status-api" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "andes-cargo-status-api" rollout to finish: 2 of 3 updated replicas are available...
deployment "andes-cargo-status-api" successfully rolled out
andes-cargo-cluster está de vuelta, exactamente donde el Módulo 3 lo dejó — mismos nombres, misma configuración, mismas tres réplicas — solo que ahora, además, con extraPortMappings y la etiqueta ingress-ready disponibles desde su creación. Nada del contenido de tus manifiestos cambió; solo el kind-config.yaml que describe el clúster que los sostiene.
Paso 6 — Instala ingress-nginx, el manifiesto oficial para kind
El proyecto ingress-nginx publica un manifiesto estático, versionado, específico para clústeres kind — pensado exactamente para el patrón de extraPortMappings en el nodo control-plane que acabas de configurar:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/kind/deploy.yaml
Qué esperar (literal, ejecutado — la URL incluye el tag controller-v1.15.1, la versión estable más reciente confirmada al momento de escribir esta lección; verifica la versión vigente en el propio repositorio si retomas esta guía más adelante):
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
Un namespace nuevo (ingress-nginx), el Deployment del propio controlador, dos Job de una sola ejecución que generan el certificado del admission webhook interno de ingress-nginx (una pieza de seguridad del propio proyecto, sin relación con el admission control de Gatekeeper/Kyverno del Módulo 6 de esta guía — mismo concepto general, proyecto distinto), y un IngressClass llamado nginx — el valor exacto que vas a usar en ingressClassName en la lección 5.
Paso 7 — El hallazgo real: el Pod aterrizó en el nodo equivocado
Aquí es donde esta lección se detiene a mostrar algo que no estaba en el plan, pero que corrió de verdad al escribirla — y que vale más que si todo hubiera salido perfecto al primer intento. Confirma dónde quedó el Pod del controlador:
kubectl get pods -n ingress-nginx -o wide
Qué esperar (literal, ejecutado — fíjate en la columna NODE):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-54754544b9-zh5m5 0/1 ContainerCreating 0 4s <none> andes-cargo-cluster-worker <none> <none>
El Pod del controlador quedó programado en andes-cargo-cluster-worker — no en andes-cargo-cluster-control-plane, el único nodo que declaraste con extraPortMappings. Esto es un problema real: el controlador va a intentar reservar los puertos 80/443 del nodo donde vive (hostPort, el mismo mecanismo que declaraste en kind-config.yaml), pero el mapeo de tu kind-config.yaml solo reenvía el tráfico de tu máquina hacia el control-plane — si el controlador corre en un worker, tu curl de la lección 5 no va a llegar a ningún lado.
La causa, confirmada revisando el manifiesto que acabas de aplicar:
kubectl get deployment ingress-nginx-controller -n ingress-nginx -o jsonpath='{.spec.template.spec.nodeSelector}'
Qué esperar (literal):
{"kubernetes.io/os":"linux"}
El nodeSelector del Deployment, tal como lo publica el manifiesto oficial, solo exige kubernetes.io/os: linux — cualquier nodo Linux del clúster (los tres, en este caso) es un candidato válido para el kube-scheduler. La etiqueta ingress-ready=true que declaraste en kind-config.yaml existe en el nodo, pero nada en el manifiesto de ingress-nginx la usa todavía para restringir dónde puede programarse el Pod.
Paso 8 — Corrígelo: exige la etiqueta ingress-ready
kubectl patch deployment ingress-nginx-controller -n ingress-nginx \
--type='json' \
-p='[{"op":"add","path":"/spec/template/spec/nodeSelector/ingress-ready","value":"true"}]'
Qué esperar:
deployment.apps/ingress-nginx-controller patched
kubectl patch con --type='json' aplica un cambio quirúrgico —agregar una sola clave al nodeSelector existente— sin tener que reescribir el manifiesto Deployment completo. Al cambiar template.spec (el nodeSelector es parte de esa plantilla), Kubernetes dispara el mismo mecanismo de RollingUpdate que ya conoces del Módulo 3: crea un Pod nuevo con la plantilla corregida, y solo entonces retira el viejo.
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx --timeout=180s
Qué esperar (literal, ejecutado):
Waiting for deployment "ingress-nginx-controller" rollout to finish: 0 of 1 updated replicas are available...
deployment "ingress-nginx-controller" successfully rolled out
Paso 9 — Confirma: el Pod está en el nodo correcto, y Running
kubectl get pods -n ingress-nginx -o wide
Qué esperar (literal, ejecutado — nombre de Pod e IP son tus valores variables; el nodo, ahora sí, es el control-plane):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-5b9b5fd68-9pr6j 1/1 Running 0 17s 10.244.0.5 andes-cargo-cluster-control-plane <none> <none>
1/1 Running, en andes-cargo-cluster-control-plane — el único nodo cuyo extraPortMappings de verdad reenvía el tráfico de tu máquina hacia adentro del clúster. Confirma también el Service y el IngressClass que el manifiesto creó:
kubectl get svc -n ingress-nginx
kubectl get ingressclass
Qué esperar (literal — los dos puertos NodePort de la columna PORT(S) son asignados al azar por Kubernetes, marcados como variables; el resto es literal):
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.96.43.245 <pending> 80:30626/TCP,443:32283/TCP 32s
ingress-nginx-controller-admission ClusterIP 10.96.181.231 <none> 443/TCP 32s
NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx <none> 32s
EXTERNAL-IP: <pending> es normal y esperado — en una nube real, ese campo se llenaría con la IP de un balanceador de carga (el mismo mecanismo que crea un Service type: LoadBalancer, Módulo 2); en kind, no existe ningún proveedor de nube que asigne esa IP, y por eso queda pendiente para siempre. Esto no impide que el controlador funcione: el tráfico entra por extraPortMappings (puerto 80/443 de tu host → hostPort del Pod), un camino completamente distinto al de EXTERNAL-IP.
Analogía: el recepcionista contratado, en el piso equivocado del edificio
Retomando la analogía del edificio: contrataste al recepcionista (ingress-nginx), pero el manifiesto de contratación solo decía "cualquier piso del edificio sirve" (nodeSelector: kubernetes.io/os: linux) — y por azar, el sistema de asignación de oficinas lo mandó a un piso sin conexión directa con la calle (un worker, sin extraPortMappings). El recepcionista está trabajando perfecto, pero ningún visitante de la calle puede encontrarlo, porque la puerta principal del edificio (los puertos 80/443 de tu host) solo está conectada físicamente al piso que tú preparaste para eso (el control-plane, con la etiqueta ingress-ready). El Paso 8 corrige el contrato de contratación para exigir, explícitamente, ese piso específico — no "cualquiera", sino el que de verdad tiene la puerta conectada a la calle.
Errores comunes
Intentar agregar extraPortMappings a un clúster que ya existe, sin recrearlo (el error que esta lección previene desde el Paso 1). Qué pasa: alguien busca un comando tipo kind edit cluster o kubectl patch node para agregar el mapeo de puertos sin perder el estado actual del clúster, y no lo encuentra, porque no existe. Cómo detectarlo: si estás buscando cómo modificar extraPortMappings de un clúster ya corriendo. Cómo corregirlo: extraPortMappings es configuración del contenedor Docker del nodo, decidida en el momento de docker run (que kind create cluster hace por ti) — igual que no puedes cambiar los puertos mapeados de un contenedor Docker ya arrancado sin recrearlo, tampoco puedes hacerlo aquí. Recrear el clúster (Pasos 3-5 de esta lección) es el único camino.
No liberar los puertos 80/443 del host antes de recrear el clúster (operativo, específico de tu máquina). Qué pasa: alguien tiene un servidor web local (por ejemplo, un proxy de desarrollo, o Docker Desktop con su propio panel expuesto en esos puertos) corriendo en el puerto 80 o 443, y kind create cluster falla con un error de "address already in use" al intentar reservarlos. Cómo detectarlo: el mensaje de error de kind create cluster menciona explícitamente el puerto en conflicto. Cómo corregirlo: lsof -nP -iTCP:80 -sTCP:LISTEN (o el puerto correspondiente) identifica qué proceso lo tiene ocupado en macOS/Linux; detén ese proceso, o cambia los valores de hostPort en kind-config.yaml a puertos distintos (por ejemplo, 8080/8443) y ajusta el resto de esta guía en consecuencia.
Asumir que EXTERNAL-IP: <pending> significa que algo salió mal (de expectativa). Qué pasa: alguien ve <pending> en la columna EXTERNAL-IP del Service ingress-nginx-controller y asume que el controlador no terminó de instalarse. Cómo detectarlo: si esperas ver una IP real ahí, como verías en un clúster EKS con un balanceador de AWS real detrás. Cómo corregirlo: en kind, no existe ningún proveedor de nube que le asigne una IP externa a un Service type: LoadBalancer — ese campo se queda <pending> para siempre, sin que eso afecte el funcionamiento. El tráfico entra por extraPortMappings, no por EXTERNAL-IP; confirma el controlador con kubectl get pods -n ingress-nginx, no con esta columna.
No notar que el nodeSelector por defecto del manifiesto no coincide con ingress-ready, y no diagnosticar el Pod en el nodo equivocado (el hallazgo real de esta lección, documentado para que no te tome por sorpresa). Qué pasa: alguien instala ingress-nginx siguiendo solo el Paso 6 de esta lección, sin los Pasos 7-9, y se pregunta por qué curl no responde nada en la lección 5. Cómo detectarlo: exactamente como esta lección lo hizo — kubectl get pods -n ingress-nginx -o wide y revisa la columna NODE; si no es andes-cargo-cluster-control-plane, ese es el problema. Cómo corregirlo: los Pasos 7-9 de esta lección, en orden — confirmar el nodeSelector actual, parchearlo para exigir ingress-ready: "true", y esperar el RollingUpdate.
Ejercicios
Ejercicio 1 — Reconstruye el flujo completo de memoria. Sin volver a la lección, enumera los pasos, en orden, desde por qué hubo que recrear el clúster hasta confirmar que el controlador quedó Running en el nodo correcto.
Ver solución
- Entender por qué
extraPortMappingsexige recrear el clúster (es configuración de creación del contenedor Docker del nodo). - Escribir el
kind-config.yamlnuevo, conextraPortMappings(puertos80/443) ykubeadmConfigPatches(etiquetaingress-ready=true) en el nodocontrol-plane. kind delete cluster+kind create cluster --config kind-config.yaml.- Confirmar la etiqueta con
kubectl get nodes --show-labels. - Recargar la imagen (
kind load docker-image) y reaplicar los seis manifiestos del Módulo 3. - Instalar
ingress-nginxcon el manifiesto oficial parakind. - Descubrir, con
kubectl get pods -n ingress-nginx -o wide, que el Pod quedó en unworker. - Confirmar la causa (
nodeSelectorsiningress-ready) y corregirla conkubectl patch. - Confirmar el Pod
Runningencontrol-plane, y elService/IngressClasscreados.
Ejercicio 2 — Explica por qué solo el control-plane necesita ingress-ready. Un colega pregunta por qué no simplemente agregaste ingress-ready=true a los tres nodos, para evitar cualquier posibilidad de que el Pod aterrice en el lugar equivocado. Explica, en dos o tres frases, por qué eso no sería mejor.
Ver solución
Una respuesta razonable: "La etiqueta por sí sola no mueve el tráfico — lo que de verdad conecta un nodo con el mundo exterior es extraPortMappings, y esa configuración solo existe en el control-plane de este kind-config.yaml. Si agregara ingress-ready=true a los tres nodos, el nodeSelector seguiría aceptando cualquiera de los tres como candidato válido, y el Pod podría volver a aterrizar en un worker sin el mapeo de puertos — el mismo problema de esta lección, solo que más difícil de diagnosticar porque la etiqueta ya no delataría el error."
Ejercicio 3 — Predice qué pasaría sin el Paso 8. Si te hubieras saltado el kubectl patch del Paso 8 y hubieras seguido directo a declarar un Ingress (lección 5), ¿qué esperarías ver al hacer curl contra tu host en el puerto 80? Justifica tu respuesta con lo que sabes de extraPortMappings.
Ver solución
El curl fallaría con un error de conexión rechazada o tiempo de espera agotado — no un 404 ni un 500, sino una falla de red completa. extraPortMappings en kind-config.yaml reenvía el puerto 80 de tu máquina exclusivamente hacia el contenedor del nodo control-plane; si el proceso NGINX del controlador está escuchando en el puerto 80 de un worker distinto, ese reenvío nunca llega a él — es literalmente como tocar la puerta de un edificio que no tiene ninguna conexión con la recepción real, aunque la recepción exista y esté funcionando en otro piso.
Resumen y siguiente paso
En esta lección recreaste andes-cargo-cluster con un kind-config.yaml actualizado (extraPortMappings + la etiqueta ingress-ready en el control-plane), reconstruiste el estado exacto que dejó el Módulo 3, instalaste el manifiesto oficial de ingress-nginx para kind, y diagnosticaste y corrigiste un problema real: el nodeSelector por defecto del manifiesto no exigía la etiqueta ingress-ready, dejando el Pod del controlador en un nodo sin el mapeo de puertos correcto. ingress-nginx-controller está ahora 1/1 Running en andes-cargo-cluster-control-plane, con un IngressClass llamado nginx listo para usar.
Antes de avanzar deberías poder: explicar por qué extraPortMappings exige recrear el clúster; reconstruir de memoria por qué el Pod del controlador aterrizó en el nodo equivocado la primera vez, y cómo se corrigió; y confirmar, con kubectl get pods -n ingress-nginx -o wide, que el controlador está sano en el nodo correcto.
Siguiente lección: manos a la obra, Ingress para status-api-service. Con el recepcionista contratado y en el piso correcto, la lección 5 escribe, por fin, el libro de reglas —el objeto Ingress— y confirma con curl real, sin port-forward, que el camino completo funciona.
Recursos
- kind — Ingress — la guía oficial del proyecto
kindsobre el patrón deIngressen clústeres locales. - kind — Configuration — referencia completa de
extraPortMappingsykubeadmConfigPatches, los dos campos nuevos de esta lección. - GitHub — kubernetes/ingress-nginx: deploy/static/provider/kind — el manifiesto exacto que aplicaste en el Paso 6.
- Kubernetes — Assigning Pods to Nodes — referencia oficial de
nodeSelector, el campo que diagnosticaste y corregiste en los Pasos 7-8. - Kubernetes — Update API Objects in Place Using kubectl patch — referencia oficial de
kubectl patch --type=json, usado en el Paso 8.