En este tema pasaremos de la teoría a la práctica: desplegaremos una aplicación basada en una imagen Docker dentro de un clúster de Kubernetes. Aprenderemos a escribir los manifiestos YAML de un Deployment y un Service explicados línea a línea, a aplicarlos y diagnosticarlos con kubectl, a exponer servicios, a escalar réplicas y a realizar actualizaciones progresivas con su correspondiente rollback. Dominar este flujo es el núcleo del trabajo diario con Kubernetes.

Objetivos de Aprendizaje

  • Escribir un manifiesto de Deployment y entender cada una de sus líneas.
  • Escribir un manifiesto de Service y comprender los tipos ClusterIP y NodePort.
  • Usar kubectl apply, get, describe y logs para operar y diagnosticar.
  • Escalar el número de réplicas de un Deployment.
  • Realizar una actualización progresiva (rolling update) y deshacerla con un rollback.

El Manifiesto de Deployment

Un Deployment describe el estado deseado de tu aplicación: qué imagen ejecutar, cuántas réplicas y cómo etiquetarlas. Veamos un manifiesto completo y lo analizaremos línea a línea.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: miapp-deployment
  labels:
    app: miapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: miapp
  template:
    metadata:
      labels:
        app: miapp
    spec:
      containers:
        - name: miapp
          image: miorg/miapp:1.0.0
          ports:
            - containerPort: 3000

Explicación línea a línea:

  • apiVersion: apps/v1: versión de la API de Kubernetes para objetos de tipo Deployment.
  • kind: Deployment: indica el tipo de objeto que estamos definiendo.
  • metadata.name: nombre único del Deployment dentro del namespace.
  • metadata.labels: etiquetas (clave-valor) para identificar y agrupar este objeto.
  • spec: especificación del estado deseado.
    • replicas: 3: número de Pods idénticos que se deben mantener en ejecución.
    • selector.matchLabels: indica qué Pods gestiona este Deployment; deben tener la etiqueta app: miapp.
    • template: plantilla con la que se crean los Pods.
      • metadata.labels: etiquetas que se asignan a cada Pod (deben coincidir con el selector).
      • spec.containers: lista de contenedores del Pod.
        • name: nombre del contenedor.
        • image: miorg/miapp:1.0.0: imagen Docker a ejecutar, con una etiqueta concreta (no latest).
        • ports.containerPort: 3000: puerto en el que escucha la aplicación dentro del contenedor.

El Manifiesto de Service

El Service da una dirección estable a los Pods, que de por sí son efímeros. Este ejemplo crea un Service que enruta el tráfico a los Pods de nuestro Deployment.

apiVersion: v1
kind: Service
metadata:
  name: miapp-service
spec:
  type: ClusterIP
  selector:
    app: miapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

Explicación línea a línea:

  • apiVersion: v1: los Services pertenecen a la API principal (v1).
  • kind: Service: tipo de objeto.
  • metadata.name: nombre del Service; otros Pods podrán contactarlo por este nombre.
  • spec.type: ClusterIP: tipo de Service (lo veremos a continuación); ClusterIP es el valor por defecto.
  • spec.selector.app: miapp: selecciona los Pods con la etiqueta app: miapp, es decir, los de nuestro Deployment.
  • spec.ports:
    • protocol: TCP: protocolo de transporte.
    • port: 80: puerto por el que el Service recibe tráfico.
    • targetPort: 3000: puerto del contenedor al que se reenvía ese tráfico.

Aplicar y Diagnosticar con kubectl

Guarda ambos manifiestos (por ejemplo, en deployment.yaml y service.yaml) y aplícalos:

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

kubectl apply crea los objetos si no existen o los actualiza si ya existían. Para inspeccionar el estado:

# Ver el Deployment y cuántas réplicas están listas
kubectl get deployments

# Ver los Pods y su estado
kubectl get pods

# Ver los Services
kubectl get services

# Detalles y eventos de un objeto (muy útil para diagnosticar)
kubectl describe deployment miapp-deployment

# Logs de un Pod concreto
kubectl logs miapp-deployment-xxxxx

Consejos de diagnóstico:

  • Si un Pod no arranca, kubectl describe pod ... muestra eventos como errores al descargar la imagen (ImagePullBackOff) o fallos de arranque (CrashLoopBackOff).
  • kubectl logs muestra la salida de la aplicación, clave para encontrar errores internos.

Exposición de Servicios: ClusterIP vs NodePort

El tipo de Service determina desde dónde es accesible la aplicación:

Tipo Accesibilidad Uso típico
ClusterIP Solo dentro del clúster Comunicación entre servicios internos.
NodePort Desde fuera, en IP-del-nodo:puerto Acceso externo sencillo o pruebas.
LoadBalancer A través de un balanceador del proveedor cloud Producción en la nube.

Para exponer la aplicación al exterior con NodePort, cambia el tipo del Service:

apiVersion: v1
kind: Service
metadata:
  name: miapp-service
