La forma de garantizar la disponibilidad es mantener un conjunto estable de réplicas de Pods ejecutándose en todo momento.

Introducción

Un cluster kubernetes se compone de varias máquinas virtuales o máquinas físicas independientes.

Entorno de trabajo

Para aprender, o hacer pruebas, puedes simular un cluster multi-node con minikube.

Asegúrate de cambiar la configuración de cpus y memory en función del número de nodos simulados que utilices!

$ minikube start --nodes 4 -p multi
...

Obtén la lista de los nodos:

$ kubectl get nodes
NAME        STATUS   ROLES           AGE     VERSION
multi       Ready    control-plane   5m27s   v1.32.0
multi-m02   Ready    <none>          4m58s   v1.32.0
multi-m03   Ready    <none>          4m29s   v1.32.0
multi-m04   Ready    <none>          4m      v1.32.0

También puedes comprobar el estado de tus nodos:

$ minikube status -p multi
multi
type: Control Plane   
host: Running
kubelet: Running      
apiserver: Running    
kubeconfig: Configured

multi-m02
type: Worker
host: Running
kubelet: Running 

...

ReplicaSet

Un ReplicationSet és un recurso que garantiza que un grupo de Pods siempre están ejecutándose.

Un Pod puede desaparecer por cualquier razón, por ejemplo que el nodo dónde se ejecuta el Pod desaparece del cluster o que el Pod ha sido desalojado del nodo.

Un ReplicaSet monitoriza constantemente mediante un "label selector" una lista de Pods que se están ejecutando, y se asegura que el número de Pods en ejecución sea igual al número deseado mediante la creación o eliminación de Pods a partir de una plantilla de Pod.

flowchart TB
  start@{shape: circle, label: "Comenzar" }
  start --> find
  style start fill:#00f

  watch(["Esperar notificación de modificación de recursos"])
  style watch fill:#048
  watch == notificación recibida ==> find


  find([Encontrar Pods que hacen match en un 'label selector'])
  find ==> compare
  style find fill:#080
  
  compare@{ shape: hex, label: "Comparar número de Pods encontrados con el número de pots deseados" }
  compare == faltan ==> few

  compare -- ok --> watch

  few([Crear Pods adicionales a partir de la plantilla])
 
  many([Eliminar los Pods que sobran])
  few --> watch
  compare == sobran ==> many
  many --> watch
  style few fill:#800
  style many fill:#800

Por tanto, un ReplicaSet necesita tres elementos fundamentales:

  • Un label selector, que determina que Pods tiene que controlar
  • Un replica count que especifica el número deseado de Pods que deberían estar en ejecución
  • Un Pod template que se utiliza cuando se crean nuevas réplicas del Pod.

En cualquier momento puedes modificar el número de replicas, pero los cambios en el "label selector" y el "Pod template" no afectan a los Pods que se están ejecutando.

A continuación crea un fichero nginx.yaml:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80 

Crea el ReplicaSet:

$ kubectl apply -f nginx.yaml
replicaset.apps/nginx created

Puedes ver que se han creado tres Pods con la etiqueta nginx en tres nodos diferentes:

$ kubectl get pods -o wide -L app
NAME          READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES   APP
nginx-78wzt   1/1     Running   0          11s   10.244.2.3   multi-m03   <none>           <none>            nginx
nginx-mxf4c   1/1     Running   0          11s   10.244.1.4   multi-m02   <none>           <none>            nginx
nginx-wdjsh   1/1     Running   0          11s   10.244.3.4   multi-m04   <none>           <none>            nginx

Kubernetes intenta desplegar los Pods en nodos diferentes para "no tener todos los huevos en la misma cesta".

Borra uno de los Pods:

$ kubectl delete pod nginx-78wzt 
pod "nginx-78wzt" deleted

Y verifica que el controlador de replicación crea un nuevo Pod:

$ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-lvw9l   1/1     Running   0          19s
nginx-mxf4c   1/1     Running   0          54s
nginx-wdjsh   1/1     Running   0          54s

