Kubernetes · Capítulo 2

Recursos de Kubernetes

Pods, ReplicaSets y Deployments, cómo se actualizan y revierten, el enfoque declarativo con kubectl apply y la organización con Namespaces.

En el capítulo anterior vimos la arquitectura del clúster. Ahora toca lo que se despliega en él: los recursos (u objetos) de Kubernetes. Recorreremos la cadena que usarás a diario, Pod → ReplicaSet → Deployment, cómo se actualiza una aplicación sin cortes y cómo se organiza todo con Namespaces.

Pods: la unidad mínima

Kubernetes no ejecuta contenedores sueltos: los envuelve en un Pod, la unidad más pequeña que se puede crear y programar en un nodo.

  • Lo normal es un contenedor por Pod. Para escalar se crean más Pods, no más contenedores dentro del mismo Pod.
  • Un Pod puede tener varios contenedores cuando trabajan juntos de forma inseparable. Por ejemplo, un sidecar que recoge logs o el proxy de una service mesh. Esos contenedores comparten red (se ven en localhost) y almacenamiento.

Definir un Pod en YAML

Todo objeto de Kubernetes se describe con cuatro campos de primer nivel:

Campo Para qué sirve
apiVersion Versión de la API del objeto (v1 para Pods).
kind Tipo de objeto: Pod, ReplicaSet, Deployment...
metadata Nombre, etiquetas (labels) y otros datos del objeto.
spec El estado deseado: en un Pod, sus contenedores.
apiVersion: v1
kind: Pod
metadata:
  name: mi-pod
  labels:
    app: miapp
    tier: front-end
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - containerPort: 80

Comandos básicos

# Crear un Pod rápido, sin YAML (la imagen debe existir en el registro)
kubectl run nginx --image=nginx

# Crear o actualizar a partir del archivo
kubectl apply -f mi-pod.yaml

# Consultar
kubectl get pods                 # Pods del namespace actual
kubectl get pods -o wide         # con la IP y el nodo de cada Pod
kubectl get pods -A              # Pods de todos los namespaces
kubectl describe pod nginx       # detalle y eventos del Pod

ReplicaSets: varias copias, siempre vivas

Un Pod solo no es resiliente: si muere, nadie lo reemplaza. El ReplicaSet es un controlador que garantiza que siempre haya un número determinado de Pods iguales en ejecución:

  • Alta disponibilidad: si un Pod cae, crea otro.
  • Escalado y reparto de carga: las réplicas pueden distribuirse entre varios nodos.

Su antecesor era el ReplicationController, hoy considerado legado. El ReplicaSet hace lo mismo y añade un selector más flexible.

Selector y labels: cómo sabe qué Pods vigilar

El ReplicaSet no "recuerda" los Pods que creó: los busca por sus labels. El campo selector.matchLabels dice qué labels deben tener los Pods que le pertenecen, y la plantilla (template) define cómo crear los que falten.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: miapp-replicaset
  labels:
    app: miapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: miapp
      tier: front-end
  template:                 # plantilla de los Pods
    metadata:
      labels:
        app: miapp          # deben coincidir con el selector
        tier: front-end
    spec:
      containers:
        - name: web
          image: nginx
          ports:
            - containerPort: 80

Dos consecuencias importantes:

  • Un ReplicaSet puede adoptar Pods que ya existían si tienen las labels del selector.
  • Si creas a mano un Pod con esas mismas labels y ya se alcanzó el número de réplicas, el ReplicaSet lo elimina: nunca deja que haya más copias de las indicadas.

Escalar un ReplicaSet

kubectl get replicasets            # o: kubectl get rs
kubectl describe rs miapp-replicaset

# Opción 1: cambiar replicas en el YAML y aplicar
kubectl replace -f miapp-replicaset.yaml

# Opción 2: escalar desde la línea de comandos
kubectl scale rs miapp-replicaset --replicas=6

Ojo: kubectl scale cambia el objeto en el clúster, no tu archivo YAML. Si después aplicas el archivo, volverás al número de réplicas que diga el archivo.

Deployments: la forma habitual de desplegar

En la práctica casi nunca se crean ReplicaSets a mano. Se usa un Deployment, un objeto de nivel superior que crea y gestiona ReplicaSets por ti y añade lo que un ReplicaSet no sabe hacer: actualizar la aplicación de forma controlada y volver atrás.

Un Deployment gestiona ReplicaSets, y cada ReplicaSet gestiona Pods

Su YAML es casi idéntico al del ReplicaSet; cambia el kind:

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: web
          image: nginx:1.27
          ports:
            - containerPort: 80
kubectl apply -f miapp-deployment.yaml
kubectl get deployments
kubectl get rs          # el ReplicaSet que creó el Deployment
kubectl get pods
kubectl get all         # todo lo anterior de un vistazo

Rollouts, revisiones y rollback

Cada vez que cambias la plantilla de Pods de un Deployment (por ejemplo, la imagen), Kubernetes lanza un rollout y guarda una revisión. Por dentro, el Deployment crea un ReplicaSet nuevo con la versión nueva y va pasando los Pods del ReplicaSet viejo al nuevo.

