Módulo 4: Networking Ingress And Networkpolicy

8. Proyecto: `status-api-service` expuesto y protegido

Descripción

Este proyecto cierra el Módulo 4 con la prueba que las siete lecciones anteriores prepararon: Ingress (lecciones 2-5) y NetworkPolicy (lecciones 6-7) funcionando juntos, verificados en la misma sesión, uno junto al otro. No hay ningún manifiesto nuevo en este proyecto — cada pieza ya existe, aplicada en su propia lección. Lo que este proyecto agrega es la vista completa: el camino que debería funcionar, funcionando; el camino que debería estar bloqueado, bloqueado; y un checklist que confirma que ninguna de las dos cosas es casualidad. Todo lo que sigue corrió contra andes-cargo-cluster.

Conexión con el módulo

Cada lección de este módulo construyó una pieza: el modelo de red (lección 2), Ingress explicado y construido (lecciones 3-5), NetworkPolicy explicado y construido (lecciones 6-7). Este proyecto es la primera vez que ves las dos mitades del módulo —"tráfico entrante" y "quién puede generarlo"— confirmadas en el mismo lugar, cerrando el arco que la lección 1 abrió: de "solo kubectl port-forward" a un servicio con una puerta HTTP permanente y un guardrail de red real.


Paso 1 — Confirma el estado completo antes de verificar nada

kubectl get all,ingress,networkpolicy,configmap,secret -n andes-cargo

Qué esperar (nombres de Pod, IPs, NodePort de hpa y AGE son tus valores variables — el resto, incluidos los nombres de cada objeto y el patrón de tres réplicas, es literal):

NAME                                          READY   STATUS    RESTARTS   AGE
pod/andes-cargo-status-api-548966dd97-bpqjq   1/1     Running   0          5m56s
pod/andes-cargo-status-api-548966dd97-jwx5r   1/1     Running   0          5m56s
pod/andes-cargo-status-api-548966dd97-ls2bc   1/1     Running   0          5m56s

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/status-api-service   ClusterIP   10.96.239.125   <none>        80/TCP    5m56s

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/andes-cargo-status-api   3/3     3            3           5m56s

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/andes-cargo-status-api-548966dd97   3         3         3       5m56s

NAME                                                             REFERENCE                           TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/andes-cargo-status-api-hpa   Deployment/andes-cargo-status-api   cpu: <unknown>/50%   2         6         3          5m56s

NAME                                           CLASS   HOSTS               ADDRESS     PORTS   AGE
ingress.networking.k8s.io/status-api-ingress   nginx   andes-cargo.local   localhost   80      4m53s

NAME                                                       POD-SELECTOR                 AGE
networkpolicy.networking.k8s.io/allow-from-ingress-nginx   app=andes-cargo-status-api   53s
networkpolicy.networking.k8s.io/default-deny-ingress       <none>                       3m24s

NAME                                      DATA   AGE
configmap/andes-cargo-status-api-config   3      5m56s
configmap/kube-root-ca.crt                1      5m57s

NAME                                    TYPE     DATA   AGE
secret/andes-cargo-status-api-secrets   Opaque   2      5m56s

HorizontalPodAutoscaler muestra cpu: <unknown>/50% porque este clúster se recreó en la lección 4 y metrics-server (Módulo 3, lección 7) todavía no se reinstaló — el HPA en sí sigue configurado y funcionando, solo sin métricas que leer todavía. Esto no afecta nada de lo que este proyecto verifica; Ingress y NetworkPolicy no dependen de metrics-server en absoluto.

Nueve tipos de objeto, un solo namespace: el Deployment/Service/HPA del Módulo 2-3, el ConfigMap/Secret del Módulo 3, y el Ingress/dos NetworkPolicy de este módulo — el estado completo y acumulativo de esta guía hasta hoy.


Paso 2 — Verifica el camino permitido: IngressService

curl -i -s --max-time 8 --resolve andes-cargo.local:80:127.0.0.1 http://andes-cargo.local/health

Qué esperar (literal, ejecutado — Date es tu valor variable):

HTTP/1.1 200 OK
Date: Fri, 14 Aug 2026 20:42:41 GMT
Content-Type: application/json
Content-Length: 51
Connection: keep-alive

{"service":"andes-cargo-status-api","status":"ok"}

