Sistemas de Almacenamiento en Docker (Parte I)

Por Javier Ramírez.

Siempre que hablamos de infraestructuras basadas en contenedores surgen las mismas dudas alrededor del almacenamiento de datos y meta-información de la plataforma.

Es fácil identificar dos aspectos muy diferentes del almacenamiento en Docker. Por una parte, tendremos el almacenamiento propio de Docker, donde se almacena toda la meta-información y referencias a objetos. Por otra parte, podremos identificar el almacenamiento propio de los aplicativos que corren en los contenedores.

El espacio de almacenamiento de meta-información y objetos (contenedores, volúmenes, redes, etc…) se gestiona mediante el denominado “graph driver”. En este aspecto, es importante identificar los directorios de trabajo de Docker bajo /var/lib/docker ya que será necesario controlar el crecimiento de los mismos. En el ejemplo mostrado, ejecutado sobre el portátil en el que se escribe este artículo, no existe un filesystem específico para /var/lib/docker; además, el filesystem / está formateado en btrfs. Esto supone que la salida en otros sistemas será diferente, dependiendo del sistema de ficheros usado en el punto de montaje.

# tree -L 2 /var/lib/docker

.

├── btrfs

│   └── subvolumes

├── containers

├── image

│   └── btrfs

├── network

│   └── files

├── plugins

│   ├── storage

│   └── tmp

├── swarm

│   ├── certificates

│   ├── docker-state.json

│   ├── raft

│   ├── state.json

│   └── worker

├── tmp

├── trust

└── volumes

├── dockerdev-go-pkg-cache-gopath

├── dockerdev-go-pkg-cache-goroot-linux_amd64

├── dockerdev-go-pkg-cache-goroot-linux_amd64_netgo

└── metadata.db

En un entorno productivo es imprescindible contar con un filesysem específico para /var/lib/docker con tamaño suficiente para albergar imágenes, contenedores, volúmenes y la propia gestión del cluster swarm. El tamaño mínimo para esta partición es complicado de prever sin conocer el número de imágenes y sus versiones, volúmenes y contenedores, así como el número de cambios respecto a las imágenes originales que se generen en estos últimos.

Recordemos que un contenedor no es más que la ejecución de un árbol de procesos y los cambios que se realicen sobre el sistema de ficheros de la imágenes base. De esta forma, si no especificamos ningún volumen ni en la ejecución del contenedor ni en la imagen base, todos los cambios se registrarán sobre el directorio específico del propio contenedor. Si por el contrario se definen volúmenes para este contenedor, se guardará la información (se creará en caso de no existir) en su directorio de volumen específico.

Es posible seleccionar el sistema de ficheros que queremos que use Docker Engine para gestionar este tipo de almacenamiento; permitiendo especificar el más acorde a nuestras necesidades. Hay que tener en cuenta también que el sistema operativo debe soportar el driver gestor de almacenamiento seleccionado lo que nos guiará sobre nuestra selección. En todo caso, usaremos un sistema de ficheros que nos permita optimizar las tareas de despliegue, gestión de cambios y el uso de recursos.

Esta limitación de elección en el sistema de gestión de este almacenamiento debido al sistema operativo se aprecia notablemente en los sistemas basados en RedHat. En estos, el sistema de almacenamiento de capas se basa en device mapper con “thin provisioning” y sistema de copy-on-write. Este sistema creará dos dispositivos lógicos, uno para datos y otro para metadatos. Esto supone un sistema de volúmenes lógicos que provocará defragmentación cuando la creación y destrucción de contenedores y volúmenes sea generalizada (un sistema de Contenedores como Servicio por ejemplo, evolución de un PaaS), necesitando de tareas de mantenimiento sobre los hosts para normalizar el sistema de ficheros de vez en cuando.

Aufs es el sistema de ficheros que se comenzó a usar debido a que se obtenían grandes beneficios al ejecutar un gran número de contenedores con librerías y ejecutables comunes (servicios escalados con muchas instancias).

Por su parte, btrfs es un sistema rápido pero no optimiza recursos cuando se trabaja con contenedores que parten de una misma imagen.

Overlay y Overlay2 están basados en unionfs, comparten cache de acceso a ficheros y son bastante rápidos en la gestión de cambios respecto de la imagen base, pero requieren kernels modernos, no disponibles en todas las distribuciones Linux. En el caso de overlay, hacer uso de un gran número de imágenes supone un consumo excesivo de inodos que afecta al sistema operativo por lo que es recomendable el uso de overlay2, que mejora considerablemente la versión anterior, pero requiere kernels superiores a la versión 4.0.

Hasta ahora hemos revisado el almacenamiento “operacional” en el sentido más estricto. No hemos tenido en cuenta ningún tipo de persistencia de dato.

Antes de entrar en el ámbito de la persistencia de datos, es importante aclarar que los contenedores no son efímeros en el sentido más estricto. Existe la creencia errónea de que los contenedores se crean, ejecutan y destruyen cuando su árbol de procesos muere, pero no es así. Los contenedores tienen existencia más allá de su ejecución. Todo contenedor persiste en el sistema de ficheros del host hasta que se ejecute su eliminación o se explicite voluntariamente su comportamiento efímero en la creación del mismo (docker run –rm …..).