spec:
  type: NodePort
  selector:
    app: miapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
      nodePort: 30080

Explicación de los cambios:

  • type: NodePort: hace que el Service sea accesible desde fuera del clúster.
  • nodePort: 30080: puerto (en el rango 30000-32767) que se abre en todos los nodos. Podrás acceder a la aplicación en http://IP-del-nodo:30080.

Escalado de Réplicas

Puedes ajustar el número de réplicas de dos formas. La forma imperativa:

kubectl scale deployment miapp-deployment --replicas=5

Este comando lleva el Deployment a 5 réplicas inmediatamente. La forma declarativa (recomendada) es editar el campo replicas en el manifiesto y volver a aplicarlo:

kubectl apply -f deployment.yaml

La forma declarativa mantiene el manifiesto como única fuente de verdad, lo que facilita el control de versiones y la reproducibilidad.

Rolling Update y Rollback

Cuando publicas una nueva versión de la imagen, Kubernetes actualiza los Pods de forma progresiva por defecto, sin cortar el servicio. Para desencadenar la actualización, cambia la etiqueta de la imagen en el manifiesto y aplica:

# Editar en deployment.yaml: image: miorg/miapp:1.1.0
kubectl apply -f deployment.yaml

También puedes hacerlo de forma imperativa:

kubectl set image deployment/miapp-deployment miapp=miorg/miapp:1.1.0

Explicación:

  • kubectl set image: cambia la imagen de un contenedor dentro del Deployment.
  • deployment/miapp-deployment: el Deployment a modificar.
  • miapp=miorg/miapp:1.1.0: el contenedor miapp usará la nueva imagen.

Para seguir el progreso de la actualización:

kubectl rollout status deployment/miapp-deployment

Si la nueva versión falla, puedes deshacer el cambio y volver a la anterior:

kubectl rollout undo deployment/miapp-deployment

Este comando realiza un rollback al ReplicaSet anterior, restaurando la versión que funcionaba. Para ver el historial de despliegues:

kubectl rollout history deployment/miapp-deployment

Errores Comunes y Consejos

  • El selector no coincide con las etiquetas del Pod: si selector.matchLabels no coincide con template.metadata.labels, el Deployment no gestionará sus Pods. Mantenlos sincronizados.
  • Usar latest: impide rollbacks fiables, porque la etiqueta no identifica una versión. Usa etiquetas concretas como 1.1.0.
  • ImagePullBackOff: la imagen no existe o el registro requiere autenticación. Revisa el nombre, la etiqueta y los secretos de acceso.
  • Confundir port con targetPort: port es el puerto del Service; targetPort es el del contenedor. Deben corresponderse correctamente.
  • Esperar acceso externo con ClusterIP: ClusterIP solo es accesible dentro del clúster; usa NodePort o LoadBalancer para acceso externo.
  • Mezclar comandos imperativos y declarativos: si escalas con kubectl scale pero luego aplicas un manifiesto con otro valor, sobrescribirás el cambio. Elige una estrategia y mantenla.

Ejercicios

Ejercicio 1

Escribe un manifiesto de Deployment para una aplicación con la imagen miorg/web:2.0.0, 4 réplicas, etiqueta app: web y que exponga el puerto 8080 del contenedor.

Ejercicio 2

Escribe un manifiesto de Service de tipo NodePort que enrute el tráfico al Deployment del ejercicio anterior, recibiendo en el puerto 80 y reenviando al 8080, abriendo el puerto 30090 en los nodos.

Ejercicio 3

Indica los comandos para: (a) aplicar ambos manifiestos, (b) escalar el Deployment a 6 réplicas de forma imperativa, (c) actualizar la imagen a miorg/web:2.1.0 y (d) deshacer la actualización si falla.

Soluciones

Solución al Ejercicio 1:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
  labels:
    app: web
spec:
  replicas: 4
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: miorg/web:2.0.0
          ports:
            - containerPort: 8080

Solución al Ejercicio 2:

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30090

Solución al Ejercicio 3:

# (a) Aplicar ambos manifiestos
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# (b) Escalar a 6 réplicas
kubectl scale deployment web-deployment --replicas=6

# (c) Actualizar la imagen
kubectl set image deployment/web-deployment web=miorg/web:2.1.0

# (d) Deshacer la actualización
kubectl rollout undo deployment/web-deployment

Conclusión

En este tema hemos desplegado una aplicación Docker en Kubernetes de principio a fin: hemos escrito y comprendido los manifiestos de Deployment y Service, los hemos aplicado y diagnosticado con kubectl, expuesto el servicio con ClusterIP y NodePort, escalado las réplicas y realizado actualizaciones progresivas con su rollback. Con esta base operativa, el siguiente paso es profundizar en cómo crecer de forma eficiente y repartir el tráfico. En el próximo tema, Escalado y Balanceo de Carga, exploraremos las estrategias de escalado y balanceo tanto en Swarm como en Kubernetes.

© Copyright 2026. Todos los derechos reservados