200, sin ningún kubectl port-forward, exactamente como confirmó la lección 5 — y ahora, además, con default-deny-ingress y allow-from-ingress-nginx activas al mismo tiempo. Este resultado es la prueba de que las dos mitades del módulo conviven sin conflicto: la puerta HTTP sigue abierta para el tráfico legítimo, aunque el guardrail de red esté activo.

Confirma también, una vez más y con la misma honestidad de siempre, el límite que este módulo nunca prometió resolver:

curl -i -s --max-time 30 --resolve andes-cargo.local:80:127.0.0.1 http://andes-cargo.local/shipments/4471

Qué esperar (literal, ejecutado — sin cambios frente a la lección 5; sigue sin ser un error de este proyecto):

HTTP/1.1 500 INTERNAL SERVER ERROR
Date: Fri, 14 Aug 2026 20:43:24 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 265
Connection: keep-alive

<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Paso 3 — Verifica el camino bloqueado: cualquier otro Pod → Service directo

kubectl exec -n andes-cargo test-client -- curl -s -o /dev/null -w "HTTP_STATUS:%{http_code}\n" --max-time 8 http://status-api-service.andes-cargo.svc.cluster.local/health

Qué esperar (literal, ejecutado — el mismo test-client de la lección 7, todavía corriendo):

HTTP_STATUS:000
command terminated with exit code 28

Bloqueado — de verdad, con el mismo mecanismo que investigó a fondo la lección 7. Ningún Pod que no venga del namespace ingress-nginx puede alcanzar status-api-service, sin importar que esté en el mismo namespace que Andes Cargo, sin importar que use el nombre DNS correcto, sin importar nada — salvo la única excepción explícita que declaraste.


El resumen visual: los dos caminos, uno junto al otro

        EL ESTADO FINAL DEL MÓDULO 4, VERIFICADO

  Cliente externo (curl, tu máquina)
          │
          │  --resolve andes-cargo.local:80:127.0.0.1
          ▼
   ┌─────────────────┐
   │  Ingress           │  status-api-ingress
   │  (extraPortMappings │  host: andes-cargo.local
   │   → control-plane)   │
   └────────┬────────┘
            │
            ▼
   ┌─────────────────┐
   │  ingress-nginx     │  namespace: ingress-nginx
   │  controller         │
   └────────┬────────┘
            │  permitido por allow-from-ingress-nginx
            ▼
   ┌─────────────────┐         test-client
   │  status-api-       │◄── X ── (namespace: andes-cargo,
   │  service            │   BLOQUEADO   sin pasar por Ingress)
   │  (andes-cargo-      │  por default-deny-ingress
   │   status-api Pods)  │
   └─────────────────┘

   /health           → 200 OK  (ambos caminos verificados)
   /shipments/<id>    → 500    (backend DynamoDB fuera del alcance $0 —
                                 representativo en toda esta guía)

Checklist final del Módulo 4

Tres verificaciones, todas confirmadas por este proyecto, ninguna hipotética:

  • ingress-nginx instalado y sano, corriendo en andes-cargo-cluster-control-plane (el único nodo con extraPortMappings), confirmado con kubectl get pods -n ingress-nginx.
  • status-api-ingress enruta tráfico real, sin kubectl port-forward, confirmado con curl contra http://andes-cargo.local/health respondiendo 200.
  • default-deny-ingress + allow-from-ingress-nginx bloquean y permiten exactamente lo esperado, verificado en ambas direcciones: el camino de Ingress funciona, cualquier otro Pod queda bloqueado — con la corrección honesta de la lección 7 sobre kindnetd documentada y citada.

Nota para el Módulo 5: qué hereda GitOps de este estado