Con esta explicación, queremos hacer notar que nuestro sistema de ficheros requiere mantenimiento ya que los volúmenes creados en tiempo de ejecución y el propio “runtime” de los contenedores permanece en el sistema host hasta que se realice un borrado. Estos datos son persistentes en el host. Veamos esto en un ejemplo sencillo.

# docker run –name test -P -d nginx:alpine

655b2fea9d79a81748b0ae4e6cf3c80f01e759de40786d4f62de50f0f510e57f

# docker exec test touch /tmp/TEST_FILE

# docker exec test ls -l /tmp/TEST_FILE

-rw-r–r– 1 root root 0 Mar 21 10:18 /tmp/TEST_FILE

# docker stop test

test

# ls -lart /var/lib/docker/containers/655b2fea9d79a81748b0ae4e6cf3c80f01e759de40786d4f62de50f0f510e57f/

# docker start test

test

# docker exec test ls -l /tmp/TEST_FILE

-rw-r–r– 1 root root 0 Mar 21 10:18 /tmp/TEST_FILE

# docker stop test

test

# docker run –name test -P -d nginx:alpine

docker: Error response from daemon: Conflict. The container name “/test” is already in use by container 655b2fea9d79a81748b0ae4e6cf3c80f01e759de40786d4f62de50f0f510e57f. You have to remove (or rename) that container to be able to reuse that name..

Como vemos en la última ejecución, el contenedor “test” existe siempre que no se especifique explícitamente su borrado.

Docker provee de herramientas de purgado más o menos selectivas que permiten mantener el entorno de ejecución lo más saneado posible.

Para deshacernos de los contenedores y sus volúmenes asociados que ya no son necesarios en nuestro sistema podemos ejecutar “docker rm -v”.

# docker run –name test -P -d nginx:alpine

3e014722a0a3ca15807db92a30ec306ec7e7de925dbd2751d928f05ae8c4ba49

# docker rm test

Error response from daemon: You cannot remove a running container 3e014722a0a3ca15807db92a30ec306ec7e7de925dbd2751d928f05ae8c4ba49. Stop the container before attempting removal or use -f

# docker rm -f test

test

Desde la versión 1.13 (conocida desde hace unos días como 17.03) existe el comando “docker system prune” que facilita el mantenimiento de los contenedores, volúmenes e imágenes almacenados en el sistema, que ya no están en uso y que consumen espacio.

# docker system df

TYPE TOTAL ACTIVE SIZE RECLAIMABLE

Images 281 20 45.56 GB 44.59 GB (97%)

Containers 38 3 69.32 MB 69.32 MB (99%)

Local Volumes 18 6 478.9 MB 312.1 MB (65%)

# docker system prune

WARNING! This will remove:

– all stopped containers

– all volumes not used by at least one container

– all networks not used by at least one container

– all dangling images

Are you sure you want to continue? [y/N] y

Deleted Containers:

a62f44257b07c3040ecef7eb24b94a04f96b430d82de5b439ab7186d8d7a1e6f

d43c13cae81afe4204365236c3e17956f7b1e066a05d4d23d24d09382144b729

15b879a8a7bfba3acb742e8f553efd52d706b6ed73c41e0e2f4293e666c48915

……………………………………..

……………………………………..

Deleted Volumes:

4d5f11dc2feb2c6ebf4a21f6f679bf15b841ff5a959eb44c540ebf475a0c3c58

5220970522df56362d330ba1106d9b57eab4bed3b615f960edca6afcd8408d24

PROMETEHUSDATA

dockerpg_PGDATA

……………………………………..

……………………………………..

Deleted Networks:

demo_simplestdemo

monitoring

test_simplestdemo

Deleted Images:

deleted: sha256:eb074182e13a32a6e3123f67cc35b4e884fb29924e930dc3032efc7e94e28e3d

deleted: sha256:1cc43dbaf6eea5510796006a38417fb114ebc8ff600821299d3582c7643095d1

deleted: sha256:e9f0e7ca2a238c7aed9ebe65e68a1fb74550c3462167f318c4f5c6463f3864c9

……………………………………..

……………………………………..

Total reclaimed space: 815.7 MB

Cuando hablamos de contenedores como objetos efímeros, sí encontramos sentido cuando relacionamos el concepto con la necesidad de que puedan ejecutarse en cualquier infraestructura (nube pública, privada, servidores virtuales, físicos o incluso los portátiles de los desarrolladores) y en cualquiera de los nodos de un cluster Swarm. Esto supone que los contenedores deben comportarse de forma efímera, dado que deben contener todo lo necesario para dar servicio en cualquier infraestructura en la que lancemos su ejecución.

En el siguiente post entraremos en materia en el almacenamiento de datos de aplicativo que tantos quebraderos de cabeza parece que pueda darnos, pero veremos que al final se trata de un problema ya resuelto en otros entornos muy similares como la virtualización.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.