Skip to main content

Persistent Volume

En la sesión anterior configuramos el volumen dentro del spec del pod. Toda la configuración para acceso al volumen está dentro del pod.

Cuando tenemos un ambiente con varios deployments, necesitaríamos configurar los parámetros del volumen en todas las definiciones.

Para evitar esto podemos gestionar el volumen de forma más centralizada donde el administrador pudiera crear un pool de volúmenes a nivel del cluster que pudiera ser utilizado por los usuarios para despliegue de aplicaciones en el cluster.

alt text

Cada aplicación crea un Persistent Volume Claim que es una solicitud de volumen. Vamos a entender más adelante.

Para crear un Persistent Volume:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /tmp/data
EOF

kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 10s

Este PV creado no tiene un Storage Class por haber sido provisionado manualmente. Ten eso en cuenta.

AccessModes define cómo el volumen debe ser montado EN TODOS LOS HOSTS. En el ejemplo anterior pusimos en /tmp que es una carpeta que tiene permiso para eso.

Podría ser:

  • ReadOnlyMany
  • ReadWriteOnce
  • ReadWriteMany

Nuevamente vamos a probar esta solución del hostPath y ver qué sucede.

Este sería un caso más productivo:

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
awsElasticBlockStore:
volumeID: <volume-id>
fsType: ext4

Vamos a aclarar los conceptos un poco más.

PersistentVolume (PV):

  • Un PersistentVolume es un recurso de almacenamiento provisionado en el cluster Kubernetes.
  • Existe independientemente de los Pods que lo utilizan.
  • Son provisionados manualmente por el administrador del cluster o de forma dinámica por medio de un provisionador de almacenamiento (Storage Class).
  • Representan el almacenamiento físico real disponible en el cluster, que puede ser discos, volúmenes de red, o cualquier otro tipo de almacenamiento soportado.
  • Los PVs tienen una especificación que define características como capacidad, modo de acceso (ReadWriteOnce, ReadWriteMany, ReadOnlyMany), entre otros.

PersistentVolumeClaim (PVC):

  • Es una solicitud hecha por un pod para un almacenamiento persistente.
  • Un PVC solicita un determinado tipo de almacenamiento (por ejemplo, una determinada capacidad y modo de acceso) y Kubernetes encuentra un PV adecuado que satisfaga esa solicitud.
  • Son usados por los desarrolladores para solicitar almacenamiento sin necesidad de preocuparse por los detalles específicos de cómo el almacenamiento es provisionado y gestionado en el cluster.
  • Cuando un PVC es creado y no hay PVs correspondientes disponibles, Kubernetes puede provisionar un nuevo PV automáticamente basándose en las especificaciones del PVC. Esto se conoce como provisionamiento dinámico.
  • Cuando un PVC es creado y hay un PV adecuado disponible, Kubernetes vincula automáticamente el PVC al PV correspondiente.

Un PVC solo existe vinculado a un PV y un PV solo puede tener un PVC cuando está vinculado.

El PVC solicita el acceso a un PV, ¿pero cuál? Basándose en coincidencias y parámetros. Así como podemos seleccionar un nodo que cumpla determinados requisitos para un pod, el PVC solicita un PV que cumpla sus requisitos.

Podríamos tener requisitos:

  • Sufficient Capacity
  • Access Modes
  • Volume Modes
  • Storage Class
  • Selector

En los metadatos de un volumen podemos tener las etiquetas para usar un matchLabels. Generalmente es bueno usar así porque más de un PV puede cumplir los requisitos del PVC.

Todos los criterios necesitan ser satisfechos, pero un PVC que necesita solo un espacio suficiente de 1Gi puede ser vinculado a un PV de 100Gi si no hay otra opción disponible. Si solo está ese, va ese mismo.

Si ningún PV satisface los criterios, el PVC quedará en Pending hasta que un PV satisfaga los criterios.

Vamos a hacer un PVC para que el PV anterior satisfaga:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
EOF

kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending

kubectl describe PVC myclaim
Name: myclaim
Namespace: default
StorageClass: standard # <<< Mira esto
Status: Pending
Volume:
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/PVC-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 12s (x2 over 24s) persistentvolume-controller waiting for first consumer to be created before binding

No definimos un Storage Class pero tiene uno. Cuando no se define, se usará el predeterminado.

kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 7d11h

kubectl describe sc standard
Name: standard
IsDefaultClass: Yes
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"standard"},"provisioner":"rancher.io/local-path","reclaimPolicy":"Delete","volumeBindingMode":"WaitForFirstConsumer"}
,storageclass.kubernetes.io/is-default-class=true
Provisioner: rancher.io/local-path
Parameters: <none>
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: WaitForFirstConsumer
Events: <none>

Si observaste bien, el PVC no hizo el bind con el PV.

Cuando un pod use ese PVC, el bind será hecho. Esto es una configuración del Storage Class (el provisionador) de mi cluster. Como no es posible editar un Storage Class por ser inmutable, necesitaría crear un Storage Class para esto para ver ese bind suceder sin necesidad de un pod.

