Los contenedores llevan años despuntando en el desarrollo de aplicaciones. No es ningún secreto que Kubernetes se ha convertido en una gran plataforma para desarrollar las mejores aplicaciones. Hace un tiempo ya explicamos por qué todas las empresas apostaban por ellos y hoy ya nadie duda de su capacidad para aumentar la portabilidad y la agilidad al desplegar y gestionar aplicaciones.

Ya conocemos cuáles son sus características y las distintas plataformas según las necesidades. Ahora, en este post nos adentramos en el almacenamiento y persistencia de datos, pero antes de empezar haremos una pequeña introducción sobre Kubernetes.

¿Qué es Kubernetes?

Una definición muy simple sería decir que Kubernetes es un orquestador de contenedores, es decir, Kubernetes (K8S) se define como un sistema open-source para la automatización de despliegues, el escalado y la gestión de aplicaciones contenerizadas.

Esta plataforma ofrece un entorno de administración centrado en contenedores y orquesta la infraestructura de cómputo, redes y almacenamiento para que las cargas de trabajo de los usuarios no tengan que hacerlo. Estos son algunos conceptos importantes:

La gestión del almacenamiento y su persistencia

Hemos contado en la introducción la definición de Pod, pero lo que no hemos contado es que los Pods por defecto son efímeros y cuando se destruyen se pierde toda la información que contenían. Para poder conservar esa información tenemos que hablar de cómo persistir esos datos.

Volumen

En Kubernetes un volumen se puede considerar como un directorio al que pueden acceder los contenedores de un pod. Tenemos diferentes tipos de volúmenes y ese tipo define cómo se crea el volumen y su contenido.

Los volúmenes no se limitan a ningún contenedor. Soportan cualquiera o todos los contenedores desplegados dentro del pod de Kubernetes.

Una ventaja clave del volumen en Kubernetes es que admite diferentes tipos de almacenamiento en los que el pod puede utilizar varios de ellos al mismo tiempo.

Existen muchos tipos de volúmenes en Kubernetes, entre ellos: awselasticblockstore, azuredisk, azurefile, cephfs, cinder, configmap, downwardapi, emptydir, fc, flocker, gcepersistentdisk, gitrepo, glusterfs, hostpath, iscsi, local, nfs, persistentvolumeclaim, portworxvolume, projected, quobyte, rbd, secret, vspherevolume.

A continuación ponemos un ejemplo del tipo de volumen emptyDir.

Un volumen emptyDir se crea por primera vez cuando se asigna un Pod a un nodo, y existe mientras ese Pod se esté ejecutando en ese nodo. Como su nombre indica, el volumen emptyDir está inicialmente vacío. Todos los contenedores del Pod pueden leer y escribir los mismos archivos en el volumen emptyDir, aunque ese volumen puede ser montado en la misma o diferente ruta en cada contenedor. Cuando un Pod es eliminado de un nodo por cualquier razón, los datos en el emptyDir son borrados permanentemente.

Este es el contenido del fichero yml para crear un pod con un volumen:

apiVersion: v1
kind: Pod
metadata:
  name: mivolumepod
  namespace: paradigma
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: /tmp/nginx
      name: k8svolume
  - name: ubuntu
    image: ubuntu:20.04
    command: ['sh', '-c', 'while true; do sleep 30; done;']
    volumeMounts:
    - mountPath: /tmp/ubuntu
      name: k8svolume
  volumes:
  - name: k8svolume
    emptyDir: {}

Podemos ver cómo existe un volumen llamado k8svolume indicado en la etiqueta volumes y que es de tipo emptyDir:

volumes:
  - name: k8svolume
    emptyDir: {}

También vemos cómo asignar a cada contenedor el volumen e indicar la ruta con el contenido que queremos almacenar en el volumen.

En el contenedor nginx almacenamos la ruta /tmp/nginx:

volumeMounts:
    - mountPath: /tmp/nginx
      name: k8svolume

En el contenedor ubuntu almacenamos la ruta /tmp/ubuntu:

volumeMounts:
    - mountPath: /tmp/ubuntu
      name: k8svolume

Creamos el pod a partir del fichero .yml que hemos creado anteriormente con el siguiente comando.

kubectl apply -f volumePod.yml

Para ver el resultado del Pod que hemos creado ejecutamos el siguiente comando:

kubectl get pods -n paradigma
Kubernetes: almacenamiento y persistencia  1

Comprobamos que no existen datos en ninguna de las carpetas de cada contenedor listando las carpetas.

kubectl exec -it mivolumepod -c ubuntu -n paradigma -- ls -l /tmp/ubuntu

Kubernetes: almacenamiento y persistencia  2
kubectl exec -it mivolumepod -c nginx -n paradigma -- ls -l /tmp/nginx
Kubernetes: almacenamiento y persistencia  3

Creamos un fichero en alguna de las carpetas, podemos hacerlo en cualquiera de los contenedores. Y, en este caso, lo haremos sobre el contenedor de ubuntu:

kubectl exec -it mivolumepod -c ubuntu -n paradigma -- touch /tmp/ubuntu/test.json

Por último, listamos las carpetas en ambos contenedores y vemos que el fichero que hemos creado en uno de los contenedores es accesible desde ambos.

kubectl exec -it mivolumepod -c ubuntu -n paradigma -- ls -l /tmp/ubuntu
Kubernetes: almacenamiento y persistencia 4
kubectl exec -it mivolumepod -c nginx -n paradigma -- ls -l /tmp/nginx
Kubernetes: almacenamiento y persistencia  5

