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>