# Seguir el progreso y ver el historial
kubectl rollout status deployment/miapp-deployment
kubectl rollout history deployment/miapp-deployment

# Actualizar la imagen sin editar el archivo
kubectl set image deployment/miapp-deployment web=nginx:1.28

# Volver a la revisión anterior (o a una concreta)
kubectl rollout undo deployment/miapp-deployment
kubectl rollout undo deployment/miapp-deployment --to-revision=1

Como el ReplicaSet anterior se conserva (con cero réplicas), el rollback es rápido: Kubernetes solo tiene que volver a escalarlo.

Sobre --record: en muchas guías verás kubectl apply --record para guardar el comando en el historial. Esa opción está obsoleta. Hoy se anota el motivo del cambio así: kubectl annotate deployment/miapp-deployment kubernetes.io/change-cause="nginx 1.28"

Estrategias de despliegue

Recreate apaga todo antes de crear; RollingUpdate reemplaza los Pods poco a poco

Estrategia Cómo funciona ¿Hay corte?
RollingUpdate (por defecto) Reemplaza los Pods poco a poco: crea uno nuevo, elimina uno viejo, y así sucesivamente. No
Recreate Elimina todos los Pods viejos y después crea todos los nuevos. Sí, mientras arrancan los nuevos

Se elige en spec.strategy.type del Deployment.

Imperativo frente a declarativo

Hay dos formas de trabajar con Kubernetes:

  • Imperativa: le dices al sistema qué hacer y cómo, paso a paso: crea esto, cambia aquello, borra lo otro.
  • Declarativa: describes el estado final en archivos y el sistema calcula qué cambios hacen falta para llegar a él.
Imperativo Declarativo
kubectl run, kubectl create, kubectl expose kubectl apply -f archivo.yaml
kubectl scale, kubectl set image kubectl apply -f carpeta/
kubectl edit (cambia el clúster, no tu archivo) El archivo es la fuente de verdad
kubectl replace --force -f (borra y recrea) Se puede versionar en Git

Lo imperativo es útil para pruebas rápidas y en exámenes con tiempo limitado. Para entornos reales, lo declarativo es lo recomendable: el archivo en Git refleja siempre lo que hay en el clúster.

Cómo funciona kubectl apply

kubectl apply compara tres versiones de cada objeto para decidir qué cambiar:

  1. Tu archivo local (lo que quieres).
  2. El objeto vivo en el clúster (lo que hay).
  3. La última configuración aplicada, que Kubernetes guarda en formato JSON como anotación del objeto, con la clave kubectl.kubernetes.io/last-applied-configuration.

Gracias a la tercera, apply sabe si quitaste un campo de tu archivo y debe borrarlo del objeto vivo, en lugar de dejarlo como estaba. Si el objeto no existe, simplemente lo crea.

Namespaces: organizar el clúster

Un Namespace es un espacio aislado dentro del clúster, con sus propios recursos, permisos y límites. Sirve para separar entornos (desarrollo, pruebas, producción) o equipos.

Kubernetes trae varios de serie:

Namespace Para qué se usa
default Donde se crean los recursos si no indicas otro.
kube-system Componentes internos de Kubernetes. No se toca.
kube-public Recursos legibles por todos los usuarios, incluso sin autenticar.
kube-node-lease Señales de vida (heartbeats) de los nodos.

Dos namespaces en el mismo clúster y cómo se llaman sus servicios entre sí

Nombres DNS entre namespaces

Dentro del mismo namespace, un servicio se alcanza por su nombre (db-service). Para llegar a uno de otro namespace se usa el nombre completo:

db-service.dev.svc.cluster.local
└─servicio─┘└ns┘└tipo┘└─dominio del clúster─┘

Trabajar con namespaces

# Crear
kubectl create namespace dev

# Ver recursos de un namespace concreto o de todos
kubectl get pods --namespace=dev
kubectl get pods --all-namespaces

# Cambiar el namespace por defecto de tu contexto
kubectl config set-context --current --namespace=dev

Para que un objeto se cree siempre en un namespace, ponlo en su metadata:

metadata:
  name: mi-pod
  namespace: dev

Limitar recursos con ResourceQuota

Un ResourceQuota fija el máximo de recursos que puede consumir un namespace en total:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cuota-dev
  namespace: dev
spec:
  hard:
    pods: "10"
    requests.cpu: "4"
    requests.memory: 5Gi
    limits.cpu: "10"
    limits.memory: 10Gi

Ideas clave

  • El Pod es la unidad mínima; normalmente lleva un solo contenedor.
  • Todo objeto tiene apiVersion, kind, metadata y spec.
  • El ReplicaSet mantiene N copias de un Pod y las encuentra por labels con su selector.
  • El Deployment gestiona ReplicaSets y añade rollouts, historial y rollback.
  • RollingUpdate (por defecto) no corta el servicio; Recreate sí.
  • Lo declarativo (kubectl apply) es lo recomendado; apply usa la anotación last-applied-configuration para calcular los cambios.
  • Los Namespaces separan recursos; entre ellos se usa el DNS servicio.namespace.svc.cluster.local, y un ResourceQuota limita su consumo.

En el próximo capítulo: scheduling, cómo decide Kubernetes en qué nodo va cada Pod.