Contenido

Kubernetes: Deployments, Services, Gateway API y cómo desplegar tu primera app

Actualizado en marzo 2026: Este artículo ha sido revisado para reflejar los cambios en Kubernetes v1.35. Se reemplazó Ingress por Gateway API como estándar recomendado para gestionar tráfico externo.

Pre-requisitos

Antes de arrancar, asegúrate de tener todo lo del primer capítulo listo:

  • Docker (u OrbStack en macOS) corriendo.
  • kubectl instalado y configurado.
  • Kind instalado con un cluster activo.

Si aún no lo tienes, revisa el post anterior donde te guío paso a paso para montar tu cluster local.


Introducción

En el post anterior montamos nuestro primer cluster local con Kind y entendimos qué es Kubernetes y por qué existe. Ahora viene lo divertido: meter cosas dentro del cluster.

En este capítulo vamos a conocer los recursos fundamentales de Kubernetes, desplegar nuestra primera app y aprender a exponerla al mundo exterior. Piénsalo como pasar de saber qué es una cocina a cocinar tu primer plato.


Los recursos fundamentales de Kubernetes

Antes de tocar el teclado, necesitas conocer a los protagonistas. Kubernetes trabaja con recursos (objetos) que defines y él se encarga de mantener. Estos son los más importantes para empezar:

Recursos fundamentales de Kubernetes

Flujo de tráfico: Gateway → HTTPRoute → Service → Deployment → Pods (con HPA escalando automáticamente)

Pod

El Pod es la unidad más pequeña en Kubernetes. No es un contenedor, es una envoltura que puede contener uno o más contenedores que comparten red y almacenamiento.

Anatomía de un Pod

Un Pod puede tener uno o más contenedores que comparten IP y volúmenes

Piénsalo así: si un contenedor Docker es una persona, un Pod es un departamento donde viven una o más personas que comparten dirección (IP) y servicios (almacenamiento).

Características clave:

  • Cada Pod tiene su propia IP dentro del cluster.
  • Los contenedores dentro del mismo Pod se comunican por localhost.
  • Si un Pod muere, no resucita solo — necesitas algo que lo gestione (como un Deployment).
  • Un Pod es efímero: nace, hace su trabajo y puede morir en cualquier momento.

Deployment

El Deployment es el recurso que realmente usarás el 90% del tiempo. Es el jefe de los Pods: le dices cuántas réplicas quieres y él se encarga de crearlas, mantenerlas vivas y actualizarlas.

¿Qué hace un Deployment?

  • Crea y gestiona Pods automáticamente a través de un ReplicaSet.
  • Rolling updates: actualiza tus Pods sin downtime, reemplazándolos de a poco.
  • Rollback: si algo sale mal, puedes volver a la versión anterior con un comando.
  • Escalado: subes o bajas el número de réplicas según necesites.

Service (svc)

Los Pods tienen IPs que cambian todo el tiempo (recuerda, son efímeros). ¿Cómo te conectas a algo que cambia de dirección constantemente? Ahí entra el Service.

Un Service es un punto de acceso estable que dirige el tráfico a un grupo de Pods. Piénsalo como la recepción de un hotel: los huéspedes (Pods) cambian de habitación, pero la recepción (Service) siempre está en el mismo lugar.

Tipos principales:

  • ClusterIP (por defecto): accesible solo dentro del cluster.
  • NodePort: expone un puerto en cada nodo del cluster.
  • LoadBalancer: crea un balanceador de carga externo (en cloud providers).

Gateway API (el reemplazo de Ingress)

Si has visto tutoriales más antiguos, probablemente mencionan Ingress como la forma de exponer apps al exterior. Ingress sigue funcionando, pero está congelado — no recibirá nuevas features. Además, el popular controlador ingress-nginx está siendo retirado (soporte hasta marzo 2026).

La alternativa moderna es Gateway API, el nuevo estándar oficial para gestionar tráfico HTTP/HTTPS en Kubernetes. Piénsalo como Ingress 2.0 pero mucho mejor diseñado.