El Módulo 5 va a tomar namespace.yaml, deployment.yaml, service.yaml, configmap.yaml, secret.yaml, hpa.yaml, ingress.yaml y las dos NetworkPolicy de este módulo, y entregárselos a ArgoCD como la fuente de verdad de un repositorio Git — el mismo patrón acumulativo que ya viste en cada módulo anterior, ahora con ocho manifiestos en vez de seis. Dos cosas valen la pena anotar antes de llegar ahí:

  • Un backend real de DynamoDB (LocalStack u otra opción), si algún equipo lo agregara, no chocaría con default-deny-ingress. La política de esta lección tiene metadata.namespace: andes-cargo — solo afecta a Pods de ese namespace. LocalStack viviría en su propio namespace (localstack), completamente fuera del alcance de esta NetworkPolicy. Esta guía no instala esa pieza en ningún módulo — la capa de datos queda fuera de su alcance $0 — pero vale la pena dejar anotado que la arquitectura de red de este módulo no le pondría ningún obstáculo.
  • andes-cargo-status-api podría alcanzar ese backend sin ningún cambio a las políticas de este módulo. Ambas NetworkPolicy de este módulo declaran únicamente policyTypes: [Ingress] —confirmado explícitamente en el kubectl describe de la lección 7 (Not affecting egress traffic)—, así que la conexión saliente de andes-cargo-status-api hacia cualquier servicio externo, si existiera, no encontraría ninguna restricción nueva de este módulo.

Limpieza: retira el Pod de prueba

test-client cumplió su propósito (lecciones 7 y 8) y no es parte del estado permanente del laboratorio — a diferencia de andes-cargo-status-api, ingress-nginx, o las dos NetworkPolicy, que sí siguen siendo necesarios para el resto de la guía:

kubectl delete pod test-client -n andes-cargo

Qué esperar:

pod "test-client" deleted

Errores comunes

Dar por cerrado el módulo sin correr las verificaciones de este proyecto, confiando en que "cada lección ya funcionó por separado" (de flujo, el mismo patrón que ya viste en el Módulo 1). Qué pasa: alguien asume que, porque las lecciones 5 y 7 ya mostraron resultados correctos por separado, no hace falta confirmar que ambas piezas siguen funcionando juntas. Cómo detectarlo: si no puedes pegar, ahora mismo, la salida real de los Pasos 2 y 3 de este proyecto. Cómo corregirlo: la razón de este proyecto es exactamente esa — confirmar que Ingress y NetworkPolicy, aplicados en lecciones distintas, no interfieren entre sí. No es redundante repetir la verificación con ambos activos al mismo tiempo.

Borrar default-deny-ingress o allow-from-ingress-nginx "para simplificar" antes de seguir con el Módulo 5 (operativo, con consecuencias reales para la continuidad de la guía). Qué pasa: alguien, pensando que las NetworkPolicy ya cumplieron su propósito pedagógico en este módulo, las borra antes de avanzar. Cómo detectarlo: kubectl get networkpolicy -n andes-cargo no muestra ninguno de los dos objetos. Cómo corregirlo: ambas políticas son parte del estado permanente del laboratorio de aquí en adelante — el Módulo 5 las hereda tal cual, como parte del repositorio Git que ArgoCD sincroniza. Si las borraste, vuelve a aplicar networkpolicy-default-deny.yaml y networkpolicy-allow-ingress-nginx.yaml (lección 7) antes de continuar.

No notar que el HPA muestra <unknown> después de recrear el clúster, y asumir que algo se rompió (de continuidad, ya explicado en el Paso 1 pero fácil de alarmar si lo ves sin contexto). Qué pasa: alguien ve cpu: <unknown>/50% en kubectl get hpa y se preocupa de que el autoscaling dejó de funcionar. Cómo detectarlo: si comparas contra el resultado del Módulo 3, donde sí aparecía un porcentaje real. Cómo corregirlo: la recreación del clúster en la lección 4 de este módulo no reinstaló metrics-server —fuera del alcance de este módulo—; el HPA en sí sigue configurado correctamente y va a volver a mostrar métricas reales en cuanto metrics-server esté disponible de nuevo. Ningún módulo posterior de esta guía depende de esto.


Ejercicios

Ejercicio 1 — Reconstruye el diagrama del estado final de memoria. Sin volver a la lección, dibuja (en texto, con flechas) el camino completo desde curl en tu máquina hasta un Pod de andes-cargo-status-api, marcando en qué punto interviene cada pieza de este módulo (Ingress, ingress-nginx, NetworkPolicy).

Ver solución

curl (tu máquina) → extraPortMappings (kind-config.yaml, hacia control-plane) → ingress-nginx (lee la regla de Ingress) → status-api-service → NetworkPolicy evalúa el origen (namespace ingress-nginx, permitido por allow-from-ingress-nginx) → Pod de andes-cargo-status-api. Un Pod que intente el mismo camino saltándose ingress-nginx llega hasta el paso de NetworkPolicy, y ahí se detiene — default-deny-ingress bloquea cualquier origen que no coincida con la excepción explícita.