Vamos a crear otro Storage Class entonces:

cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: Immediate
EOF

# Observa que este Storage Class no es el predeterminado del sistema, por lo que para ser usado necesita ser apuntado
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast rancher.io/local-path Delete Immediate false 5s
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 6d18h

# Otro PV apuntando al Storage Class
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol2
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /tmp/data
storageClassName: fast
EOF

kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 3h13m
pv-vol2 1Gi RWO Retain Available fast <unset> 8s

# Otro PVC pero forzando que esté en el Storage Class fast
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast
EOF

# Viendo el bind
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 56m
myclaim2 Bound pv-vol2 1Gi RWO fast <unset> 6s

kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 3h19m
pv-vol2 1Gi RWO Retain Bound default/myclaim2 fast <unset> 5m59s

Vamos a crear un PVC que buscaría el Storage Class fast, pero no tendría un PV para asociarse:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim3
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast
EOF

kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 63m
myclaim2 Bound pv-vol2 1Gi RWO fast <unset> 6m22s
myclaim3 Pending fast <unset> 2m26s

# Vamos a eliminar myclaim2 para liberar y ver qué sucede
kubectl delete PVC myclaim2
persistentvolumeclaim "myclaim2" deleted

# Aun así todavía no encontró disponible
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 6h36m
myclaim3 Pending fast <unset> 5h35m

kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 8h
pv-vol2 1Gi RWO Retain Released default/myclaim2 fast <unset> 5h43m

¿Qué sucede cuando eliminamos un PVC? Por defecto, incluso perdiendo el vínculo, el PV no es eliminado. Ocurre que un PV posee una política de retención de datos y el predeterminado es Retain.

persistentVolumeReclaimPolicy: Retain es el predeterminado

No definimos esto, por lo que mantuvo el Retain.

  • Retain: Los datos permanecen y ningún otro PVC puede asociarse, ¿NI EL MISMO?
  • Recycle: el PV vuelve a estar disponible cuando el PVC sea destruido y los datos serán borrados cuando otro PVC solicite el acceso.
  • Delete: el PV será destruido cuando el PVC sea destruido.

Si creo el mismo PVC anterior, ¿será asociado al PV?

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast

EOF
persistentvolumeclaim/myclaim2 created

kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 6h45m
myclaim2 Pending fast <unset> 6s
myclaim3 Pending fast <unset> 5h44m

# ¿Por qué todavía no fue?
kubectl describe PVC myclaim2
Name: myclaim2
Namespace: default
StorageClass: fast
Status: Pending
Volume:
Labels: <none>
Annotations: volume.beta.kubernetes.io/storage-provisioner: rancher.io/local-path
volume.kubernetes.io/storage-provisioner: rancher.io/local-path
Finalizers: [kubernetes.io/PVC-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Provisioning 29s (x2 over 44s) rancher.io/local-path_local-path-provisioner-7577fdbbfb-5dd24_6c93344f-2afb-45ff-b736-d7a5071e082a External provisioner is provisioning volume for claim "default/myclaim2"
Warning ProvisioningFailed 29s (x2 over 44s) rancher.io/local-path_local-path-provisioner-7577fdbbfb-5dd24_6c93344f-2afb-45ff-b736-d7a5071e082a failed to provision volume with StorageClass "fast": configuration error, no node was specified
Normal ExternalProvisioning 7s (x4 over 44s) persistentvolume-controller Waiting for a volume to be created either by the external provisioner 'rancher.io/local-path' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
#

No, ni creando un PVC idéntico conseguiremos ese PV.

TODO: Vamos a poner un intento de recuperación más adelante y ver los caminos posibles para hacer que un pod acceda a esos datos nuevamente.

Vamos a desplegar ahora un POD que usa un PVC y que dinámicamente creará el PV. Como vimos anteriormente, creamos un PVC y un PV no fue creado, y a continuación vamos a usar un código muy parecido y funcionará. El motivo de esto es que cuando usamos dentro del pod un PVC es en este momento que el provisionamiento dinámico sucede, en el momento en que dice juntar el PVC en el pod.

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
---
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
EOF

kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Bound PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO standard <unset> 4s

kubectl get pod
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 12s

# Podemos ver que el PV fue creado dinámicamente con la Reclaim Policy Delete, es decir, también será eliminado cuando el PVC sea eliminado
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO Delete Bound default/myclaim standard <unset> 19s

kubectl delete pod mypod
pod "mypod" deleted

kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Bound PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO standard <unset> 47s

kubectl delete PVC myclaim
persistentvolumeclaim "myclaim" deleted

kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE

➜ files git:(main)

TODO: Necesito digerir un poco más

Ya vimos que es posible reservar un PV para un PVC específico, pero necesito estar seguro entre el READ WRITE ONCE y MANY y si lo que hice localmente tendría algún problema.