¿Por qué Gateway API es mejor?

  • Separación de roles: el equipo de infra gestiona el Gateway, los devs configuran sus HTTPRoutes. Cada equipo toca solo lo que le corresponde.
  • Más expresivo: soporta traffic splitting, header matching, redirects y más de forma nativa (sin annotations hacky).
  • Multi-protocolo: no solo HTTP. Soporta TCP, UDP y gRPC de forma nativa.
  • Portable: funciona igual con NGINX Gateway Fabric, Envoy Gateway, Traefik, Istio, etc.

Los recursos principales de Gateway API son:

  • GatewayClass: define qué implementación usar (como el “driver” — NGINX, Envoy, etc.).
  • Gateway: el punto de entrada que escucha en puertos/hostnames específicos.
  • HTTPRoute: las reglas de enrutamiento que conectan el Gateway con tus Services.

Nota: Si estás migrando desde Ingress, existe la herramienta ingress2gateway que convierte tus manifiestos Ingress a Gateway API automáticamente.

HPA (Horizontal Pod Autoscaler)

El HPA es el recurso que escala automáticamente tus Pods basándose en métricas como CPU o memoria. Si tu app está recibiendo mucho tráfico y los Pods se están ahogando, el HPA crea más réplicas automáticamente. Cuando el tráfico baja, las reduce.

Spoiler: El HPA merece su propio capítulo completo, así que aquí solo veremos lo básico. En el siguiente post lo exploraremos a fondo con ejemplos prácticos de autoescalado basado en CPU, memoria y métricas custom.


Desplegando tu primer Pod con comando

Vamos a lo práctico. Asegúrate de tener tu cluster de Kind corriendo (si seguiste el post anterior, ya lo tienes). Lo primero: lanzar un Pod con un solo comando.

kubectl run mi-nginx --image=nginx:latest --port=80

Esto crea un Pod llamado mi-nginx usando la imagen de NGINX y exponiendo el puerto 80. Así de simple.

Verifica que está corriendo:

kubectl get pods
NAME       READY   STATUS    RESTARTS   AGE
mi-nginx   1/1     Running   0          30s

Para ver más detalles:

kubectl describe pod mi-nginx

Y cuando ya no lo necesites:

kubectl delete pod mi-nginx

Importante: Un Pod creado así con kubectl run es un Pod “suelto”. Si se muere, nadie lo levanta de nuevo. Para producción siempre usarás Deployments.


Desplegando con un Deployment (comando)

Ahora vamos con el Deployment, que es lo que realmente usarás. Creamos un Deployment con un comando:

kubectl create deployment mi-app --image=nginx:latest --replicas=2

Esto crea:

  • Un Deployment llamado mi-app.
  • Un ReplicaSet (gestionado automáticamente).
  • 2 Pods corriendo la imagen de NGINX.

Verifica:

kubectl get deployments
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
mi-app   2/2     2            2           15s
kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
mi-app-5d9b7f6b4-abc12   1/1     Running   0          20s
mi-app-5d9b7f6b4-def34   1/1     Running   0          20s

Nota cómo los Pods tienen nombres generados automáticamente. El Deployment se encarga de todo.


¿Qué es un Manifiesto YAML?

Hasta ahora usamos comandos imperativos (kubectl run, kubectl create). Esto está bien para probar, pero en el mundo real usamos el enfoque declarativo: escribir un archivo YAML que describe exactamente cómo quieres que se vea tu recurso.

Estructura de un manifiesto YAML

Un manifiesto YAML define el estado deseado de tu recurso en Kubernetes

¿Por qué YAML y no comandos?

  • Versionable: lo metes en Git y tienes historial de cambios.
  • Reproducible: cualquier persona puede aplicar el mismo archivo y obtener el mismo resultado.
  • Declarativo: le dices a K8s “quiero esto” y él se encarga de llegar ahí.
  • Documentación viva: el YAML es la fuente de verdad de tu infraestructura.

Todo manifiesto de Kubernetes tiene 4 campos obligatorios:

apiVersion: # Versión del API (apps/v1, v1, networking.k8s.io/v1)
kind:       # Tipo de recurso (Deployment, Service, Ingress)
metadata:   # Nombre, labels, namespace
spec:       # La especificación del recurso (aquí va la magia)

Definiendo nuestro Deployment con YAML

Vamos a crear un Deployment completo en YAML. Crea un archivo llamado deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mi-app
  labels:
    app: mi-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mi-app
  template:
    metadata:
      labels:
        app: mi-app
    spec:
      containers:
        - name: mi-app
          image: nginx:latest
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "100m"

Desglosemos lo importante:

CampoQué hace
replicas: 2Queremos 2 Pods corriendo
selector.matchLabelsCómo el Deployment encuentra sus Pods (por labels)
templateLa plantilla que usa para crear cada Pod
containersLista de contenedores dentro de cada Pod
resourcesLímites de CPU y memoria (buena práctica siempre)

Aplícalo:

kubectl apply -f deployment.yaml
deployment.apps/mi-app created

Verifica:

kubectl get deployment mi-app
kubectl get pods -l app=mi-app

Exponiendo la app con un Service

Nuestros Pods ya corren, pero nadie puede acceder a ellos. Vamos a crear un Service para darles un punto de acceso estable. Crea service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: mi-app-svc
spec:
  selector:
    app: mi-app  # Selecciona los Pods con label app=mi-app
  ports:
    - protocol: TCP
      port: 80        # Puerto del Service
      targetPort: 80   # Puerto del contenedor
  type: ClusterIP

Aplícalo:

kubectl apply -f service.yaml

Verifica:

kubectl get svc mi-app-svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
mi-app-svc   ClusterIP   10.96.45.123   <none>        80/TCP    10s

Ahora cualquier Pod dentro del cluster puede acceder a tu app usando mi-app-svc:80 o el DNS completo mi-app-svc.default.svc.cluster.local.


Exponiendo al exterior con Gateway API

Para que el tráfico externo llegue a tu app, usaremos Gateway API — el estándar moderno de Kubernetes para gestionar tráfico entrante.

Instalando Gateway API en Kind

Primero, instala los CRDs (Custom Resource Definitions) de Gateway API:

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml

Luego, necesitas un Gateway Controller. Vamos a usar Envoy Gateway por su simplicidad:

kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.3.0/install.yaml

Espera a que esté listo:

kubectl wait --namespace envoy-gateway-system \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/name=envoy-gateway \
  --timeout=90s

Creando el Gateway y la HTTPRoute

Primero, crea el Gateway (el punto de entrada). Crea gateway.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: mi-gateway
spec:
  gatewayClassName: eg  # Envoy Gateway
  listeners:
    - name: http
      protocol: HTTP
      port: 80

Luego, crea la HTTPRoute que conecta el Gateway con tu Service. Crea httproute.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: mi-app-route
spec:
  parentRefs:
    - name: mi-gateway
  hostnames:
    - "mi-app.local"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: mi-app-svc
          port: 80

Aplica ambos:

kubectl apply -f gateway.yaml
kubectl apply -f httproute.yaml

Verifica:

kubectl get gateway
NAME         CLASS   ADDRESS        PROGRAMMED   AGE
mi-gateway   eg      172.18.0.200   True         30s
kubectl get httproute
NAME           HOSTNAMES            PARENTREFS       AGE
mi-app-route   ["mi-app.local"]     ["mi-gateway"]   15s

Nota cómo la separación es más limpia: el Gateway define dónde escuchar y la HTTPRoute define cómo enrutar. En un equipo real, el equipo de infra crearía el Gateway y cada equipo de desarrollo configuraría sus propias HTTPRoutes.

Tip local: Para probar, agrega 127.0.0.1 mi-app.local a tu archivo /etc/hosts y podrás acceder desde el navegador.


Escalando réplicas en el Deployment

¿Tu app necesita más músculo? Escalar es ridículamente fácil. Desde la línea de comandos:

kubectl scale deployment mi-app --replicas=5

Verifica:

kubectl get pods -l app=mi-app
NAME                      READY   STATUS    RESTARTS   AGE
mi-app-5d9b7f6b4-abc12   1/1     Running   0          10m
mi-app-5d9b7f6b4-def34   1/1     Running   0          10m
mi-app-5d9b7f6b4-ghi56   1/1     Running   0          5s
mi-app-5d9b7f6b4-jkl78   1/1     Running   0          5s
mi-app-5d9b7f6b4-mno90   1/1     Running   0          5s

O si prefieres el enfoque declarativo, simplemente cambia replicas: 5 en tu deployment.yaml y vuelve a aplicar:

kubectl apply -f deployment.yaml

Para bajar las réplicas, el mismo proceso pero con un número menor. Kubernetes eliminará los Pods sobrantes de forma ordenada.


Autoescalado con HPA (vista previa)

Escalar manualmente está bien, pero ¿qué pasa a las 3 AM cuando tu app se vuelve viral? No vas a estar despierto para ejecutar kubectl scale. Para eso existe el Horizontal Pod Autoscaler (HPA).

El HPA monitorea métricas (como uso de CPU) y ajusta automáticamente el número de réplicas:

kubectl autoscale deployment mi-app --min=2 --max=10 --cpu-percent=70

Esto le dice a Kubernetes: “mantén entre 2 y 10 réplicas de mi-app, y si el uso de CPU pasa del 70%, escala hacia arriba”.

Verifica el HPA:

kubectl get hpa
NAME     REFERENCE           TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
mi-app   Deployment/mi-app   10%/70%   2         10        2          30s

Requisito: Para que el HPA funcione, necesitas tener el Metrics Server instalado en tu cluster y tus Pods deben tener resources.requests definidos (como hicimos en nuestro YAML más arriba).

En el siguiente capítulo profundizamos en el HPA: configuración con YAML, escalado por memoria, métricas custom con Prometheus, behavior policies y una intro al VPA.


Port-forward: probando tu Pod en local

Hay una forma rápida y directa de probar un Pod o Service sin necesidad de Ingress: port-forward. Esto crea un túnel desde tu máquina local al cluster.

kubectl port-forward

Port-forward crea un túnel directo desde tu máquina al Pod dentro del cluster

Port-forward a un Pod

kubectl port-forward pod/mi-app-5d9b7f6b4-abc12 8080:80

Esto redirige localhost:8080 de tu máquina al puerto 80 del Pod. Abre tu navegador en http://localhost:8080 y verás NGINX.

Port-forward a un Service

Más práctico, porque no necesitas saber el nombre exacto del Pod:

kubectl port-forward svc/mi-app-svc 8080:80

Esto es perfecto para desarrollo y debugging. El terminal queda ocupado mientras el port-forward está activo. Usa Ctrl+C para detenerlo.

Nota: Port-forward es solo para desarrollo y pruebas. En producción usarás Services tipo LoadBalancer o Gateway API para exponer tus apps.


Referencias oficiales

Aquí tienes los enlaces a la documentación oficial de Kubernetes para cada recurso que cubrimos. Siempre es buena idea tener estos a mano:


Resumen

Hoy recorrimos bastante terreno. Vamos a recapitular:

  • Pod: la unidad mínima de Kubernetes, una envoltura para contenedores.
  • Deployment: el gestor de Pods que se encarga de réplicas, rolling updates y rollbacks.
  • Service: el punto de acceso estable para llegar a tus Pods.
  • Gateway API: el estándar moderno para gestionar tráfico HTTP/HTTPS desde el exterior (reemplaza a Ingress).
  • HPA: el autoescalador que ajusta réplicas según métricas (más en el próximo capítulo).
  • Aprendimos a desplegar con comandos imperativos y con manifiestos YAML (declarativos).
  • Usamos port-forward para probar nuestra app localmente.

En el próximo capítulo nos metemos de lleno con el HPA: configuración avanzada, escalado por CPU, memoria, métricas custom con Prometheus, behavior policies y una introducción al VPA.


¿Te gustó este artículo? Compártelo con alguien que esté aprendiendo Kubernetes. Y si tienes dudas, ¡déjame un comentario!