De esta manera hemos visto cómo compartir información entre contenedores dentro de un mismo Pod.

State Persistence

Ya os hemos contado que cuando un Pod es eliminado de un nodo por cualquier razón, los datos del volumen son borrados permanentemente. Para evitar que los datos se pierdan, Kubernetes nos permite implementar el almacenamiento persistente utilizando PersistentVolume y PersistentVolumeClaims.

Los PVCs se vincularán automáticamente a un PV que tenga StorageClass
y accessModes compatibles.

Si creamos un PVC con un storageClass:fast y un accessModes:RWO va a
buscar un PV con esas características y que esté disponible y automáticamente
se enlaza con él y es el PVC el que configuraremos al crear el YML del pod.

Kubernetes: almacenamiento y persistencia 6

PersistentVolume

Vamos a explicar las etiquetas importantes del PV:

A continuación el contenido del fichero yml para crear un PersistentVolume.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mipv
spec:
  storageClassName: local-storage
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

Creamos el PersistentVolume a partir del fichero .yml que hemos creado anteriormente con el siguiente comando.

kubectl apply -f persistentVolume.yml

Para ver el resultado del PersistentVolume ejecutamos el siguiente comando.

kubectl get pv
Kubernetes: almacenamiento y persistencia 7

Aunque vemos cómo se ha creado el volumen y el Status es Available, todavía no está asociado.

Para asociarlo a un Pod necesitamos un PVC (PersistentVolumeClaim) que os contamos a continuación.

PersistentVolumeClaim

¿Cuáles son las labels importantes del PVC?

A continuación el contenido del fichero yml para crear un PersistentVolumeClaim.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mipvc
  namespace: paradigma
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 512Mi

Como comentamos anteriormente, al crear el PVC si queremos que se enlace automáticamente al PV tiene que tener definido el mismo storageClassName y el mismo accessMode. En este caso lo hemos definido así: el PersistentVolumeClaim a partir del fichero .yml que hemos creado anteriormente con el siguiente comando.

kubectl apply -f persistentVolumeClaim.yml

Si quieres el resultado del PersistentVolumeClaim:

kubectl get pvc -n paradigma
Kubernetes: almacenamiento y persistencia  8

Aquí ya visualizamos cómo se ha creado el PVC y se ha enlazado con el volume mipv. También podemos ver de nuevo el volumen para ver que también nos indica el enlace.

kubectl get pv
Kubernetes: almacenamiento y persistencia  9

Observamos como en la columna CLAIM nos muestra el PVC al que se ha enlazado.

Por último, nos quedaría asignar este PV y PVC a un pod. Este es el contenido del fichero yml para crear un Pod asignándole el PVC que hemos creado:

apiVersion: v1
kind: Pod
metadata:
  name: mipvcpod
  namespace: paradigma
spec:
  containers:
  - name: ubuntu
    image: ubuntu:18.04
    command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]
    volumeMounts:
    - mountPath: "/mnt/storage"
      name: mivolume
  volumes:
  - name: mivolume
    persistentVolumeClaim:
      claimName: mipvc

Se añade la siguiente información:

volumes:
  - name: mivolume
    persistentVolumeClaim:
      claimName: mipvc

En ella indicamos que el volumen viene asociado a un PersistentVolumeClaim.

Luego, se crea el Pod a partir del fichero .yml (que tenemos de antes) con el siguiente comando:

kubectl apply -f persistentVolumePod.yml

Si queremos ver el resultado, ejecutamos el siguiente comando:

kubectl get pods -n paradigma
Kubernetes: almacenamiento y persistencia 10

Y para finalizar, podemos comprobar que se ha asignado correctamente el PV al Pod ejecutamos el siguiente comando:

kubectl describe pod mipvcpod -n paradigma
Kubernetes: almacenamiento y persistencia 11

En el apartado de Volumes observaremos que se ha asignado correctamente el Volumen. Al ser un PersistentVolume si eliminamos el Pod, el PV permanece y puede ser utilizado por cualquier Pod utilizando el PVC que le corresponde, en este caso mipvc.

Como hemos visto, los Pods son efímeros y al destruirlos pierden toda la información que tenían, por eso es importante poder almacenar y conservar esa información gracias a la persistencia de datos.

En este post hemos conocido el paso a paso para realizar el almacenamiento y persistencia de datos configurando Pods.

Buenas prácticas

El ciclo de vida del Persistent Volume (PV) es independiente de cualquier contenedor particular en el cluster. Las Persistent Volume Claim (PVC) son una petición realizada por un usuario o aplicación de contenedor para un tipo específico de almacenamiento. Al crear un PV, la documentación de Kubernetes recomienda lo siguiente:

Conclusión

El almacenamiento persistente de Kubernetes ofrece a las aplicaciones una forma conveniente de solicitar y consumir recursos de almacenamiento. PVC y PV son equivalentes a las interfaces e implementaciones orientadas a objetos. El Pod creado por el usuario declara el PVC, y Kubernetes encontrará un PV con el que emparejarlo. Si no hay un PV con el que emparejarse, va a la StorageClass correspondiente, le ayuda a crear un PV, y luego completa la unión con el PVC. El PV recién creado necesita crear un disco remoto para el host a través del nodo maestro vinculado y luego montar el disco remoto vinculado al directorio del host a través del componente kubelet de cada nodo.

Los PVs son ideales si tiene datos que tienen que ser compartidos entre Pods o que deben sobrevivir a los reinicios. Los PVs pueden ser definidos y enlazados a un Pod específico.

¿Te has quedado con alguna duda? ¡Déjanos un comentario!

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.