Ejercicio 2 — Explica por qué un backend de datos externo no necesitaría ningún cambio a estas políticas. Un colega pregunta si, en caso de conectar andes-cargo-status-api a un backend real de DynamoDB (vía LocalStack o una cuenta AWS real — algo que esta guía no hace, pero que un equipo real podría agregar), habría que modificar default-deny-ingress o allow-from-ingress-nginx. Explícale, en dos o tres frases, por qué no.

Ver solución

Una explicación razonable: "Ninguna de las dos políticas toca el tráfico saliente — ambas declaran solo policyTypes: [Ingress], confirmado explícitamente con kubectl describe en la lección 7 (Not affecting egress traffic). La conexión hacia un backend externo es una conexión que andes-cargo-status-api inicia hacia afuera, no una que recibe — ese tipo de tráfico está completamente fuera del alcance de lo que este módulo restringió."

Ejercicio 3 — Diseña la verificación para un tercer camino. Si Andes Cargo agregara, en el futuro, un tercer namespace (monitoring, con una herramienta de observabilidad que necesita hacerle curl a /health para verificar que el servicio está vivo), ¿qué política nueva necesitarías, y qué verificación —siguiendo el patrón de este proyecto— confirmaría que funciona sin abrir la puerta a nada más?

Ver solución

Necesitarías una tercera NetworkPolicy (o una regla adicional dentro de allow-from-ingress-nginx, aunque separarla en un objeto propio es más claro) con podSelector: app: andes-cargo-status-api y una regla from con namespaceSelector: kubernetes.io/metadata.name: monitoring, restringida al puerto 8080 — el mismo patrón exacto que allow-from-ingress-nginx, con el namespace de origen cambiado. La verificación seguiría el mismo patrón de dos pasos de este proyecto: un Pod de prueba dentro de monitoring confirmando 200 contra status-api-service, y test-client (o cualquier Pod fuera de ingress-nginx/monitoring) confirmando que sigue bloqueado — la prueba de que la excepción nueva no abrió la puerta más de lo necesario.


Resumen y siguiente paso

Este proyecto confirmó, con evidencia real y ambas mitades activas al mismo tiempo, el estado completo del Módulo 4: Ingress enrutando tráfico externo real hacia status-api-service (200 en /health, sin ningún kubectl port-forward), y NetworkPolicy bloqueando por completo cualquier camino que se salte esa puerta (test-client, bloqueado con evidencia literal), mientras el camino legítimo sigue funcionando sin fricción. andes-cargo-status-api es, desde hoy, un servicio con una puerta HTTP permanente y un guardrail de red real — las dos piezas que "3 réplicas balanceadas" (Módulo 2) y "configuración/salud/autoscaling" (Módulo 3) todavía no cubrían.

Antes de avanzar deberías poder: ejecutar el checklist completo de memoria; explicar por qué un backend de datos externo, si algún equipo lo agregara, no necesitaría ningún cambio a las NetworkPolicy de este módulo; y describir, con el diagrama de esta lección, el camino completo de una solicitud HTTP real desde tu máquina hasta un Pod, incluidas las dos piezas de seguridad de red que la protegen.

Siguiente módulo: GitOps con ArgoCD. El Módulo 5 toma exactamente los ocho manifiestos que existen hoy —namespace.yaml, deployment.yaml, service.yaml, configmap.yaml, secret.yaml, hpa.yaml, ingress.yaml, y las dos NetworkPolicy— y los convierte en la fuente de verdad de un repositorio Git real, servido por Gitea dentro del propio clúster, sincronizado por un operador que hace watch en vez de esperar a que alguien corra kubectl apply a mano.

Recursos

  1. kubernetes-and-eks-in-production-guide (NIEVA), Módulo 4, lecciones 4, 5 y 7 — el origen exacto de cada manifiesto y cada verificación que este proyecto reutiliza sin cambios.
  2. Kubernetes — Debug Services — guía oficial de diagnóstico de conectividad, útil como referencia general para cualquier problema de red que encuentres más allá del alcance de esta guía.
  3. Kubernetes — Ingress y Kubernetes — Network Policies — las dos referencias centrales de todo este módulo, ambas confirmadas con evidencia real en este proyecto.