Como hemos dicho al principio, un ReplicationSet és un recurso igual que un Pod.

Por tanto, puedes obtener una lista de todos los ReplicaSet que se han creado:

$ kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
nginx   3         3         3       96s

Así com una descripción de un controlador de replicación determinado:

$ kubectl describe rs nginx
Name:         nginx
Namespace:    default
Selector:     app=nginx
Labels:       <none>
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
  Containers: ...
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  2m9s  replicaset-controller  Created pod: nginx-wdjsh
  Normal  SuccessfulCreate  2m9s  replicaset-controller  Created pod: nginx-mxf4c
  Normal  SuccessfulCreate  2m9s  replicaset-controller  Created pod: nginx-78wzt
  Normal  SuccessfulCreate  94s   replicaset-controller  Created pod: nginx-lvw9l

La lista de eventos muestra las acciones realizadas hasta el momento por el controlador: ha creado 4 Pods.

Respondiendo a un fallo de nodo

Cuando pides a kubernetes que despliegue un Pod, el despliegue se hará en cualquier nodo que esté disponible.

El problema es que si este nodo "muere" ...

$ minikube -p multi node stop multi-m02
✋  Stopping node "multi-m02"  ...
🛑  Apagando "multi-m02" mediante SSH...
🛑  Successfully stopped node multi-m02

El nodo ya no está Ready:

$ kubectl get nodes
NAME        STATUS     ROLES           AGE   VERSION
multi       Ready      control-plane   3d    v1.32.0
multi-m02   NotReady   <none>          3d    v1.32.0
multi-m03   Ready      <none>          3d    v1.32.0
multi-m04   Ready      <none>          3d    v1.32.0

Importante. El resultado no es inmediato porque el cluster primero se tiene que asegurar que no es un problema temporal de conexión de pocos segundos.

Si miras las lista de Pods, puedes ver el Pod nginx-lvw9l está Running en el nodo multi-m02 ...

$ kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
nginx-lvw9l   1/1     Running   0          3m42s   10.244.2.4   multi-m03   <none>           <none>
nginx-mxf4c   1/1     Running   0          4m17s   10.244.1.4   multi-m02   <none>           <none>
nginx-wdjsh   1/1     Running   0          4m17s   10.244.3.4   multi-m04   <none>           <none>

Aunque puedes verificar que el Pod ya no está ejecutándose:

$ kubectl exec nginx-lvw9l -- bash
error: Internal error occurred: error sending request: Post "https://192.168.49.3:10250/exec/default/nginx-gmkgq/nginx?command=bash&error=1&output=1": dial tcp 192.168.49.3:10250: connect: no route to host

El motivo es que Kubernetes espera un rato para asegurarse de que definitivamente la falta de conexión con el nodo no es por un fallo temporal en la red o porque Kubelet se está reiniciando.

Pero después de ese tiempo, Kubernetes notifica un cambio de recursos y el controlado de recursos nginx crea un nuevo Pod:

$ kubectl get pods -o wide
NAME          READY   STATUS        RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
nginx-lvw9l   1/1     Running       0          9m36s   10.244.2.4   multi-m03   <none>           <none>
nginx-mxf4c   1/1     Terminating   0          10m     10.244.1.4   multi-m02   <none>           <none>
nginx-wdjsh   1/1     Running       0          10m     10.244.3.4   multi-m04   <none>           <none>
nginx-zbw6q   1/1     Running       0          50s     10.244.0.4   multi       <none>           <none>

Vuelve a arrancar el nodo multi-m02:

$ minikube -p multi node start multi-m02
👍  Starting "multi-m02" worker node in "multi" cluster
🚜  Pulling base image v0.0.46 ...
🔄  Restarting existing docker container for "multi-m02" ...
🐳  Preparando Kubernetes v1.32.0 en Docker 27.4.1...
🔎  Verifying Kubernetes components...
🌟  Complementos habilitados: 
😄  Successfully started node multi-m02!

Etiquetas

Un controlador sólo gestiona los Pods que coinciden con su selector de etiquetas.

Para saber a que controlador está asociado un Pod en ese momento, puedes mirar el atributo metadata.ownerReferences:

$ kubectl get pods nginx-lvw9l -o=jsonpath='{.metadata.ownerReferences[0]}'
{ 
  "apiVersion":"apps/v1",
  "blockOwnerDeletion":true,
  "controller":true,
  "kind":"ReplicaSet",
  "name":"nginx",
  "uid":"bb526ba4-f15a-4517-a056-b6ef8c8cd531"
}

A un ReplicaSet no le importa si añades etiquetas adicionales a los Pods que gestiona:

$ kubectl label pod nginx-zbw6q env=prod
pod/nginx-zbw6q labeled

Puedes ver que los Pods son los mismos que antes porque este cambio no afecta de ningun manera al ReplicaSet:

$ kubectl get pods --show-labels
NAME          READY   STATUS    RESTARTS   AGE     LABELS
nginx-lvw9l   1/1     Running   0          14m     app=nginx
nginx-wdjsh   1/1     Running   0          15m     app=nginx
nginx-zbw6q   1/1     Running   0          5m55s   app=nginx,env=prod

Pero si cambias el valor de la etiqueta app como se muestra a continuación:

$ kubectl label pod nginx-lvw9l app=nginx-old --overwrite
pod/nginx-lvw9l labeled

El ReplicaSet sólo encontrará dos Pods etiquetados como app=nginx, y creará un nuevo Pod con esta etiqueta:

$ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
nginx-gvt68   1/1     Running   0          83s   nginx
nginx-lvw9l   1/1     Running   0          28m   nginx-old
nginx-wdjsh   1/1     Running   0          29m   nginx
nginx-zbw6q   1/1     Running   0          19m   nginx

El flag --overwrite es necesario porque de esta forma no modificarás de manera accidental una etiqueta.

Puedes verificar que el Pod nginx-lvw9l ya no está gestionado por el controlador:

$ kubectl get pods nginx-lvw9l -o=jsonpath='{.metadata.ownerReferences[0]}'

Esto es útil cuando un Pod no funciona correctamente y quieres probar que está pasando: lo sacas del alcance del controlador, que creará automáticamente un Pod para reemplazarlo, y puedes depurar o averiguar que está sucediendo para luego eliminarlo.

Escalando horizontalmente

Puedes modificar el número de Pods cambiando el valor del atributo spec.replicas.

Puedes aumentar el número de Pods

$ kubectl scale rs nginx  --replicas=5
replicaset.apps/nginx scaled

$ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
nginx-gvt68   1/1     Running   0          21m   nginx
nginx-lvw9l   1/1     Running   0          48m   nginx-old
nginx-m7b46   1/1     Running   0          39s   nginx
nginx-sm7zx   1/1     Running   0          39s   nginx
nginx-wdjsh   1/1     Running   0          49m   nginx
nginx-zbw6q   1/1     Running   0          40m   nginx

O reducir el número de Pods:

$ kubectl scale rs nginx --replicas=2
replicaset.apps/nginx scaled

$ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
nginx-lvw9l   1/1     Running   0          51m   nginx-old
nginx-wdjsh   1/1     Running   0          52m   nginx
nginx-zbw6q   1/1     Running   0          43m   nginx

Eliminar un ReplicaSet

Cuando eliminar un ReplicaSet mediante kubectl delete, también se eliminan los Pods a menos que utilices el flag --cascade=orphan:

$ kubectl delete rs nginx --cascade=false
replicaset.apps "nginx" deleted

$ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
nginx-lvw9l   1/1     Running   0          55m   nginx-old
nginx-wdjsh   1/1     Running   0          56m   nginx
nginx-zbw6q   1/1     Running   0          47m   nginx

Si vuelves a crea de nuevo el ReplicaSet, sólo se necesita crear un nuevo Pod para satisfacer las restricciones:

$ kubectl apply -f nginx.yaml 
replicaset.apps/nginx created

$ kubectl get pods -L app
NAME          READY   STATUS              RESTARTS   AGE   APP
nginx-lnfrd   0/1     ContainerCreating   0          2s    nginx
nginx-lvw9l   1/1     Running             0          57m   nginx-old
nginx-wdjsh   1/1     Running             0          57m   nginx
nginx-zbw6q   1/1     Running             0          48m   nginx

Borra todos los recursos:

$ kubectl delete rs nginx
replicaset.apps "nginx" deleted

$ kubectl delete pod nginx-lvw9l 
pod "nginx-lvw9l" deleted

$ kubectl get pods
No resources found in default namespace.

DaemonSet

En un ReplicaSet, Kubernetes despliega el número de Pods indicados en el recurso en los nodos del cluster que le parezcan mejor.

En cambio, en un DaemonSet Kubernetes despiega un único Pod para cada nodo de cluster.

Esto es útil para Pods de sistema com puede ser un recopilador de "logs", un monitor de recursos, el proceso kube-proxy, etc.

Por ejemplo, Fluentd es un recopilador de datos para unificar el registro de eventos del sistema.

Crea el fichero fluentd.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
      name: fluentd
  template:
    metadata:
      labels:
        name: fluentd
    spec:
      nodeSelector:
        fluentd-enabled: "true"
      containers:
        - name: fluentd-elasticsearch
          image: quay.io/fluentd_elasticsearch/fluentd:latest

Puedes ver que en la configuración del recurso utilizamos un selector de etiquetas para limitar los nodos en que se desplegará el Pod.

Por ejemplo, etiqueta el nodo multi-m02 con fluentd-enabled=true:

$ kubectl label node multi-m02 fluentd-enabled=true
node/multi-m02 labeled

Crea el recurso fluentd:

$ kubectl apply -f fluentd.yaml
daemonset.apps/fluentd created

Puedes ver que sólo se ha desplegado un Pod fluend-????? en el cluster:

$ kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
fluentd-wdh2j   1/1     Running   0          34s   10.244.1.4   multi-m02   <none>           <none>

Edita el fichero fluentd.yaml y elimina el selector:

    ...
    spec:
      containers:
        - name: fluentd-elasticsearch
          image: quay.io/fluentd_elasticsearch/fluentd:latest

Actutaliza el recurso:

$ kubectl apply -f fluentd.yaml 
daemonset.apps/fluentd configured

Ahora se crea un Pod fluend-????? en cada nodo del cluster:

$ kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
fluentd-gj4hs   1/1     Running   0          15s   10.244.2.3   multi-m03   <none>           <none>
fluentd-gxrfv   1/1     Running   0          15s   10.244.3.3   multi-m04   <none>           <none>
fluentd-rp7vz   1/1     Running   0          15s   10.244.0.4   multi       <none>           <none>
fluentd-xqlvp   1/1     Running   0          11s   10.244.1.5   multi-m02   <none>           <none>

Vuelve a añadir el selector de nodo y verifica que se eliminan todos los Pods excepto el de multi-m02:`

$ nano fluentd.yaml 

$ kubectl apply -f fluentd.yaml 
daemonset.apps/fluentd configured

$ kubectl get pods -o wide
NAME            READY   STATUS        RESTARTS   AGE    IP           NODE        NOMINATED NODE   READINESS GATES
fluentd-gxrfv   0/1     Completed     0          7m9s   10.244.3.3   multi-m04   <none>           <none>
fluentd-xqlvp   1/1     Terminating   0          7m5s   10.244.1.5   multi-m02   <none>           <none>

Modifica la etiqueta del nodo multi-m02 a fluentd-enabled=false y verifica que el Pod fluentd-xqlvp se elimina:

$ kubectl label node multi-m02 fluentd-enabled=false --overwrite
node/multi-m02 labeled

$ kubectl get pods -o wide
NAME            READY   STATUS      RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
fluentd-mszqj   0/1     Completed   0          44s   10.244.1.6   multi-m02   <none>           <none>

TODO